【问题标题】:Easy way to convert exec sp_executesql to a normal query?将 exec sp_executesql 转换为普通查询的简单方法?
【发布时间】:2010-11-11 10:10:58
【问题描述】:

在使用 Profiler 和 SSMS 处理调试查询时,我通常会从 Profiler 复制查询并在 SSMS 中测试它们。因为我使用参数化 sql,所以我的查询都作为 exec sp_executesql 查询发送。

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

我将把它转换成普通查询以便于编辑(智能感知、错误检查、行号等):

DECLARE @someParameter tinyint
SET @someParameter = 2

/*some query here*/

当然,查询越大越复杂,就越难做到这一点。而且,当您来回多次往返时,可能会让人头疼,而且会耗费大量时间。

有没有一种简单(例如宏命令)的方法可以将 muh executesql 转换成更方便的东西?

【问题讨论】:

  • +1 我也很喜欢
  • 您应该考虑将最终解决方案发布为答案...

标签: sql-server macros format ssms sp-executesql


【解决方案1】:

我花了一点时间编写了一个简单的脚本来为我做这件事。这是一个 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 语句后,它的格式是使用http://sqlformat.appspot.com 的 API 完成的

【讨论】:

  • 嗨,马特,我对你如何使用 api 转换 sp_executesql 非常感兴趣,你能分享你的代码吗?谢谢!
  • 把它塞进一个 git repo 供你享用 ;) github.com/mattwoberts/execsqlformat
  • 谢谢!很高兴知道您首先使用正则表达式来解析输入。
  • 不要看嘴里的礼物马,而是“剪贴板”按钮复制带有换行符的文本,而不是 CR/LF,这使得在 Windows 上无法粘贴到任何内容中,甚至记事本++。用户必须复制选择 HTML 并复制。最好只是将该控件设置为 HTML 文本区域,以便您可以 Ctrl+A 全选然后复制。不是我在抱怨什么的。 :)
【解决方案2】:

我花了一点时间,对 Matt Roberts / Wangzq 解决方案进行了小修改,没有 DECLAREs 部分,您可以在 .NET Fiddledownload LINQPad 5 file 上试用。

输入:

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.";
    }
}

【讨论】:

    【解决方案3】:

    我正在寻找类似的东西,所以我在 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();
       }
    }
    

    【讨论】:

      【解决方案4】:

      另一种直接在查询中替换参数值的解决方案 (不完全是您要求的,但它可能对其他人有用):

      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'
      

      这样更容易理解。

      可以通过传递文件参数或复制剪贴板中的sp_executesql、运行应用程序然后从剪贴板粘贴生成的 SQL 来使用该页面上的控制台应用程序。

      更新:

      还可以将 SQL 格式化程序添加到该解决方案中以提高可读性:

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

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

      【讨论】:

        【解决方案5】:

        Sql Prompt 最近(2017-02-06)得到了这个功能。选择文本并在上下文菜单中查找“Inline EXEC”。一定要爱提示:)

        【讨论】:

          【解决方案6】:

          您可以使用此 Azur 数据工作室扩展。它基于@Matt Roberts 回购。 https://github.com/PejmanNik/sqlops-spexecutesql-to-sql/releases/tag/0.0.1

          【讨论】:

            【解决方案7】:

            我不知道现有的加载项可以做到这一点。但是你可以创建一个:)

            一些正则表达式和一些字符串连接,然后将其卖给 Vinko 和其他寻找此功能的灵魂。

            如果您想深入研究,这里有一些关于创建 SSMS 插件的信息: http://sqlblogcasts.com/blogs/jonsayce/archive/2008/01/15/building-a-sql-server-management-studio-addin.aspx

            【讨论】:

              【解决方案8】:

              我也遇到了这个问题,并编写了简单的应用程序来解决它 - 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' ) )

              【讨论】:

                【解决方案9】:

                结论:我注意到这仍然会引起一些注意,因此我将在此处添加详细信息以了解我的最终解决方案。

                事实证明,没有什么比为自己做这件事更好的了。我创建了一个简单的控制台应用程序来解析我的存储过程并吐出我想要的内容。通过将其添加到外部工具列表中,并将当前文件名作为参数传递,我可以使用以下内容来删除和重新排列我需要的内容。

                在使用中,我会添加一个新的 sql 文件,粘贴 sql,保存,然后运行外部工具。完成后,IDE 要求我重新加载文件。噗,没有存储过程了。

                我确实注意到,这可能不适用于 every executesql 语句,因此如果它不能满足您的需求,则必须进行修改。

                class Program
                {
                    const string query = "query";
                    const string decls = "decls";
                    const string sets = "sets";
                    static void Main(string[] args)
                    {
                        try
                        {
                            var text = File.ReadAllText(args[0]);
                            if(string.IsNullOrEmpty(text))
                            {
                                Console.WriteLine("File is empty.  Try saving it before using the hillbilly sproc 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.  Shit.");
                                Console.Read();
                                return;
                            }
                
                            var sb = new StringBuilder();
                            sb.Append("DECLARE ").AppendLine(match.Groups[decls].Value);
                            foreach(var set in match.Groups[sets].Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                                sb.Append("SET ").AppendLine(set);
                            sb.AppendLine(match.Groups[query].Value.Replace("''", "'"));
                            File.WriteAllText(args[0], sb.ToString());
                        }
                        catch(Exception ex)
                        {
                            Console.WriteLine("S*t blew up, yo");
                            Console.WriteLine(ex.ToString());
                            Console.WriteLine("Press a key to exit");
                            Console.Read();
                        }
                    }
                }
                

                【讨论】:

                • 天啊,@marc,你那么讨厌存储过程吗?
                【解决方案10】:

                这是我用来检查 NHibernate 查询的简单 UI。使用了一些正则表达式、解析和sqlformat.org API 来美化sql。

                <html>
                <head>
                    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
                    <script type="text/javascript">
                        $(function(){
                            $("#btn-format").on("click", () =>{
                                var insql = $("#textarea-in").val();        
                
                                var regex = new RegExp("exec sp_executesql N'(?<command>.+?(?='(,N'$)?))'(,\s*N'(?<types>.+?(?=',))',\s*(?<vals>.+))?");
                                var groups = insql.replace(/\n|\r/g, "").match(regex).groups;
                
                                var outsql = "";
                                if (groups.types)
                                {
                                    var types = groups.types.match(/@[^\s]+ \w+(\([\w\d,]+\))?/g);
                                    for (const typeDeclaration of types) {
                                        outsql = outsql + 'declare ' +  typeDeclaration + '\n';
                                    }
                                    outsql = outsql + '\n';
                                    for (const setVal of groups.vals.split(',')) {
                                        outsql = outsql + 'set ' +  setVal + '\n';
                                    }
                                    outsql = outsql + '\n';
                                }
                                $.ajax({
                                    url: 'https://sqlformat.org/api/v1/format',
                                    type: 'POST',
                                    dataType: 'json',
                                    crossDomain: true,
                                    data: {
                                        sql: groups.command, reindent: 1
                                    },
                                    success: (data) => {
                                        outsql = outsql + data.result;
                                        $("#textarea-out").val(outsql);
                                    },
                                    error: () =>{
                                        outsql = outsql + '-- No format happened. See browser console for details \n';
                                        outsql = outsql + groups.command;
                                        $("#textarea-out").val(outsql);
                                    }
                                });        
                            })    
                        });
                    </script>        
                </head>
                <body>
                    <textarea id="textarea-in" style="width: 100%; height: 48%;" class="form-control" placeholder="type 'exec sp_executesql...' here"></textarea>
                    <br/>
                    <button id="btn-format">Format</button>
                    <br/>
                    <textarea id="textarea-out" style="width: 100%; height: 48%;" class="form-control"></textarea>
                </body>
                

                Test in Fiddle

                注意:如果查询中有单引号,则不起作用

                【讨论】:

                  猜你喜欢
                  • 2017-05-29
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-01-18
                  • 2017-08-16
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多