はじめに
前段で個人的な経緯をダラダラ書くので、『お前の事情になんか興味ねえ』方は、適当に斜め読みするなり、このあたりまでスキップしてください。
要約すると、ScriptDom というライブラリを使って、SQLをUNIONで繋げたり、逆にUNIONで繋がったSQLの一方を削除したり、そんなことをする部品を作ってみましたよ、というお話です。
ScriptDom
https://www.nuget.org/packages/Microsoft.SqlServer.TransactSql.ScriptDom/15.0.4200.1
また、肝心のScriptDomを使っている 部品内部のコードの詳しい解説などはこの記事にありません。
おわりにに GitHub へのリンクを置きましたので、ScriptDomを利用しているコードがどんなものか見てみたい方は、過度な期待をせずにご覧ください。
事の発端
今を去ること1年ほど前、私は悩んでいました。
- SQLを安全に組み立てるためのライブラリが欲しい。
- SQLを動的に文字列編集したりするのはもううんざり。
- カンマやスペースを入れ忘れてSQLのコンパイルが通らないなんていうおバカなバグにはもう悩まされたくない。
.NETなら、EntityFramework があるじゃないかって?
確かに EntityFramework は強力なツールです。
しかし、私のやりたいことはそれでは実現できませんでした。EntityFrameworkを使う場合、最初からEntityFrameworkを頼ってゼロからクエリを組み立てる必要があります。
簡単なクエリならそれはとても威力を発揮するのですが、少し複雑なクエリになってくると冗長な部分が目立ち始めます。テーブル結合なんかが多いとあっという間に大変なことになってしまいます。
恐らく、EntityFrameworkには、オブジェクトに対するクエリと、関係データベースに対するクエリのインタフェースを共通化したいというような思想があるのだろうと想像します。
これはこれでとても素晴らしいと感じます。
けど、そうじゃないんです。
ここには既に、『コンパイルが通る事の保証されたSQLスクリプト』があって、これを『動的に、安全に、そしてできれば簡単に』書き換える術が欲しいんです。
そうして一年前の私は、自分の要求を満たしてくれるライブラリを求めてネットの海をさ迷いはじめました……。
なければ作ればいいじゃない!?
しかし、当時の私は調べ方が甘く、自分が満足するライブラリをうまく見つけることができませんでした。
おかしい、こんなの既にあってもおかしくないじゃない?
少なくとも、SQLServerで実行可能なSQLスクリプトをパースする技術は、Microsoftさんにはあるはずじゃない?
なら、それが 提供されていてもいいでしょ?
しかし、見つけられなかったのでした。
厳密に言えば、それ(その名はScriptDom)に出会ってはいたのですが、自分の求めていたものだとは気付かず、見逃してしまったのです。
その時の私は、それが単に『SQLの字句解析をしてトークンに分解するだけ』のツールと勘違いしたのです。
そうして。
私は、無駄な工数を費やして、不完全で、まず間違いなく将来的には保証しきれなくなるであろう、自作のSQLパーサ兼ビルダを作る羽目になったのでした。
再会
頑張って自作のSQLビルダを作った日も遠くなった頃、当時携わっていたプロジェクトが落ち着きを見せ始め、同時に私の中で消化不良だった気持ちが湧き上がってきました。
(やっぱり本当は、あるんでしょう?)
こうして先日めでたく、私は再び『ScriptDom』に出会ったのでした。
いや、これ、超便利ですやん。
汎用性の高いライブラリのため、全容を理解するには時間がかかりそうだし、取っ付きにくいところもあるとは思います。
1年前の自分には、それが自分の求めていたライブラリとは気付けませんでした。
自作のSQLパーサ/ビルダを作るにしたって、最初からこの『ScriptDom』を利用していれば、もっと少ない時間で安定した部品を作れたと思います。
基本的な操作は、
usingSystem.IO;usingMicrosoft.SqlServer.TransactSql.ScriptDom;////////////////////////////////// (中略) ///////////////////////////////////////////////// SQLスクリプトをパースするstringquery="select hoge from fuga";varparser=newTSql150Parser(false);varresult=parser.Parse(newStringReader(query),outIList<ParseError>errors);// resultに、式ツリーが帰ってくるので、ここで思うがままに編集する。// SQLスクリプトを生成するvargenerator=newSql150ScriptGenerator();generator.GenerateScript(result,outstringformated);
たった、これだけです。
まあ上のコードでは、肝心のSQLをこねこねする部分をコメント1行で誤魔化しているのですが。
それでも一番厄介な、パースしたり、スクリプトに復元するところをライブラリが上手いことやってくれるのだからありがたいことです。
とは言え、このままでは使いにくい。
ありがたいとは言うものの、使いこなすにはそれなりのハードルがあります。
例えば、次のような3つのクエリをUNIONするSQLがあったとします。
SELECTFoo1.Bar1FROMFoo1--クエリ(1)UNIONSELECTFoo2.Bar2FROMFoo2--クエリ(2)UNIONSELECTFoo3.Bar3FROMFoo3--クエリ(3)
これをパースすると、以下のようなイメージの式ツリーになります。
- UNION
- UNION
- クエリ(1)
- クエリ(2)
- クエリ(3)
- UNION
コンピュータ君からすると、ツリー上にこうやって組み立てられている方が扱いやすいのだろうけど、人間の感覚では、このクエリ(1)から(3)までは、すべて同じ階層にあってほしいところです。
人間様としては、こんなイメージで扱えるほうが分かりやすい。
- クエリ(1)
- UNION クエリ(2)
- UNION クエリ(3)
ScriptDom を利用してSQLを編集してやるぞと思っても、もう少し扱いやすいようにラッパークラスなりを用意しないと厳しそうです。
実際に部品を少し作ってみる
それならば。
試しに複数のクエリを、UNION、EXCEPT、INTERSECTで1つのクエリに組み立てる部品を作ってみよう。
例えばこんな感じのSQLに……
SELECTFoo1.Bar1FROMFoo1UNIONSELECTFoo2.Bar2FROMFoo2UNIONSELECTFoo3.Bar3FROMFoo3
最後にもう1個クエリを追加して、以下のようにする。
SELECTFoo1.Bar1FROMFoo1UNIONSELECTFoo2.Bar2FROMFoo2UNIONSELECTFoo3.Bar3FROMFoo3--↓↓↓これを追加する↓↓↓UNIONSELECTFoo4.Bar4FROMFoo4--↑↑↑これを追加する↑↑↑
まあ、これくらいなら文字列で編集しても大したことないでしょうが……。
ScriptDomを利用して、この編集を以下のようなコードで行えるクラスを作ってみました。
usingKzLib.SqlServer.TransactSql.ScriptDom;////////////// (中略) ////////////////stringbaseSql=@"SELECT
Foo1.Bar1
FROM Foo1
UNION
SELECT
Foo2.Bar2
FROM Foo2
UNION
SELECT
Foo3.Bar3
FROM Foo3";stringadditionalSql=@"SELECT
Foo4.Bar4
FROM Foo4";TransactSQLtSQL=TransactSQL.Parse(baseSql);tSQL.Items.Add(newQueryItem(additionalSql,BinaryQueryExpressionType.Union));System.Diagnostics.Debug.Write(tSQL.ToString());
たったこれだけのことをする部品の割には大変でした……。
おわりに
全然大した事のできない部品だし、すごく汚いコードなのだけれども、GitHub に公開してみました。
https://github.com/8810hayate/TransactSQL
しばらくいじってみて、もっと機能が充実したら、次のプロジェクトで使ってみようかなぁ。