Простой способ конвертировать exec sp executesql в обычный запрос?

при работе с отладочными запросами с помощью Profiler и SSMS, для меня довольно часто копировать запрос из Profiler и тестировать их в SSMS. Поскольку я использую параметризованный sql, все мои запросы отправляются как запросы exec sp_executesql.

exec sp_executesql 
N'/*some query here*/', 
N'@someParameter tinyint',
@ someParameter =2

Я возьму это и преобразую его в обычный запрос для удобства редактирования (intellisense, проверка ошибок, номера строк и т. д.):

DECLARE @someParameter tinyint
SET @someParameter = 2

/*some query here*/

конечно, больше и сложнее запрос, тем сложнее это сделать. И когда вы идете туда и обратно несколько раз, это может быть боль в заднице и впитывать много времени.

есть ли простой (например, макрокоманда) способ конвертировать muh executesql в что-то более удобное?

8 ответов


Я не знаю о существующей надстройке, которая может это сделать. Но вы могли бы создать один:)

несколько регулярных выражений и некоторая конкатенация строк и после этого продают его Винко и другим душам, ищущим эту функциональность.

Если вы хотите погрузиться в это, вот некоторая информация о создании добавления SSMS: http://sqlblogcasts.com/blogs/jonsayce/archive/2008/01/15/building-a-sql-server-management-studio-addin.aspx


Я потратил немного времени на создание простого сценария, который сделал это для меня. Это WIP, но я вставил (очень уродливую) веб-страницу перед ней, и теперь она размещена здесь, Если вы хотите попробовать:

http://execsqlformat.herokuapp.com/

пример ввода:

exec sp_executesql 
          N'SELECT * FROM AdventureWorks.HumanResources.Employee 
          WHERE ManagerID = @level',
          N'@level tinyint',
          @level = 109;

и вывод:

BEGIN
DECLARE @level tinyint;

SET @level = 109;

SELECT * FROM AdventureWorks.HumanResources.Employee  
          WHERE ManagerID = @level
END

форматирование фактического оператора SQL после того, как я вытащил его из ввода, выполняется с помощью API на http://sqlformat.appspot.com


Я искал что-то подобное, поэтому я использую это в LinqPad, просто скопируйте оператор sp_executesql в буфер обмена и запустите код в LinqPad. Он выводит инструкцию SQL.

void Main()
{
    ConvertSql(System.Windows.Forms.Clipboard.GetText()).Dump();
}

private static string ConvertSql(string origSql)
{
  string tmp = origSql.Replace("''", "~~");       
  string baseSql;
  string paramTypes;
  string paramData = "";
  int i0 = tmp.IndexOf("'") + 1;
  int i1 = tmp.IndexOf("'", i0);
  if (i1 > 0)
  {
      baseSql = tmp.Substring(i0, i1 - i0); 
      i0 = tmp.IndexOf("'", i1 + 1);
      i1 = tmp.IndexOf("'", i0 + 1);
      if (i0 > 0 && i1 > 0)
      {
          paramTypes = tmp.Substring(i0 + 1, i1 - i0 - 1);
          paramData = tmp.Substring(i1 + 1);
      }
  }
  else
  {
      throw new Exception("Cannot identify SQL statement in first parameter");
  }

  baseSql = baseSql.Replace("~~", "'");  
  if (!String.IsNullOrEmpty(paramData))  
  {
      string[] paramList = paramData.Split(",".ToCharArray());
      foreach (string paramValue in paramList)
      {
          int iEq = paramValue.IndexOf("=");
          if (iEq < 0)
              continue;
          string pName = paramValue.Substring(0, iEq).Trim();
          string pVal = paramValue.Substring(iEq + 1).Trim();
          baseSql = baseSql.ReplaceWholeWord(pName, pVal);
      }
  }

  return baseSql;
}

public static class StringExtensionsMethods
{
   /// <summary>
   /// Replaces the whole word.
   /// </summary>
   /// <param name="s">The s.</param>
   /// <param name="word">The word.</param>
   /// <param name="replacement">The replacement.</param>
   /// <returns>String.</returns>
   public static String ReplaceWholeWord(this String s, String word, String replacement)
   {
       var firstLetter = word[0];
       var sb = new StringBuilder();
       var previousWasLetterOrDigit = false;
       var i = 0;
       while (i < s.Length - word.Length + 1)
       {
           var wordFound = false;
           var c = s[i];
           if (c == firstLetter)
               if (!previousWasLetterOrDigit)
                   if (s.Substring(i, word.Length).Equals(word))
                   {
                       wordFound = true;
                       var wholeWordFound = true;
                       if (s.Length > i + word.Length)
                       {
                           if (Char.IsLetterOrDigit(s[i + word.Length]))
                               wholeWordFound = false;
                       }

                       sb.Append(wholeWordFound ? replacement : word);

                       i += word.Length;
                   }

           if (wordFound) continue;

           previousWasLetterOrDigit = Char.IsLetterOrDigit(c);
           sb.Append(c);
           i++;
       }

       if (s.Length - i > 0)
           sb.Append(s.Substring(i));

       return sb.ToString();
   }
}

другое решение, которое заменяет значения параметров непосредственно в запросе (не совсем то, что вы просили, но это может оказаться полезным для других):

https://code.msdn.microsoft.com/windowsdesktop/spExecuteSql-parser-1a9cd7bc

мне идет с:

exec sp_executesql N'UPDATE Task SET Status = @p0, Updated = @p1 WHERE Id = @p2 AND Status = @p3 AND Updated = @p4',N'@p0 int,@p1 datetime,@p2 int,@p3 int,@p4 datetime',@p0=1,@p1='2015-02-07 21:36:30.313',@p2=173990,@p3=2,@p4='2015-02-07 21:35:32.830'

в:

UPDATE Task SET Status = 1, Updated = '2015-02-07 21:36:30.313' WHERE Id = 173990 AND Status = 2 AND Updated = '2015-02-07 21:35:32.830'

что облегчает понимание.

консольное приложение на этой странице можно использовать, передав параметр file или скопировав sp_executesql в буфере обмена, запустив приложение, а затем вставив полученный SQL из буфера обмена.

обновление:

форматер SQL также может быть добавлен к этому решению для удобства чтения:

http://www.nuget.org/packages/PoorMansTSQLFormatter/

newSql = ConvertSql(Clipboard.GetText());
var formattedSql = SqlFormattingManager.DefaultFormat(newSql);
Clipboard.SetText(formattedSql);

Я потратил немного времени и создал небольшую модификацию Matt Roberts / Wangzq solutions без раздела объявления, вы можете попробовать его на .NET Fiddle или скачать LINQPad 5 файл.

вход:

exec sp_executesql N'UPDATE MyTable SET [Field1] = @0, [Field2] = @1',N'@0 nvarchar(max) ,@1 int',@0=N'String',@1=0

выход:

UPDATE MyTable SET [Field1] = N'String', [Field2] = 0

код:

using System;
using System.Linq;
using System.Text.RegularExpressions;

public class Program
{
    public static void Main()
    {
        var sql = @"exec sp_executesql N'UPDATE MyTable SET [Field1] = @0, [Field2] = @1',N'@0 nvarchar(max) ,@1 int',@0=N'String',@1=0";
        Console.WriteLine(ConvertSql(sql));
    }

    public static string ConvertSql(string origSql)
    {
        var re = new Regex(@"exec*\s*sp_executesql\s+N'([\s\S]*)',\s*N'(@[\s\S]*?)',\s*([\s\S]*)", RegexOptions.IgnoreCase); // 1: the sql, 2: the declare, 3: the setting
        var match = re.Match(origSql);
        if (match.Success)
        {
            var sql = match.Groups[1].Value.Replace("''", "'");
            //var declare = match.Groups[2].Value;
            var setting = match.Groups[3].Value + ',';

            // to deal with comma or single quote in variable values, we can use the variable name to split
            var re2 = new Regex(@"@[^',]*?\s*=");
            var variables = re2.Matches(setting).Cast<Match>().Select(m => m.Value).ToArray();
            var values = re2.Split(setting).Where(s=>!string.IsNullOrWhiteSpace(s)).Select(m => m.Trim(',').Trim().Trim(';')).ToArray();

            for (int i = variables.Length-1; i>=0; i--)
            {
                sql = Regex.Replace(sql, "(" + variables[i].Replace("=", "")+")", values[i], RegexOptions.Singleline);
            }
            return sql;     
        }

        return @"Unknown sql query format.";
    }
}

SQL Prompt получил эту функцию недавно (2017-02-06). Выберите текст и найдите "Inline EXEC" в контекстном меню. Должен любить подсказку:)


Я тоже столкнулся с этой проблемой и написал простое приложение для ее решения -ClipboardSqlFormatter. Это приложение в трее, которое прослушивает входные события буфера обмена и пытается обнаружить и преобразовать динамический sql в статический sql.

все, что вам нужно, это скопировать динамический sql (например, из SQL profiler) и вставить в текстовый редактор-вставленный sql будет статическим sql:)

например, если скопирован sql:

exec sp_executesql N' SELECT "obj"."CreateDateTime", "obj"."LastEditDateTime" FROM LDERC "doc" INNER JOIN LDObject "obj" ON ("doc"."ID" = "obj"."ID") LEFT OUTER JOIN LDJournal "ContainerID.jrn" ON ("doc"."JournalID" = "ContainerID.jrn"."ID") WHERE ( "doc"."ID" = @V0 AND ( "doc"."StateID" <> 5 AND "ContainerID.jrn"."Name" <> ''Hidden journal'' ) ) ',N'@V0 bigint',@V0=6815463'

затем вставленный sql будет:

SELECT "obj"."CreateDateTime" ,"obj"."LastEditDateTime" FROM LDERC "doc" INNER JOIN LDObject "obj" ON ("doc"."ID" = "obj"."ID") LEFT OUTER JOIN LDJournal "ContainerID.jrn" ON ("doc"."JournalID" = "ContainerID.jrn"."ID") WHERE ( "doc"."ID" = 6815463 AND ( "doc"."StateID" <> 5 AND "ContainerID.jrn"."Name" <> 'Hidden journal' ) )


заключение: я отмечаю, что это все еще привлекает немного внимания, поэтому я добавлю подробности здесь для того, каким было мое окончательное решение.

оказывается, нет ничего лучше, чем делать это для себя. Я создал простое консольное приложение, которое проанализировало мою хранимую процедуру и выплюнуло то, что я хотел. Добавив его в список внешних инструментов и передав текущее имя файла в качестве аргумента, я мог бы использовать следующее, Чтобы удалить и переставить то, что мне нужно.

в использовании я бы добавил новый sql файл, вставить в sql, сохранить его, а затем запустить внешний инструмент. После его завершения IDE просит меня перезагрузить файл. Пуф хранимой процедуры.

обработка исключений и другие вещи удаляются, и я отмечаю, что это может не работать с оператор executesql, поэтому вам придется изменить, если он не соответствует вашим потребностям.

var text = File.ReadAllText(args[0]);
if(string.IsNullOrEmpty(text))
{
    Console.WriteLine(
        "File is empty; try saving it before using the hillbilly stored procedure decoder");
}
var regex = new Regex(
    @"exec sp_executesql N'(?<query>.*)',N'(?<decls>.*)',(?<sets>.*)",     
    RegexOptions.Singleline);
var match = regex.Match(text);

if(!match.Success || match.Groups.Count != 4)
{
    Console.WriteLine("Didn't capture that one.");
    Console.Read();
    return;
}

var sb = new StringBuilder();
// declares go on top
sb.Append("DECLARE ").AppendLine(match.Groups["decls"].Value);
// split out our sets, add them one line at a time
foreach(var set in match.Groups["sets"]
                   .Value.Split(new char[] { ',' }, 
                   StringSplitOptions.RemoveEmptyEntries))
    sb.Append("SET ").AppendLine(set);
// Add our query, removing double quotes
sb.AppendLine(match.Groups["query"].Value.Replace("''", "'"));
File.WriteAllText(args[0], sb.ToString());