I am following the Snowflake Python Connector docs for variable binding to avoid SQL injection. I successfully set up a db connection with the following dict of credentials:
import snowflake.connector
CONN = snowflake.connector.connect(
user=snowflake_creds['user'],
password=snowflake_creds['password'],
account=snowflake_creds['account'],
warehouse=snowflake_creds["warehouse"],
database=snowflake_creds['database'],
schema=snowflake_creds['schema'],
)
cur = CONN.cursor(snowflake.connector.DictCursor)
The following block works fine and I get back query results, hard-coding the table name and using the standard format binding:
command = ("SELECT * FROM TEST_INPUT_TABLE WHERE batch_id = %s")
bind_params = (2)
results = cur.execute(command % bind_params).fetchall()
Similarly, this block works fine, using the pyformat binding:
command = ("SELECT * FROM TEST_INPUT_TABLE WHERE batch_id = %(id)s")
bind_params = {"id": 2}
results = cur.execute(command, bind_params).fetchall()
But the following two blocks both result in a ProgrammingError (pasted below the second block):
command = ("SELECT * FROM %s WHERE batch_id = %s")
bind_params = ("TEST_INPUT_TABLE", 2)
results = cur.execute(command, bind_params).fetchall()
command = ("SELECT * FROM %(tablename)s WHERE batch_id = %(id)s")
bind_params = {
"tablename": "TEST_INPUT_TABLE",
"id": 2
}
results = cur.execute(command, bind_params).fetchall()
ProgrammingError: 001011 (42601): SQL compilation error:
invalid URL prefix found in: 'TEST_INPUT_TABLE'
Is there some difference between how strings and ints get interpolated? I would
not think it would make a difference but that is all I can think of. Am I
missing something simple here? I don't want to have to choose between hard-coding the table name and putting the system at risk of SQL injection. Thanks for any guidance.
You should be wrapping your bind variables with an INDENTIFER() function when they reference an object, rather than a string literal. For example:
command = ("SELECT * FROM IDENTIFIER(%(tablename)s) WHERE batch_id = %(id)s")
https://docs.snowflake.com/en/sql-reference/identifier-literal.html
Give that a try.
Question is related to Snowflake and Snowsql. That said I'm trying to within a stored proc create a temp table and then 'copy into' this temp table from azure blob storage.
I'm manually executed the snow sql statements and they work fine.
Statement 1:CREATE TEMPORARY TABLE DB.TABLES.LINE_DETAILS_INCREMENTAL LIKE DB.TABLES.LINE_DETAILS;
Statement 2:
COPY INTO DB.TABLES.LINE_DETAILS_INCREMENTAL FROM (SELECT * FROM #DB.BASE.Azure/S/LINE_DETAILS_INCREMENTAL )
force = false file_format = (type = csv field_delimiter = '|' encoding = 'Windows 1252' skip_header = 0);
But when I encapsulate this into a stored proc and try to run it it gives error:-
"JavaScript compilation error: Uncaught SyntaxError: Unexpected identifier in SP_DELETE_LINE_DETAILS at ' var insert_clause = 'COPY INTO DB.TABLES.LINE_DETAILS_INCREMENTAL FROM (SELECT * FROM #Feeds_DB.BASE.Azure/S/LINE_DETAILS_INCREMENTAL ) force = true file_format = (type = csv field_delimiter = '|' encoding = 'Windows1252' skip_header = 0) On_error = continue;'' position 288 ".
Code of the stored proc is:-
CREATE or replace procedure "DB"."TABLES"."SP_DELETE_INSERT_3DAYS_INTO_LINE_DETAILS"()
returns varchar(1000)
language javascript
as
$$
try{
var create_clause = 'CREATE TEMPORARY TABLE DB.TABLES.LINE_DETAILS_INCREMENTAL LIKE DB.TABLES.PS_TRANSACTION_LINE_DETAILS;'
var create_stmt = snowflake.createStatement({sqlText: create_clause});
var create_res = create_stmt.execute();
var insert_clause = 'COPY INTO DB.TABLES.LINE_DETAILS_INCREMENTAL FROM (SELECT * FROM #Tableau_Feeds_DB.BASE_TABLES.JDAStagingAzure/POS/PS_TRANSACTION_LINE_DETAILS_INCREMENTAL ) force = true file_format = (type = csv field_delimiter = '|' encoding = 'Windows1252' skip_header = 0) On_error = continue;'
var insert_stmt = snowflake.createStatement({sqlText: insert_clause});
var insert_res = insert_stmt.execute();
var select_clause = 'select distinct TO_CHAR(TO_DATE(CREATE_DATE)) as CREATE_DATE from DB.TABLES.LINE_DETAILS_INCREMENTAL order by CREATE_DATE';
var select_stmt = snowflake.createStatement({sqlText: select_clause});
var select_res = select_stmt.execute();
while (select_res.next())
{
date_ip = select_res.getColumnValue(1);
var desc_user_sql = `delete from DB.TABLES.LINE_DETAILS where TO_DATE(CREATE_DATE) = :1;`
var desc_user_stmt = snowflake.createStatement({sqlText: desc_user_sql, binds: [date_ip]});
var desc_user_sql2 = `INSERT INTO DB.TABLES.PS_TRANSACTION_LINE_DETAILS select * from DB.TABLES.PS_TRANSACTION_LINE_DETAILS_INCREMENTAL where TO_DATE(CREATE_DATE) = :1;`
var desc_user_stmt2 = snowflake.createStatement({sqlText: desc_user_sql2, binds: [date_ip]});
try{
desc_user_stmt.execute();
desc_user_stmt2.execute();
}
catch(err)
{
return "Error inserting records: " +err;
}
}
return "Data has been insert in success!";
}
catch(err){
return "Error whileselecting Roles : " +err;
}
return 0;
$$
I think the issue you have here is that you're using single quote marks both to start/end your string, and within the string itself.
For example, you have the following phrase in your string:
encoding = 'Windows1252'
I would suggest escaping the additional quotation marks with a backslash, like so:
encoding = \'Windows1252\'
Do this for all additional quotation marks and you should be fine.
Let me know if you still face issues after!
Try changing single quotes as below
var insert_clause = `COPY INTO DB.TABLES.LINE_DETAILS_INCREMENTAL FROM (SELECT * FROM #Tableau_Feeds_DB.BASE_TABLES.JDAStagingAzure/POS/PS_TRANSACTION_LINE_DETAILS_INCREMENTAL ) force = true file_format = (type = csv field_delimiter = '|' encoding = 'Windows1252' skip_header = 0) On_error = continue;`
https://docs.snowflake.com/en/sql-reference/stored-procedures-usage.html#line-continuation
Using Nodejs and mssql package for SQL Server, I understand normal SELECT query with parameters:
aVar = "somestring"; // used as example, the real input is from a web form
request = new sql.Request(conn)
.input('aParam', sql.NVarChar(200), aVar);
q = "SELECT * FROM myTable WHERE theColumn = #aParam";
request.query(q, function(err, record) { ...
Those work just fine. Changing this to use LIKE, and I cannot get it to work. I've tried single quote before the % character, after, around it, just around the string, the whole thing, etc. Doing this:
q = "SELECT * FROM myTable WHERE theColumn LIKE " + "'%#aParam%'";
does not result in any errors, but returns no results. All other attempts with those single quotes results in query syntax errors. I cant seem to locate any documentation for parameterizing queries with wildcards. Where am I going wrong with this?
The syntax to do this is mentioned here - https://github.com/tediousjs/node-mssql/issues/332.
But in short:
q = `SELECT * FROM myTable WHERE theColumn LIKE '%' + #aParam + '%';`;
Unless you writing low level driver/worker, I would recommend to use any ORM:
const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password');
const MyTable = sequelize.define('myTable', {
theColumn: Sequelize.STRING
});
const aVar = 'somestring';
const results = await MyTable.findAll({
where: {
theColumn: { $like: `%${aVar}%` }
}
});
})
console.dir(results);
await can be used only inside async functions. For Framework supporting this out of the box take a look at Koa.js https://github.com/koajs/koa
I am trying to write a dapper query for IN clause, but it's not working throwing casting error saying "Conversion failed when converting the nvarchar value 'A8B08B50-2930-42DC-9DAA-776AC7810A0A' to data type int." . In below query fleetAsset is Guid converted into string.
public IQueryable<MarketTransaction> GetMarketTransactions(int fleetId, int userId, int rowCount)
{
//Original EF queries which I am trying to convert to Dapper
//var fleetAsset = (from logicalFleetNode in _context.LogicalFleetNodes
// where logicalFleetNode.LogicalFleetId == fleetId
// select logicalFleetNode.AssetID).ToList();
////This query fetches guid of assetprofiles for which user having permissions based on the assets user looking onto fleet
//var assetProfileIds = (from ap in _context.AssetProfileJoinAccounts
// where fleetAsset.Contains(ap.AssetProfile.AssetID) && ap.AccountId == userId
// select ap.AssetProfileId).ToList();
var fleetAsset = _context.Database.Connection.Query<string>("SELECT CONVERT(varchar(36),AssetID) from LogicalFleetNodes Where LogicalFleetId=#Fleetid",
new { fleetId }).AsEnumerable();
//This query fetches guid of assetprofiles for which user having permissions based on the assets user looking onto fleet
var sql = String.Format("SELECT TOP(#RowCount) AssetProfileId FROM [AssetProfileJoinAccounts] AS APJA WHERE ( EXISTS (SELECT " +
"1 AS [C1] FROM [dbo].[LogicalFleetNodes] AS LFN " +
"INNER JOIN [dbo].[AssetProfile] AS AP ON [LFN].[AssetID] = [AP].[AssetID]" +
" WHERE ([APJA].[AssetProfileId] = [AP].[ID]) " +
" AND ([APJA].[AccountId] = #AccountId AND LogicalFleetId IN #FleetId)))");
var assetProfileIds = _context.Database.Connection.Query<Guid>(sql, new { AccountId = userId, FleetId = fleetAsset, RowCount=rowCount });
Dapper performs expansion, so if the data types match, you should just need to do:
LogicalFleetId IN #FleetId
(note no parentheses)
Passing in a FleetId (typically via an anonymous type like in the question) that is an obvious array or list or similar.
If it isn't working when you remove the parentheses, then there are two questions to ask:
what is the column type of LocalFleetId?
what is the declared type of the local variable fleetAsset (that you are passing in as FleetId)?
Update: test case showing it working fine:
public void GuidIn_SO_24177902()
{
// invent and populate
Guid a = Guid.NewGuid(), b = Guid.NewGuid(),
c = Guid.NewGuid(), d = Guid.NewGuid();
connection.Execute("create table #foo (i int, g uniqueidentifier)");
connection.Execute("insert #foo(i,g) values(#i,#g)",
new[] { new { i = 1, g = a }, new { i = 2, g = b },
new { i = 3, g = c },new { i = 4, g = d }});
// check that rows 2&3 yield guids b&c
var guids = connection.Query<Guid>("select g from #foo where i in (2,3)")
.ToArray();
guids.Length.Equals(2);
guids.Contains(a).Equals(false);
guids.Contains(b).Equals(true);
guids.Contains(c).Equals(true);
guids.Contains(d).Equals(false);
// in query on the guids
var rows = connection.Query(
"select * from #foo where g in #guids order by i", new { guids })
.Select(row => new { i = (int)row.i, g = (Guid)row.g }).ToArray();
rows.Length.Equals(2);
rows[0].i.Equals(2);
rows[0].g.Equals(b);
rows[1].i.Equals(3);
rows[1].g.Equals(c);
}
When dealing with debugging queries using Profiler and SSMS, its pretty common for me to copy a query from Profiler and test them in SSMS. Because I use parameterized sql, my queries are all sent as exec sp_executesql queries.
exec sp_executesql
N'/*some query here*/',
N'#someParameter tinyint',
# someParameter =2
I'll take this and convert it into a normal query for ease of editing (intellisense, error checking, line numbers, etc):
DECLARE #someParameter tinyint
SET #someParameter = 2
/*some query here*/
Of course, the bigger and more complex the query, the harder to do this. And when you're going back and forth multiple times, it can be a pain in the ass and soak up lots of time.
Is there an easy (e.g., macro command) way to convert muh executesql into something more convenient?
I spent a little time making an simple script that did this for me. It's a WIP, but I stuck a (very ugly) webpage in front of it and it's now hosted here if you want to try it:
http://execsqlformat.herokuapp.com/
Sample input:
exec sp_executesql
N'SELECT * FROM AdventureWorks.HumanResources.Employee
WHERE ManagerID = #level',
N'#level tinyint',
#level = 109;
And the output:
BEGIN
DECLARE #level tinyint;
SET #level = 109;
SELECT * FROM AdventureWorks.HumanResources.Employee
WHERE ManagerID = #level
END
The formatting of the actual SQL statement once I've plucked it from the input is done using the API at http://sqlformat.appspot.com
I was looking for something similar so I use this in LinqPad, just copy sp_executesql statement to the clipboard and run the code in LinqPad. It outputs the SQL statement.
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();
}
}
I spent a little time and created a small modification of Matt Roberts / Wangzq solutions without DECLAREs section, you can try it on .NET Fiddle or download LINQPad 5 file.
Input:
exec sp_executesql N'UPDATE MyTable SET [Field1] = #0, [Field2] = #1',N'#0 nvarchar(max) ,#1 int',#0=N'String',#1=0
Output:
UPDATE MyTable SET [Field1] = N'String', [Field2] = 0
Code:
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.";
}
}
Another solution which replaces the parameter values directly in the query
(not exactly what you asked for but it might prove useful to others):
https://code.msdn.microsoft.com/windowsdesktop/spExecuteSql-parser-1a9cd7bc
I goes from:
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'
to:
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'
which makes it easier to understand.
The console application on that page can be used by passing a file parameter or copying the sp_executesql in the clipboard, running the app and then pasting the resulting SQL from the clipboard.
Update:
An SQL formatter can also be added to that solution for easier readability:
http://www.nuget.org/packages/PoorMansTSQLFormatter/
newSql = ConvertSql(Clipboard.GetText());
var formattedSql = SqlFormattingManager.DefaultFormat(newSql);
Clipboard.SetText(formattedSql);
You can use this Azur data studio extension. it based on #Matt Roberts repo.
https://github.com/PejmanNik/sqlops-spexecutesql-to-sql/releases/tag/0.0.1
Sql Prompt got this feature recently (2017-02-06). Select the text and look for "Inline EXEC" in the context menu. Gotta love Prompt :)
I am not aware of an existing Add-In that can do this. But you could create one :)
A few regular expressions and some string concatenation and after that sell it to Vinko and other souls looking for this functionality.
If you're feeling like diving into this, here is some information on creating an SSMS addin:
http://sqlblogcasts.com/blogs/jonsayce/archive/2008/01/15/building-a-sql-server-management-studio-addin.aspx
I faced with this problem too and wrote simple application for solving it - ClipboardSqlFormatter. This is a tray application that listens clipboard input events and tries to detect and convert dynamic sql to static sql.
Anything you need is to copy dynamic sql (from sql profiler for example) and paste to text editor - pasted sql will be a static sql :)
For example, if copied sql is:
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'
then pasted sql will be:
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'
)
)
Conclusion: I note this still gets a little attention so I'll add details here for what my eventual solution was.
It turns out that nothing beats doing it for yourself. I created a simple console app that parsed my stored procedure and spit out what I wanted. By adding it to the list of external tools, and passing the current filename as an argument, I could use the following to strip out and rearrange what I needed.
In use, I'd add a new sql file, paste in the sql, save it, then run the external tool. After it completes, the IDE asks me to reload the file. Poof, no more stored procedure.
I do note that this may not work with every executesql statement, so you'll have to modify if it does not meet your needs.
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();
}
}
}
Here is simple UI that i use to inspect NHibernate queries. Some regex, parsing and sqlformat.org API to beautify sql is used.
<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
Note: Will not work, if you have single quotes in query
I have improved one of previous answers particularly refining regular expression in order to support queries without parameters. Here is my option in form of PowerShell script, which uses Windows clipboard both as input and as output:
$regex = "(?s)^exec sp_executesql N'(?<query>.*?[^'])'(?:,N'(?<decls>.*?[^'])',(?<sets>.*))?$"
$inputText = [string](Get-Clipboard)
if (!($inputText -match $regex)) {
return
}
$resultBuilder = [System.Text.StringBuilder]::new()
if ($matches["decls"]) {
$resultBuilder.AppendLine("DECLARE $($matches["decls"])")
$matches["sets"].Split(',') | ForEach-Object {
$resultBuilder.AppendLine("SET $_");
}
}
$resultBuilder.AppendLine($matches["query"].Replace("''", "'"));
Set-Clipboard $resultBuilder.ToString()