1

単調な振戦 (振戦せん妄と手根管症候群の中間) に悩まされないように、SQL ステートメントとそのパラメーター値の大きなファイルの解析を自動化する方法を見つける必要があります。

次の形式の膨大な数の SQL ステートメントを含むファイルがあります。

select Animal#, RacketThreshold, PeakOil as Oil
from OilAnimalPlatypus2
where OilAnimalPlatypusID = :ID
  and Animal# = :Animal
  and TelecasterAccessType = 'D'
UNION
select Animal, RacketThreshold, PeakOil as Oil
from OilRequestPlatypus
where PlatypusID = :ID
  and Animal = :Animal
order by RacketThreshold

-->ID(VARCHAR[0])=<NULL> 
:Animal(INTEGER)=2

...つまり、複数行の sql ステートメントの後に空白行が続き、その後に 2 つのダッシュとパラメーター名、データ型、および引数を含む矢印が続き、その後に同じことが無限に繰り返されます (SQL ステートメントにパラメーターなし)。

この大量のゴミから、一意のクエリごとに個別の文字列を作成したいと思います (多くの場合、params に異なる引数値が割り当てられていますが、それらの多くは同じです)。可能であれば、特定のクエリに渡されたすべての引数値を追跡したいと思います (たとえば、最初に呼び出されて特定のパラメーターに「1」が渡された場合、次回は「42」、次回は「3.14」など)、その引数名に1、42、3.14のコレクションが必要です。

400 を超えるクエリがありますが、これをすべて「手作業」で行うという考えは嫌いです。特に、一致するクエリを比較する場合はそうです。

更新しました

さて、Jon を使用するためにこのコードを追加した後:

private void buttonOpenAndParseSQLMonFile_Click(object sender, EventArgs e)
{
    var queriesAndArgs = (Dictionary<string, List<string>>)ParseFile("SQLMonTraceLog.txt");
    foreach(var pair in queriesAndArgs)
    {
        richTextBoxParsedResults.AppendText(pair.Key);
        richTextBoxParsedResults.AppendText(Environment.NewLine);
        foreach (String s in pair.Value)
        {
            richTextBoxParsedResults.AppendText(s);
            richTextBoxParsedResults.AppendText(Environment.NewLine);
        }
        richTextBoxParsedResults.AppendText(Environment.NewLine);
    }
}

...リッチテキスト ボックスで次のような結果が得られます。

select ABCID from ABCWorker where lower(loginid) = lower(user) 


select r.roleid from abcrole r, abcworker w where lower(w.loginid)=lower(user)   and r.abcid=w.abcid   and r.status='A'


select Tier#, BenGrimm, PeakRate as Ratefrom RageAnimalGreenBayPackers2 where RageAnimalGreenBayPackersID = :ID and Tier# = 

:Tier and FlyingVAccessType = 'D' UNION select Tier, BenGrimm, PeakRate as Rate from CaliforniaCondorGreenBayPackers where 

GreenBayPackersID = :ID and Tier = :Tier order by BenGrimm 
-->   :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=1 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=1 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=1 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=4 


select Tier#, BenGrimm, PeakRate as Rate from RageAnimalGreenBayPackers2 where RageAnimalGreenBayPackersID = :ID and Tier# = 

:Tier and FlyingVAccessType = 'D' UNION select Tier, BenGrimm, PeakRate as Rate from CaliforniaCondorGreenBayPackers where 

GreenBayPackersID = :ID and Tier = :Tier order by BenGrimm 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=2 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=5 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=1 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=2 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=3 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=4 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=2 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=3 
-->  :ID(VARCHAR[0])=<NULL> :Tier(INTEGER)=4 
(etc.)

...だから、これは非常に有益でしたが、私が必要としているものはそれほど多くないことがわかりました。さらに、ファイルの lamo 手動微調整に依存しています。SO、一歩下がって、実際に与えられたファイルを解析する必要があると思います。各「興味深い」イベントを区切る数字を増やします。

. . .
6       11:30:46  SQL Execute: select ABCID
from ABCWorker
where lower(loginid) = lower(user)
7       11:30:46  SQL Prepare: select r.roleid from abcrole r, abcworker w where lower(w.loginid)=lower(user)   and     
r.abcid=w.abcid   and r.status='A'
8       11:30:46  SQL Execute: select r.roleid from abcrole r, abcworker w where lower(w.loginid)=lower(user)   and     
r.abcid=w.abcid   and r.status='A'
9       11:30:46  SQL Execute: select Tier#, BenGrimm, PeakRate as Rate
from RageAnimalGreenBayPackers2
where RageAnimalGreenBayPackersID = :ID
  and Tier# = :Tier
  and FlyingVAccessType = 'D'
UNION
select Tier, BenGrimm, PeakRate as Rate
from CaliforniaCondorGreenBayPackers
where GreenBayPackersID = :ID
  and Tier = :Tier
order by BenGrimm
10      11:30:46  :ID(VARCHAR[0])=<NULL> 
:Tier(INTEGER)=1
11      11:30:46  SQL Execute: select Tier#, BenGrimm, PeakRate as Rate
from RageAnimalGreenBayPackers2
where RageAnimalGreenBayPackersID = :ID
  and Tier# = :Tier
  and FlyingVAccessType = 'D'
UNION
select Tier, BenGrimm, PeakRate as Rate
from CaliforniaCondorGreenBayPackers
where GreenBayPackersID = :ID
  and Tier = :Tier
order by BenGrimm
12      11:30:46  :ID(VARCHAR[0])=<NULL> 
:Tier(INTEGER)=2
. . .
4

3 に答える 3

1

別の空白行でクエリを互いに分離していると仮定すると、次を使用してファイルを解析できます。コードは最後までファイルを読み取ります。parseQuery を呼び出すたびに、空白行が見つかるまで行を読み取り、それらをクエリとして追加します。次に、次の行をチェックし、それが引数ブロックの先頭でない場合は、引数なしでクエリを保存し、別のクエリの先頭にあると想定して最初からやり直します。その行引数ブロックの先頭である場合、コードは別の空白行に達するまで読み取り、クエリとその引数を保存してから戻ります。while(parseQuery) は、ファイル全体が解析されることを保証します。

最後に、コードはクエリ文字列をキーとして、文字列のリストをさまざまな引数として含む辞書を出力します。簡単にするために、エラー チェックは省略されています。実際のシナリオでは、ファイルが存在しないなどの処理を追加する必要があります。

static IDictionary<string, List<string>> ParseFile(string path)
{
    Dictionary<string, List<string>> queries = new Dictionary<string, List<string>>();
    using (var reader = File.OpenText(path))
    {
        while (parseQuery(reader, queries)) { }
    }
    return queries;
}

private static bool parseQuery(StreamReader reader, Dictionary<string, List<string>> queries)
{
    StringBuilder sbQuery = new StringBuilder();
    StringBuilder sbArgs = new StringBuilder();
    // Read in query
    bool moreLines = ParseBlock(reader, sbQuery);
    if (moreLines)
    {
        while (moreLines)
        {
            string line = reader.ReadLine();
            // Check for the beginning of an args block.
            if (line != null && line.StartsWith("-->"))
            {
                // Read in args
                sbArgs.Append(line);
                moreLines = ParseBlock(reader, sbArgs);
                break;
            }
            // If this is not an args block, it is a new query
            // Save the last query and start over
            else
            {
                AddQuery(queries, sbQuery.ToString(), sbArgs.ToString());
                sbQuery = new StringBuilder();
                sbQuery.Append(line); // Make sure we capture the last line
                moreLines = ParseBlock(reader, sbQuery);
            }
        }
    }
    AddQuery(queries, sbQuery.ToString(), sbArgs.ToString());
    return moreLines;
}

private static bool ParseBlock(StreamReader reader, StringBuilder builder)
{
    string line;
    while ((line = reader.ReadLine()) != null)
    {
        line = line.Trim();
        if (string.IsNullOrWhiteSpace(line)) break;

        builder.Append(line + " ");
    }
    return line != null;
}

private static void AddQuery(Dictionary<string, List<string>> queries, string query, string args)
{
    if (query.Length > 0)
    {
        List<string> lstParams;
        if (!queries.TryGetValue(query, out lstParams))
        {
            lstParams = new List<string>();
        }
        lstParams.Add(args);
        queries[query] = lstParams;
    }
}
于 2012-06-20T21:55:39.687 に答える
1

本当に必要なのは字句解析器です。ANTLR をチェックしてください - http://www.antlr.org/

「文法」を定義する必要があります。これは、言語の各要素 (この場合は SQL のファイル) の特性です。最後に、ANTLR はファイルを処理し、文法定義に基づいて見つかった場合の結果を吐き出しました。

これは単なるトークン化と解析のプロセスです。

于 2012-06-20T21:17:07.897 に答える
1

これが私のコメントの具体例です。StreamReaderを使用して、各ブロックを List に収集することで、これを非常に簡単に行うことができます。例えば:

string line = String.Empty;

List<String> statementBlocks = new List<String>();

System.IO.StreamReader file = new System.IO.StreamReader("C:\\temp\\annoying_text_file.sql");

StringBuilder blockCollector = new StringBuilder();

//read the file a line at a time
while((line = file.ReadLine()) != null)
{
  //If the line has content, then we append it to our string builder 
  if(!String.IsNullOrWhitespace(line)) //String.IsNullOrWhitespace is new in .Net 4 and will also match the new line
  {
      blockCollector.AppendLine(line);
  }
  else
  {
       //we've hit a blank line - dump it to the list and reinitialize the stringbuilder
       statementBlocks.Add(blockCollector.ToString();
       statementBlocks = new StringBuilder();
  }

}

//Tidy up
file.Close();

foreach(string statementBlock in statementBlocks)
{
  if(!String.IsNullOrEmpty(statementBlock))
  {
      if(statememtBlock.StartsWith("-->"))
      {
        //Code to split out the arguments; if they are delimited with : then you can just string.split this line
        //string[] paramsAndValues = line.Replace("-->", String.Empty).Split(Char.Parse(":"))
        // then for each string in here it's paramName(DataType)=Value, which is also splittable.
      }
      else
      {
      //Do whatever you want with this valid block (including writing it to another file!)
      //To keep only the unique ones, store each block in a list, then look to see if a block already exists in the list each time; if it does, just skip this block. Given you also know that the next block will be a parameter block, you can also collect the parameters here too
      }
  }    
}

これがコンパイルされていることを今すぐ確認することはできませんが、あなたが望むことを行うための可能な方法の一般的な感覚を与えるはずです.

空行はステートメントブロック間の行のみであると仮定します。

于 2012-06-20T21:17:50.883 に答える