Легкий способ преобразования 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 во что-то более удобное?
Ответы
Ответ 1
Мне не известно о существующем надстройке, который может это сделать. Но вы можете создать один:)
Несколько регулярных выражений и некоторая последовательность конкатенации и после этого продают ее Vinko и другим душам, ищущим эту функциональность.
Если вы чувствуете, как погружение в это, вот некоторая информация о создании SSMS addin:
http://sqlblogcasts.com/blogs/jonsayce/archive/2008/01/15/building-a-sql-server-management-studio-addin.aspx
Ответ 2
Я потратил немного времени на создание простого script, который сделал это для меня. Это 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
Ответ 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 из буфера обмена.
Update:
Форматирование SQL также можно добавить к этому решению для упрощения чтения:
http://www.nuget.org/packages/PoorMansTSQLFormatter/
newSql = ConvertSql(Clipboard.GetText());
var formattedSql = SqlFormattingManager.DefaultFormat(newSql);
Clipboard.SetText(formattedSql);
Ответ 5
Я потратил немного времени и создал небольшую модификацию решений Matt Roberts/Wangzq без раздела DECLAREs, вы можете попробовать его на .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.";
}
}
Ответ 6
Заключение: я отмечаю, что это все еще уделяет мало внимания, поэтому я добавлю подробности здесь, для чего мое возможное решение было.
Оказывается, ничто не делает это для себя. Я создал простое консольное приложение, которое проанализировало мою хранимую процедуру и выплевывало то, что я хотел. Добавив его в список внешних инструментов и передав текущее имя файла в качестве аргумента, я мог бы использовать следующее, чтобы разграничить и изменить то, что мне нужно.
В использовании я бы добавил новый sql файл, вставил в sql, сохранил его, а затем запустил внешний инструмент. После его завершения IDE просит меня перезагрузить файл. Poof, больше не хранится процедура.
Обработка исключений и другие вещи удаляются, и я замечаю, что это может не работать с каждым оператором 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());
Ответ 7
Sql Prompt получил эту функцию недавно (2017-02-06). Выберите текст и найдите "Inline EXEC" в контекстном меню. Gotta love Prompt:)
Ответ 8
Я столкнулся с этой проблемой и написал простое приложение для ее решения - ClipboardSqlFormatter. Это приложение лотка, которое прослушивает события ввода буфера обмена и пытается обнаружить и преобразовать динамический sql в статический sql.
Все, что вам нужно, это скопировать динамический sql (например, из профилей sql) и вставить в текстовый редактор - вставленный 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
Вы можете использовать это расширение студии данных Azur. он основан на ремарке @Matt Roberts. https://github.com/PejmanNik/sqlops-spexecutesql-to-sql/releases/tag/0.0.1
![enter image description here]()