MW211 EXIT

devlog
MSSQL/マッチング・後編(Ⅱ)
2015年05月14日
MERGE文はあきらめきれないあなたに。

【案1】CASE文で差分がなければ現状維持の値を代入する
┌──────────────────────────────────────┐
│MERGE INTO [旧表]                                                           │
│    USING [新表]                                                            │
│       ON [新表].[キー] = [旧表].[キー]                                     │
│    WHEN MATCHED THEN                                                       │
│        UPDATE SET [列]       = [新表].[列],                                │
│                   [更新日時] = CASE                                        │
│                                  WHEN [旧表].[列] = [新表].[列] THEN       │
│                                    [旧表].[更新日時]                       │
│                                  ELSE                                      │
│                                    getdate()                               │
│                                END                                         │
│    WHEN NOT MATCHED BY TARGET THEN                                         │
│        INSERT (                                                            │
│                [キー],                                                     │
│                [列],                                                       │
│                [更新日時]                                                  │
│            ) VALUES (                                                      │
│                [新表].[キー],                                              │
│                [新表].[列],                                                │
│                getdate()                                                   │
│            );                                                              │
└──────────────────────────────────────┘

【案2】まず重複するものを除いたからMERGEを行う
┌──────────────────────────────────────┐
│MERGE INTO [旧表]                                                           │
│    USING (                                                                 │
│        SELECT *                                                            │
│            FROM [新表]                                                     │
│            WHERE NOT EXISTS (                                              │
│                      SELECT *                                              │
│                          FROM [旧表]                                       │
│                          WHERE [旧表].[キー] = [新表].[キー]               │
│                            AND [旧表].[列]   = [新表].[列]                 │
│                  )                                                         │
│    ) AS [新表]                                                             │
│       ON [新表].[キー] = [旧表].[キー]                                     │
│    WHEN MATCHED THEN                                                       │
│        UPDATE SET [列]       = [新表].[列],                                │
│                   [更新日時] = getdate()                                   │
│    WHEN NOT MATCHED BY TARGET THEN                                         │
│        INSERT (                                                            │
│                [キー],                                                     │
│                [列],                                                       │
│                [更新日時]                                                  │
│            ) VALUES (                                                      │
│                [新表].[キー],                                              │
│                [新表].[列],                                                │
│                getdate()                                                   │
│            );                                                              │
└──────────────────────────────────────┘

【案2】が拡張性があってよさげだ。

※NULL値の比較に注意
分類:MSSQL
MSSQL/非NULL列の追加
2015年05月13日
┌──────────────────────────────────────┐
│ALTER TABLE [表] ADD [列] [int] NOT NULL;                                   │
└──────────────────────────────────────┘
「NOT NULL」制約の列を後から追加した場合、エラーとなる。
追加した列の初期値が「NULL」だからだ(矛盾してしまう。

なので、既定値付きなら問題ない。
┌──────────────────────────────────────┐
│ALTER TABLE [表] ADD [列] [int] NOT NULL DEFAULT 0;                         │
└──────────────────────────────────────┘

つまり、最初のやつは以下のような指定だったわけだ(これは矛盾している)。
┌──────────────────────────────────────┐
│ALTER TABLE [表] ADD [列] [int] NOT NULL DEFAULT NULL;                      │
└──────────────────────────────────────┘

でも、こういうことをやりたいケースというのは多々ある。

これを実現するには迂回する形で3ステップ必要となる。
┌──┬───────────────────────────────────┐
│(1) │ALTER TABLE [表] ADD [列] [int] NULL;                                 │
├──┼───────────────────────────────────┤
│(2) │UPDATE [表] SET [列] = 0;                                             │
├──┼───────────────────────────────────┤
│(3) │ALTER TABLE [表] ALTER COLUMN [列] [int] NOT NULL;                    │
└──┴───────────────────────────────────┘
  (1) ひとまずNULL許容属性で追加する
  (2) NULLじゃない値にする
  (3) NOT NULL制約をつける
分類:MSSQL
MSSQL/対等外部結合
2015年04月22日
外部結合をつかって、二つの表を対等外部結合してみた。
┌──────────────────────────────────────┐
│SELECT ISNULL([表A].[キー],[表B].[キー]) AS [キー],                       │
│       [表A].[項目A],                                                     │
│       [表B].[項目B]                                                      │
│    FROM [表A]                                                             │
│        FULL JOIN [表B]                                                    │
│          ON [表B].[キー] = [表A].[キー]                                  │
└──────────────────────────────────────┘
結果は上々で、以下三パターンのレコードとなった(もちろんキーの重複はなし)。
┌──┬───┬───┐
│キー│項目A│項目B│
├──┼───┼───┤
│キー│項目A│  -  │
├──┼───┼───┤
│キー│  -  │項目B│
└──┴───┴───┘

これに気を良くして、三つの表でこれを試みた。
┌──────────────────────────────────────┐
│SELECT ISNULL([表A].[キー],ISNULL([表B].[キー],[表C].[キー])) AS [キー], │
│       [表A].[項目A],                                                     │
│       [表B].[項目B],                                                     │
│       [表C].[項目C]                                                      │
│    FROM [表A]                                                             │
│        FULL JOIN [表B]                                                    │
│          ON [表B].[キー] = [表A].[キー]                                  │
│        FULL JOIN [表C]                                                    │
│          ON [表C].[キー] = [表A].[キー]                                  │
└──────────────────────────────────────┘
すると基幹となる表がない場合、二つに分裂してしまった。
┌──┬───┬───┬───┐
│キー│項目A│項目B│項目C│
├──┼───┼───┼───┤
│キー│項目A│項目B│  -  │
├──┼───┼───┼───┤
│キー│項目A│  -  │項目C│
├──┼───┼───┼───┤
│キー│項目A│  -  │  -  │
├──┼───┼───┼───┤
│キー│  -  │項目B│項目C│
├──┼───┼───┼───┤
│キー│  -  │項目B│  -  │←二件レコードができてしまう
│キー│  -  │  -  │項目C│
└──┴───┴───┴───┘

なので、トーナメントの二段階選抜的な形にしなければならない。
┌──────────────────────────────────────┐
│SELECT ISNULL([連合D].[キー],[表C].[キー]) AS [キー],                     │
│       [連合D].[項目A],                                                   │
│       [連合D].[項目B],                                                   │
│       [表C].[項目C]                                                      │
│    FROM (                                                                  │
│        SELECT ISNULL([表A].[キー],[表B].[キー]) AS [キー],               │
│               [表A].[項目A],                                             │
│               [表B].[項目B]                                              │
│            FROM [表A]                                                     │
│                FULL JOIN [表B]                                            │
│                  ON [表B].[キー] = [表A].[キー]                          │
│    )AS [連合D]                                                            │
│        FULL JOIN [表C]                                                    │
│          ON [表C].[キー] = [連合D].[キー]                                │
└──────────────────────────────────────┘

でも、これだと四つ以上になった時に複雑度が増していきそうなので
(でもそれこそトーナメント式(B木)で書けたりして(でもいずれにせよ煩雑になりそう))
以下の様に、基準データをあらかじめ決めてから、
そこに左外部結合していくのがいいかも。
┌──────────────────────────────────────┐
│SELECT [基準D].[キー],                                                     │
│       [表A].[項目A],                                                     │
│       [表B].[項目B],                                                     │
│       [表C].[項目C]                                                      │
│    FROM (                                                                  │
│        SELECT [キー] FROM [表A]                                           │
│        UNION                                                               │
│        SELECT [キー] FROM [表B]                                           │
│        UNION                                                               │
│        SELECT [キー] FROM [表C]                                           │
│    ) AS [基準D]                                                           │
│        LEFT JOIN [表A]                                                    │
│          ON [表A].[キー] = [基準D].[キー]                                │
│        LEFT JOIN [表B]                                                    │
│          ON [表B].[キー] = [基準D].[キー]                                │
│        LEFT JOIN [表C]                                                    │
│          ON [表C].[キー] = [基準D].[キー]                                │
└──────────────────────────────────────┘
分類:MSSQL
MSSQL/年月で集計
2015年04月19日
日付データ(日時データ)を月毎に集計する方法。
┌──────────────────────────────────────┐
│SELECT LEFT(CONVERT(varchar, [日時], 111), 7) AS [年月],                    │
│       SUM([値])                              AS [合計]                     │
│    FROM [表]                                                               │
│    GROUP BY LEFT(CONVERT(varchar, [製造完了日時], 111), 7);                │
└──────────────────────────────────────┘
「YYYY/MM/DD」の形式にして、「YYYY/MM」だけ見て集計する。
分類:MSSQL
MSSQL/四捨五入
2015年04月18日
小数第二位までで四捨五入する場合は、以下のような感じ。
┌──────────────────────────────────────┐
│ROUND([値], 2)                                                              │
└──────────────────────────────────────┘
でもこれだと「1.230000」後ろに「0」が並ぶので、
これが嫌ならキャストしてあげる。
┌──────────────────────────────────────┐
│CONVERT(decimal(8, 2), ROUND([値], 2))                                      │
└──────────────────────────────────────┘
「1.23」になる。
分類:MSSQL
MSSQL/共通テーブル式(CTE)
2015年04月12日
WITH句問い合わせのことを、共通テーブル式(CTE)という。
分類:MSSQL
MSSQL/入れ子とADOとストアドプロシージャ
2015年04月10日
ADODBの入れ子と、ストアドプロシージャの入れ子を合成させた場合
途中のロールバックがどういう影響を与えるかまとめてみた。
┌──────────────────────────────────────┐
│┌.BeginTrans                           │大抵の場合、DB側で                │
││┌.BeginTrans                         │入れ子をサポートしていないので、  │
│││  ├─────────→ 有効?     │二つ目の「.BeginTrans」で         │
││└.CommitTrans                        │「このセッションでは、これ以上の  │
││┌.BeginTrans                         │  トランザクションを              │
│││  ├─────────→ 無効       │  開始できません」エラーとなる    │
││└.RollbackTrans                      │                                  │
│└.CommitTrans          ─→ 問題なし? │なので、実態不明                  │
└──────────────────────────────────────┘
                                       +                                       
┌──────────────────────────────────────┐
│┌BEGIN TRANSACTION                                                         │
││┌BEGIN TRANSACTION                                                       │
│││  ├─────────→ 全滅(無効)                                     │
││└COMMIT TRANSACTION                                                      │
││┌BEGIN TRANSACTION                                                       │
│││  ├─────────→ 全滅(無効)                                     │
││└ROLLBACK TRANSACTION                                                    │
│└COMMIT TRANSACTION    ─→ 例外                                           │
└──────────────────────────────────────┘
                                       ||                                       
┌──────────────────────────────────────┐
│┌.BeginTrans                                                               │
││┌BEGIN TRANSACTION                                                       │
│││  ├─────────→ 全滅(無効)                                     │
││└COMMIT TRANSACTION                                                      │
││┌BEGIN TRANSACTION                                                       │
│││  ├─────────→ 全滅(無効)                                     │
││└ROLLBACK TRANSACTION                                                    │
│└.CommitTrans          ─→ 問題なし                                       │
└──────────────────────────────────────┘
ADODBの入れ子が実態がよくわからないのだが
実験した範囲では、全ロールバック(全滅)するってのが基本のようだ。
トランザクションが終わっているのに「COMMIT TRANSACTION」は蛇足だろうと
例外になるものの、「.CommitTrans」は大目にみてくれる(?)ようだ。
もちろん「.BeginTrans」と辻褄が合わないとエラーとなるけど。
分類:MSSQL
MSSQL/トランザクション入れ子(3)実際の動き
2015年04月09日
トランザクションに入れ子があり、その途中で例外が発生した場合
どのように動くかを確認してみると…
┌──────────────────────────────────────┐
│BEGIN TRY                           ■                                      │
│    BEGIN TRANSACTION;              ■                                      │
│    -- ----------------------       ■                                      │
│    BEGIN TRY                       ■                                      │
│        BEGIN TRANSACTION;          ■                                      │
│        SELECT 1/0;  --例外発生     ★─┐◎例外発生                        │
│        COMMIT TRANSACTION;         □  │                                  │
│    END TRY                         □  │                                  │
│    BEGIN CATCH                     ■←┘                                  │
│        ROLLBACK TRANSACTION;       ■    ①外側のトランザクションも終了    │
│    END CATCH;                      ■                                      │
│    -- ----------------------       ■                                      │
│    COMMIT TRANSACTION;             ★─┐②終了済みのため例外              │
│END TRY                             □  │                                  │
│BEGIN CATCH                         ■←┘                                  │
│    ROLLBACK TRANSACTION;           ★─→③終了済みのため例外              │
│END CATCH;                          ■     (TRY-CATCH外のためメッセージ出力)│
└──────────────────────────────────────┘
入れ子の階層をさらに一つ足した場合は…
┌──────────────────────────────────────┐
│BEGIN TRY                           ■                                      │
│    BEGIN TRANSACTION;              ■                                      │
│    -- -------------------------    ■                                      │
│    BEGIN TRY                       ■                                      │
│        BEGIN TRANSACTION;          ■                                      │
│        -- ----------------------   ■                                      │
│        BEGIN TRY                   ■                                      │
│            BEGIN TRANSACTION;      ■                                      │
│            SELECT 1/0;  --例外発生 ★─┐◎例外発生                        │
│            COMMIT TRANSACTION;     □  │                                  │
│        END TRY                     □  │                                  │
│        BEGIN CATCH                 ■←┘                                  │
│            ROLLBACK TRANSACTION;   ■    ①外側のトランザクションも終了    │
│        END CATCH;                  ■                                      │
│        -- ----------------------   ■                                      │
│        COMMIT TRANSACTION;         ★─┐②終了済みのため例外              │
│    END TRY                         □  │                                  │
│    BEGIN CATCH                     ■←┘                                  │
│        ROLLBACK TRANSACTION;       ★─┐③終了済みのため例外              │
│    END CATCH;                      □  │                                  │
│    -- --------------------------   □  │                                  │
│    COMMIT TRANSACTION;             □  │                                  │
│END TRY                             □  │                                  │
│BEGIN CATCH                         ■←┘                                  │
│    ROLLBACK TRANSACTION;           ★─→④終了済みのため例外              │
│END CATCH;                          ■     (TRY-CATCH外のためメッセージ出力)│
└──────────────────────────────────────┘

ちなみ、トランザクションが終了済みのため例外が発生した場合の
エラーメッセージは以下の通りである
┌──────────────────────────────────────┐
│メッセージ 3903、レベル 16、状態 1                                          │
│ROLLBACK TRANSACTION 要求に対応する BEGIN TRANSACTION がありません。        │
├──────────────────────────────────────┤
│メッセージ 3902、レベル 16、状態 1                                          │
│COMMIT TRANSACTION 要求に対応する BEGIN TRANSACTION がありません。          │
└──────────────────────────────────────┘
TRY-CATCHで捕捉された場合は、エラーメッセージは抑止されるので
出力されるのは最後の一回だけとなる

見てのとおり、字面上ては「BEGIN」と「COMMIT(ROLLBACK)」が対になっていても
実際は、思いっきり破綻してしまうのだ。

なお、「PRINT @@TRANCOUNT;」で「BEGIN」の階層数が確認できるのだが、
これを見ていると、「BEGIN」の都度、順調にカウントアップしていくのだが、
最初の「ROLLBACK」で一気に「0」になってしまうのがわかる。
分類:MSSQL
MSSQL/トランザクション入れ子(2)コミットとロールバック
2015年04月08日
入れ子における、内側と外側(大外)の相互関係は以下の通り
┌─────────┬───────────────┐
│                  │          外側(大外)          │
│                  ├───────┬───────┤
│                  │●コミット    │○ロールバック│
├─┬───────┼───────┼───────┤
│内│●コミット    │●コミット    │○ロールバック│
│  ├───────┼───────┼───────┤
│側│○ロールバック│☆ロールバック│×(例外)      │
└─┴───────┴───────┴───────┘
  ・内側のコミットは意味をなさない(事実上無視)
    →外側(大外)がコミットならコミット、
      外側(大外)がロールバックならロールバック
  ・内側のロールバックは外側(大外)のロールバックを行う
    よって、さらに外側でロールバックを行うと
    ロールバック済みなので例外(エラー)となる

┌──────────────────────────────────────┐
│内側のロールバックは、外側に影響与え過ぎなので控えたとして                  │
│内側のコミットは役立たずだし(はっきりいって意味がない)                      │
│なんか、入れ子にしても意味がなさそう                                        │
└──────────────────────────────────────┘
分類:MSSQL
MSSQL/トランザクション入れ子(1)対
2015年04月07日
┌──────────────────────────────────────┐
│Q.「BEGIN TRANSACTION」と                                                 │
│    「COMMIT TRANSACTION」(もしくは「ROLLBACK TRANSACTION」)は              │
│    必ず一対でなければならない?                                            │
└──────────────────────────────────────┘
  A.必ず一対でなければならない
     但し、ストアドプロシージャの記述は一対でなくても定義は可能
     (実行時にエラーが発生)
     また逆に、対に記述したつもりでも、入れ子のロールバックで
     想定していた対が崩壊すると、実行時にエラーとなってしまう
分類:MSSQL
前へ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 次へ