web-dev-qa-db-ja.com

T-SQLコードで「列挙型の」マジックナンバーをエレガントに処理する

Dynamics GPを実行します。GPを扱っている人なら誰でも、アプリケーション内のさまざまな列挙値を表すその「マジックナンバー」列のすべてに精通しています。たとえば、レポートクエリは次のようになります。

...
WHERE sod.SOPTYPE = 2
    AND iv.VCTNMTHD = 3
    AND pod.POLNESTA IN (2, 3)

...これは、自己文書化から可能な限り離れています。

最初は「Enums」というテーブルを作成して値を入力し、スカラーラッパー関数を作成して次のようにクエリできるようにするという明るいアイデアを思いつきました。

...
WHERE sod.SOPTYPE = Enum('Sales Doc Type', 'Order')
    AND iv.VCTNMTHD = Enum('Valuation Method', 'Average Perpetual')
    AND pod.POLNESTA IN (Enum('PO Line State', 'Released'), Enum('PO Line State', 'Change Order'))

もちろん、オプティマイザは基礎となる「定数」値を認識できず、カーディナリティを推測する必要があったため、かなり貧弱な選択を行ったため、それは失敗しました。

したがって、コードでは実際の定数値である必要がありますが、この種のことを簡単にするSSMSに組み込まれたNice機能があるかどうかはわかりません。コードスニペットまたはテンプレートを何らかの方法で(誤用)できますか?なんらかのルックアップ/挿入を実行してコードを次のようにすることができれば、それはすばらしいことです。

...
WHERE sod.SOPTYPE = /*Order*/2
    AND iv.VCTNMTHD = /*Average Perpetual*/3
    AND pod.POLNESTA IN (/*Released*/2, /*Change Order*/3)

また、SSMSアドインのチェックアウトにも反対していません。

4
db2

このルックアップをSSMS/SQLCMD固有の機能にanyの方法で結び付けないようにすることを実際にお勧めします。そのためには、コードがこれら2つのプログラムだけを使用してのみ実行できることが必要です。 SSMSまたはSQLCMDから実行されたとしても、そのようなロジックをストアドプロシージャに組み込むことはできません。また、SSRSなどでSQLCMDコマンドを使用することもできません。

一部のオプション(それらを実行するために使用されるクライアントツールから完全に独立しています):

  1. SQL Server 2019がオプションである場合、新しい Scalar UDF Inlining 機能がEnum('Sales Doc Type', 'Order')アプローチの問題を解決する可能性があります。

  2. インラインTVFがうまくいく可能性があります。 WHERE句のこれらの述語をINNER JOINsになるように再構築する必要がある場合があります。

    FROM  SOPxxxxx sod
    INNER JOIN dbo.Enum('Sales Doc Type', 'Order') sdt
            ON sod.SOPTYPE = sdt.Value
    

    もちろん、INリストは少しトリッキーです。これは、各オプションにLEFT JOINが必要で、次にWHERE述語が必要になる可能性があるためです。 NULL

    FROM  dbo.POP10110 pod
    LEFT JOIN dbo.Enum('PO Line State', 'Released') pls_r
            ON pod.POLNESTA = pls_r.Value
    LEFT JOIN dbo.Enum('PO Line State', 'Change Order') pls_co
            ON pod.POLNESTA = pls_co.Value
    WHERE  ( pls_r.Value IS NOT NULL
         OR  pls_co.Value IS NOT NULL)
    

    [〜#〜]または[〜#〜]、これと同じiTVFを使用すると、より読みやすく管理しやすい非相関サブクエリを実行できる場合があります。

    FROM  SOPxxxxx sod
    WHERE sod.SOPTYPE = (SELECT sdt.Value FROM dbo.Enum('Sales Doc Type', 'Order') sdt)
    

    そしてINリストは少なくとも見栄えが良いです:

    FROM  dbo.POP10110 pod
    WHERE pod.POLNESTA IN (
                           SELECT pls_r.Value
                           FROM   dbo.Enum('PO Line State', 'Released') pls_r
                           UNION ALL
                           SELECT pls_co.Value
                           FROM   dbo.Enum('PO Line State', 'Change Order') pls_co
                          )            
    

    または、おそらく(クエリオプティマイザが何を好むかわからない):

    FROM  dbo.POP10110 pod
    WHERE pod.POLNESTA IN (
               (SELECT pls_r.Value FROM dbo.Enum('PO Line State', 'Released') pls_r),
               (SELECT pls_co.Value FROM dbo.Enum('PO Line State', 'Change Order') pls_co)
                          )            
    

  3. SQLCLRがオプションの場合、notがデータにアクセスするandを実行するSQLCLRスカラーUDFは、[SqlFunction(IsDeterministic = true)]としてマークされ、実行計画に定数で折りたたまれます( 「クエリ処理アーキテクチャガイド」のドキュメントには現在、SQLCLR関数を折りたたむことはできないと記載されていますが、これは間違いである可能性があるため、SQL Server 2012でその機能が追加されたときにドキュメントが更新されなかったのではないかと思います。 「クエリ処理アーキテクチャガイド」の「定数の折りたたみ」情報を修正 )。

    ここでの秘訣は、SQLCLRスカラーUDFでデータを読み取ることなくテーブルからデータを読み取ることです。これにより、データを折りたたみ可能にすることができます。これを行うには、次の手順を実行します。

    1. クラスレベルで静的な読み取り専用辞書を作成します。
    2. 通常の外部SqlConnectionを使用してローカルインスタンスに接続するクラスの静的コンストラクターを作成します。これにより、テーブルから選択され、静的辞書が読み込まれます。このコンストラクタは、メモリの圧迫やDBCC FREESYSTEMCACHEなどによってアセンブリがアンロードされた場合でも、辞書が常に入力されるようにします。残念ながら、内部の「コンテキスト」接続は、SQLコンテキストがないためここでは使用できません。静的コンストラクターが実行されます(ただし、あったとしても素晴らしいでしょう!)。
    3. アセンブリにWITH PERMISSION_SET = EXTERNAL_ACCESSのマークを付けます。辞書は「読み取り専用」であるため、UNSAFEを指定する必要はありません。 「読み取り専用」であっても、アイテムの追加や削除が可能です。これは、すべてのユーザーに対して常に同じである使い捨てのコレクションであるため、問題を引き起こすことはありません。
    4. T-SQLに公開されている静的メソッドを修飾して、次を使用してルックアップを実行します。
      [SqlFunction(IsDeterministic = true)]
      
      デフォルトでDataAccessに設定されているため、SystemDataAccessまたはNoneプロパティを指定する必要はありません。

    このメソッドを使用すると、これまで実行していたEnum()アプローチを実装できます。また、サードパーティのアプリを使用しているので、アセンブリを別のデータベースにインストールすることで、問題を "クリーン"に保つことができます(レポートのプロシージャなどでも同様です)。私はそれがまだ一定のフォールディングを行うことをテストして確認しました。

  4. [〜#〜] or [〜#〜](これは本当にオプション3bです):これが3番目であることを考えるとパーティーアプリ。ルックアップ値/列挙値がほとんど変化しないと仮定しても安全である場合、テーブルからそれらを読み取る必要はありません。 .NETコードで辞書全体をハードコード化することができます。このアプローチでは、現在のインスタンスに外部SqlConnectionを戻す必要がないため、アセンブリをPERMISSION_SET = SAFEとしてマークしておくことができます。

2
Solomon Rutzky

SSMS内では、SQLCMDモードを有効にして、次のように実行できます。

-- Enable SQLCMD Mode  (ALT+Q+M)
:SetVar cSomeNumber 42

SELECT $(cSomeNumber)

スクリプトをファイルに保存した場合、これらを使用した場合と同じように動作します。待機する... SQLCMD.exe

":SetVar"と "$(cSomeNumber)"は処理されますbeforeただし、SQLはエンジンに送信されます。つまり、これは別のクライアント(ADO.Netなど)では使用できず、DBOを作成すると、解釈された値が記録されます。したがって、この:

-- Enable SQL Command Mode  (ALT+Q+M)
:SetVar cSomeNumber 42

CREATE View Test AS SELECT $(cSomeNumber) as SomeNumber

これが得られます:

-- Enable SQL Command Mode  (ALT+Q+M)

CREATE View [dbo].[Test] AS SELECT 42 as SomeNumber

これは、すべてのユーティリティを制限します。

0
Graham