Get default value of stored procedure parameter - sql-server

I have created below stored procedure with default value:
CREATE PROCEDURE [dbo].[Sample1]
#OrderID INT = 10285
AS
SELECT ProductName, OrderID
FROM Products P, [Order Details] Od
WHERE Od.ProductID = P.ProductID
AND Od.OrderID = #OrderID
Tried to get default value (10285) of parameters using sys.parameters.
Select a.object_id, a.default_value
from sys.parameters a
inner join sys.types b on b.system_type_id = a.system_type_id
where Object_id = object_id('[dbo].[Sample1]')
But I got NULL as default_value, while I was expecting 10285 as default_value.
Is there any way to get default value?

It looks that Microsoft has neglected this topic and there is no trivial way to find parameters default values and even if a default value is present or not on a specific parameter:
As we all know, T-SQL stored procedure parameter defaults are not
stored in sys.parameters, all_parameters, and system_parameters. They
are also not exposed through sp_sproc_columns, sys.columns, or
sp_procedure_params_rowset.
Feedback from Microsoft:
As posted by Tibor Karaszi, BOL document that "SQL Server only
maintains default values for CLR objects in this catalog view;
therefore, this column has a value of 0 for Transact-SQL objects. To
view the default value of a parameter in a Transact-SQL object, query
the definition column of the sys.sql_modules catalog view, or use the
OBJECT_DEFINITION system function."
We dont store even the bit that indicating parameter is of default
value in Yukon.
I have tested the first code snippet in this answer and it seems to work for your simple example:
SELECT
data3.name
, [default_value] = REVERSE(RTRIM(SUBSTRING(
data3.rtoken
, CASE
WHEN CHARINDEX(N',', data3.rtoken) > 0
THEN CHARINDEX(N',', data3.rtoken) + 1
WHEN CHARINDEX(N')', data3.rtoken) > 0
THEN CHARINDEX(N')', data3.rtoken) + 1
ELSE 1
END
, LEN(data3.rtoken)
)))
FROM (
SELECT
data2.name
, rtoken = REVERSE(
SUBSTRING(ptoken
, CHARINDEX('=', ptoken, 1) + 1
, LEN(data2.ptoken))
)
FROM (
SELECT
data.name
, ptoken = SUBSTRING(
data.tokens
, token_pos + name_length + 1
, ISNULL(ABS(next_token_pos - token_pos - name_length - 1), LEN(data.tokens))
)
FROM (
SELECT
sm3.tokens
, p.name
, name_length = LEN(p.name)
, token_pos = CHARINDEX(p.name, sm3.tokens)
, next_token_pos = CHARINDEX(p2.name, sm3.tokens)
FROM (
SELECT
sm2.[object_id]
, sm2.[type]
, tokens = REVERSE(SUBSTRING(sm2.tokens, ISNULL(CHARINDEX('SA', sm2.tokens) + 2, 0), LEN(sm2.tokens)))
FROM (
SELECT
sm.[object_id]
, o.[type]
, tokens = REVERSE(SUBSTRING(
sm.[definition]
, CHARINDEX(o.name, sm.[definition]) + LEN(o.name) + 1
, ABS(CHARINDEX(N'AS', sm.[definition]))
)
)
FROM sys.sql_modules sm WITH (NOLOCK)
JOIN sys.objects o WITH (NOLOCK) ON sm.[object_id] = o.[object_id]
JOIN sys.schemas s WITH (NOLOCK) ON o.[schema_id] = s.[schema_id]
WHERE o.[type] = 'P '
AND s.name + '.' + o.name = 'dbo.Sample1'
) sm2
WHERE sm2.tokens LIKE '%=%'
) sm3
JOIN sys.parameters p WITH (NOLOCK) ON sm3.[object_id] = p.[object_id]
OUTER APPLY (
SELECT p2.name
FROM sys.parameters p2 WITH (NOLOCK)
WHERE p2.is_output = 0
AND sm3.[object_id] = p2.[object_id]
AND p.parameter_id + 1 = p2.parameter_id
) p2
WHERE p.is_output = 0
) data
) data2
WHERE data2.ptoken LIKE '%=%'
) data3
However, it is really ugly for a task that one expects to be easily queryable from system views.

I agree default stored procedure parameter values should be exposed via a SQL Server catalog view.
The T-SQL parsing method may work in many cases but is fragile. Consider using the TransactSQL ScriptDOM. Below is an example using a mix of PowerShell and C#. Not saying this will be perfect for all cases but it seems to process all the parameters I've thrown at it thusfar.
I used the Microsoft.SqlServer.TransactSql.ScriptDom.dll assembly from my SSMS install in this example but it can be downloaded from the NuGet Gallery.
try
{
Add-type -LiteralPath #("C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Extensions\Application\Microsoft.SqlServer.TransactSql.ScriptDom.dll");
Add-type `
-ReferencedAssemblies #("C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Extensions\Application\Microsoft.SqlServer.TransactSql.ScriptDom.dll") `
-TypeDefinition #"
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SqlServer.TransactSql.ScriptDom;
using System.IO;
public static class ProcParser
{
public static List<StoredProcedureParameter> GetStoredProcedureParameters(string storedProcedureDefinition)
{
StringReader reader = new StringReader(storedProcedureDefinition);
var parser = new TSql140Parser(true);
IList<ParseError> errors;
TSqlFragment sqlFragment = parser.Parse(reader, out errors);
if (errors.Count > 0)
{
throw new Exception(`"Error parsing stored procedure definition`");
}
SQLVisitor sqlVisitor = new SQLVisitor();
sqlFragment.Accept(sqlVisitor);
return sqlVisitor.StoredProcedureParameters;
}
}
internal class SQLVisitor : TSqlFragmentVisitor
{
public List<StoredProcedureParameter> StoredProcedureParameters = new List<StoredProcedureParameter>();
public override void ExplicitVisit(ProcedureParameter node)
{
var p = StoredProcedureParameter.CreateProcedureParameter(node);
StoredProcedureParameters.Add(p);
}
}
public class StoredProcedureParameter
{
public string ParameterName;
public string ParameterType;
public string ParameterDirection = null;
public string DefaultParameterValue = null;
public static StoredProcedureParameter CreateProcedureParameter(ProcedureParameter node)
{
var param = new StoredProcedureParameter();
//parameter name
param.ParameterName = node.VariableName.Value;
//data type
switch (((ParameterizedDataTypeReference)node.DataType).Parameters.Count)
{
case 0:
if (node.DataType.Name.Identifiers.Count == 1)
{
param.ParameterType = node.DataType.Name.Identifiers[0].Value;
}
else
{
//schema-qualified type name
param.ParameterType = node.DataType.Name.Identifiers[0].Value + `".`" + node.DataType.Name.Identifiers[1].Value;
}
break;
case 1:
param.ParameterType = node.DataType.Name.Identifiers[0].Value + "(" + ((ParameterizedDataTypeReference)node.DataType).Parameters[0].Value + ")";
break;
case 2:
param.ParameterType = node.DataType.Name.Identifiers[0].Value + "(" + ((ParameterizedDataTypeReference)node.DataType).Parameters[0].Value + "," + ((ParameterizedDataTypeReference)node.DataType).Parameters[1].Value + ")";
break;
}
//default value
if (node.Value != null)
{
param.DefaultParameterValue = node.ScriptTokenStream[node.LastTokenIndex].Text;
}
//direction
if (node.Modifier == ParameterModifier.Output)
{
param.ParameterDirection = `"OUTPUT`";
}
else if (node.Modifier == ParameterModifier.ReadOnly)
{
param.ParameterDirection = `"READONLY`";
}
else
{
param.ParameterDirection = `"INPUT`";
}
return param;
}
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(ParameterName);
sb.Append(`" `");
sb.Append(ParameterType);
if (DefaultParameterValue != null)
{
sb.Append(`" `");
sb.Append(DefaultParameterValue);
}
sb.Append(`" `");
sb.Append(ParameterDirection);
return sb.ToString();
}
}
"#
}
catch [System.Reflection.ReflectionTypeLoadException]
{
Write-Host "Message: $($_.Exception.Message)"
Write-Host "StackTrace: $($_.Exception.StackTrace)"
Write-Host "LoaderExceptions: $($_.Exception.LoaderExceptions)"
throw;
}
Function Get-ProcText($connectionString, $procName)
{
$connection = New-Object System.Data.SqlClient.SqlConnection($connectionString);
$connection.Open();
$command = New-Object System.Data.SqlClient.SqlCommand("SELECT definition FROM sys.sql_modules WHERE object_id = OBJECT_ID(#ProcName);", $connection);
$procNameParameter = $command.Parameters.Add((New-Object System.Data.SqlClient.SqlParameter("#ProcName", [System.Data.SqlDbType]::NVarChar, 261)));
$procNameParameter.Value = $procName;
$procText = $command.ExecuteScalar();
$connection.Close();
return $procText;
}
############
### main ###
############
try {
# get proc text definition from database
$procText = Get-ProcText `
-connectionString "Data Source=.;Initial Catalog=tempdb;Integrated Security=SSPI" `
-procName "dbo.testproc";
# parse parameters from proc text
$procParameters = [ProcParser]::GetStoredProcedureParameters($procText);
# display parameter values
foreach($procParameter in $procParameters)
{
Write-Host "ParameterName=$($procParameter.ParameterName)";
Write-Host "`tParameterType=$($procParameter.ParameterType)";
Write-Host "`tDefaultParameterValue=$($procParameter.DefaultParameterValue)";
Write-Host "`tParameterDirection=$($procParameter.ParameterDirection)";
}
}
catch {
throw;
}

I implemented Alexei's brilliant solution, but one of my variables, and a comment on a parameter both had the word 'class' in it and I couldn't for the life of me figure out why it broke. I finally realized that the word 'class' has 'AS' in it. So the key to avoiding that situation is to differentiate the AS keyword denoting the beginning of the body of the sp from random AS strings in the parameter section. I found a way that works for me, and might work for others. It relies on the fact that the AS keyword is on its own line prededed by CHAR(13) + CHAR(10) and succeeded by CHAR(13) + CHAR(10). So I modified these two lines:
, tokens = REVERSE(SUBSTRING(sm2.tokens, ISNULL(CHARINDEX(CHAR(10) + CHAR(13) + 'SA' + CHAR(10) + CHAR(13), sm2.tokens) + 2, 0), LEN(sm2.tokens)))
, tokens = REVERSE(SUBSTRING(
sm.[definition]
, CHARINDEX(o.name, sm.[definition]) + LEN(o.name) + 1
, ABS(CHARINDEX(CHAR(13) + CHAR(10) + N'AS' + CHAR(13) + CHAR(10), sm.[definition]))
)

Related

Snowflake's get_ddl displays extra single quotes everywhere

In Snowflake, when I create a store proc like so
create procedure stack_overflow_question(select_table varchar)
returns varchar
language sql
as
declare
select_statement varchar;
begin
select_statement := '
SELECT * FROM ' || :select_table || '
';
end;
Then, later when I use select get_ddl('procedure', 'stack_overflow_question(varchar)'); function to make edits to the store proc, the result of this function call has extra single quotes.
Here is the result
CREATE OR REPLACE PROCEDURE "STACK_OVERFLOW_QUESTION"("SELECT_TABLE" VARCHAR(16777216))
RETURNS VARCHAR(16777216)
LANGUAGE SQL
EXECUTE AS OWNER
AS 'declare
select_statement varchar;
begin
select_statement := ''
SELECT * FROM '' || :select_table || ''
'';
end';
Note the difference between the two! The extra single quotes. Also double quotes in the name of the store proc.
Is there something that I can do to prevent this from happening? I am using Snowsight - but don't think that this actually is the problem. Also, I am using snowflake as the language for store procs.
Any ideas?
I wrote a UDF that you can wrap around get_ddl that will convert the DDL from using doubled single quotes to single quotes and wrap the body with $$:
create or replace function CODE_DDL_TO_TEXT(CODE_TEXT string)
returns string
language javascript
as
$$
var lines = CODE_TEXT.split("\n");
var out = "";
var startCode = new RegExp("^AS '$", "ig");
var endCode = new RegExp("^'\;$", "ig");
var inCode = false;
var isChange = false;
var s;
for (i = 0; i < lines.length; i++){
isChange = false;
if(!inCode) {
inCode = startCode.test(lines[i]);
if(inCode) {
isChange = true;
out += "AS $" + "$\n";
}
}
if (endCode.test(lines[i])){
out += "$" + "$;";
isChange = true;
inCode = false;
}
if(!isChange){
if(inCode){
s = lines[i].replace(/''/g, "'") + "\n";
s = s.replace(/\\\\/g, "\\");
out += s;
} else {
out += lines[i] + "\n";
}
}
}
return out;
$$;
You can then call the UDF by wrapping it around the get_ddl function. Here is an example of fishing its own DDL out of get_ddl:
select CODE_DDL_TO_TEXT(get_ddl('function', 'CODE_DDL_TO_TEXT(string)'));
Edit:
You can also use this SQL to reconstruct a stored procedure from the INFORMATION_SCHEMA:
select 'create or replace procedure ' || PROCEDURE_NAME || ARGUMENT_SIGNATURE ||
'\nreturns ' || DATA_TYPE ||
'\nlanguage ' || PROCEDURE_LANGUAGE ||
'\nas $' || '$\n' ||
PROCEDURE_DEFINITION ||
'\n$' || '$;'
from INFORMATION_SCHEMA.PROCEDURES
;
This only returns body -
SELECT PROCEDURE_DEFINITION
FROM INFORMATION_SCHEMA.PROCEDURES
WHERE PROCEDURE_SCHEMA = 'SCHEMA_NAME' AND PROCEDURE_NAME = upper('stack_overflow_question');

Snowflake Stored procedure Copy into Temporary table

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

How to get row data where column = ? also applying limit to rows?

I am getting an error on applying a raw query in sq-lite.The error is
Caused by: android.database.sqlite.SQLiteException: near "Limit": syntax error (code 1): ,
while compiling: SELECT * FROM quiz_questions WHERE Levels = "Level 1"+ Limit 2+
The query SELECT * FROM quiz_questions WHERE Levels = "Level 1" works and gives me multiple rows but when i am applying limit to query, it gives error.
This is code for it.
public ArrayList<Question> getLockedLevels(ArrayList<String> Level) {
ArrayList<Question> questionList = new ArrayList<>();
db = getReadableDatabase();
for (int i = 0; i < Level.size(); i++)
{
String levelID = Level.get(i);
String SELECT_TABLE_QUERY = "SELECT * FROM " + TABLE_NAME + " WHERE " + COLUMN_LEVEL + " = \"" + levelID + "\"+ Limit 1";
Cursor cursor = db.rawQuery(SELECT_TABLE_QUERY, null);
if (cursor.moveToFirst()) {
do {
Question question = new Question();
question.setmLevels(cursor.getString(cursor.getColumnIndex(COLUMN_LEVEL)));
question.setmLevels_lockmanager(cursor.getInt(cursor.getColumnIndex(COLUMN_LEVEL_LOCKMANAGER)));
questionList.add(question);
} while (cursor.moveToNext());
}
cursor.close();
}
return questionList;
}
Try
String SELECT_TABLE_QUERY = "SELECT * FROM " + TABLE_NAME + " WHERE " + COLUMN_LEVEL + " = '" + levelID + "' Limit 1";
That should resovle to :-
SELECT * FROM quiz_questions WHERE Levels = 'Level 1' Limit 1
However, the above is subject to potential SQL Injection.
Using :-
String levelID = Level.get(i);
String SELECT_TABLE_QUERY = "SELECT * FROM " + TABLE_NAME + " WHERE " + COLUMN_LEVEL + "=? Limit 1";
Cursor cursor = db.rawQuery(SELECT_TABLE_QUERY, new String[]{levelID });
would remove the potential for SQL Injection and is therefore considered the more correct way.
Alternatively you could utilise the convenience query method that not only protects against SQL Injection but also generates much of the SQL.
So you could use :-
for (int i = 0; i < Level.size(); i++) {
Cursor cursor = db.query(TABLE_NAME,null,COLUMN_LEVEL + "=?",new String[]{Level.get(i)},null,null,null,"1");
while (cursor.moveToNext()) {
Question question = new Question();
question.setmLevels(cursor.getString(cursor.getColumnIndex(COLUMN_LEVEL)));
question.setmLevels_lockmanager(cursor.getInt(cursor.getColumnIndex(COLUMN_LEVEL_LOCKMANAGER)));
questionList.add(question);
}
cursor.close();
}
See SQliteDatabase - query
The above also uses the shorter while(cursor.moveToNext) { ..... } for looping through the Cursor (if there are no rows then the loop is not entered as moveToNext will return false if the move cannot be made)

SQL Joining tables; can you repeat columns?

I'm trying to compose a view that we can use to export our inventory.
I have two tables:
Inventory, which contains the columns Description, Year, Make, Model, and Serial.
Pictures, which contains the columns DocumentBody, MimeType, Serial, and Last Modified.
I'd like to make a view that has all columns from Inventory, and also adds columns for x amount of Pictures related to Serial number.
So if there were two pictures with the same serial number, the resultant table would include these fields:
Description, Year, Make, Model, Serial, DocumentBody1, MimeType1, Last Modified1, DocumentBody2, MimeType2, Last Modified2.
For those Inventory items that only have one picture, the second picture columns would all be null.
Is this something I can even do? From what I'm reading about joins, it doesn't seem possible.
As others have said, you should probably evaluate whether you actually need the view you think you need. But if you really want it, you could use PIVOT in MSSQL:
WITH BaseData AS
(
SELECT Serial
,DocumentBody
,MimeType
,LastModified
,ROWNUMBER() OVER(PARTITION BY Serial ORDER BY LastModified) AS RowNum
FROM Pictures) AS t
),
DocumentPivot AS (
SELECT
Serial
,DocumentBody
,'DocumentBody' + RowNum AS ColumnName
FROM BaseData
),
MimePivot AS (
SELECT
Serial
,MimeType
,'MimeType' + RowNum AS ColumnName
FROM BaseData
),
ModifiedPivot AS (
SELECT
Serial
,LastModified
,'LastModified' + RowNum AS ColumnName
FROM BaseData
)
SELECT Description
,Year
,Make
,Model
,Inventory.Serial
,DocumentBody1
,MimeType1
,LastModified1
,DocumentBody2
,MimeType2
,LastModified2
,...
,LastModified10
FROM Inventory
LEFT OUTER JOIN (
SELECT Serial
,DocumentBody1
,DocumentBody2
,...
,DocumentBody10
FROM DocumentPivot
PIVOT (MAX(DocumentBody) FOR ColumnName IN (DocumentBody1, DocumentBody2, ..., DocumentBody10)) AS P1
) AS Documents
ON Documents.Serial=Inventory.Serial
LEFT OUTER JOIN (
SELECT Serial
,MimeType1
,MimeType2
,...
,MimeType10
FROM MimePivot
PIVOT (MAX(MimeType) FOR ColumnName IN (MimeType1, MimeType2, ..., MimeType10)) AS P2
) AS Mimes
ON Mimes.Serial=Inventory.Serial
LEFT OUTER JOIN (
SELECT Serial
,LastModified1
,LastModified2
,...
,LastModified10
FROM ModifiedPivot
PIVOT (MAX(LastModified) FOR ColumnName IN (LastModified1, LastModified2, ..., LastModified10)) AS P3
) AS Modifieds
ON Modifieds.Serial=Inventory.Serial
Select inventory.*, count(pictures.serial) as picture_count From inventory Left Join pictures On inventory.serial = pictures.serial Where [your where statement]
Use Left Join in case there are no pictures at all. This way you still get back a result.
Update
Actually, after reading your question again, it seems you just want to extend your search results with each additional picture in the system. That's not the best way to do this. The best you can do is just get a row returned for each pic that's in the system.
Select inventory.*, pictures.DocumentBody, pictures.MimeType, pictures.Serial, pictures.Last_Modified From inventory Left Join pictures On inventory.serial = pictures.serial Where [your where statement]
Since there is no "Group By" clause, this will give you 1 row for each picture. Then you can just loop through the results.
Also
There are ways to do this by making temp tables, looping through results within a stored procedure, creating new columns (DocumentBody1, DocumentBody2, etc) for each picture result and adding the data to the new fields, then querying the temp table. But that's a lot to go through I would think.
thanks for the help everyone. In the end, I ended up using PHP to accomplish this with the following code:
<?php
date_default_timezone_set('America/Edmonton');
$serverName = "database";
$connectionInfo = array( "Database"=>"CRM_MSCRM");
$conn = sqlsrv_connect( $serverName, $connectionInfo);
if( $conn === false )
{
echo "Unable to connect.\n\n";
die( print_r( sqlsrv_errors(), true));
}
else
{
echo "Connected. Selecting trucks...\n\n";
}
$tsql = "SELECT * FROM CRM_MSCRM.dbo.Trader_Export_Simple";
$stmt = sqlsrv_query( $conn, $tsql);
if( $stmt === false )
{
echo "Error executing query.\n\n";
die( print_r( sqlsrv_errors(), true));
}
$csvData = array();
while ($row = sqlsrv_fetch_array($stmt))
{
$count = 1;
$mainpicsql = "SELECT * FROM CRM_MSCRM.dbo.TruckImages WHERE Serial = '".$row[0]."' AND MainPic = 1";
$mainpicstmt = sqlsrv_query( $conn, $mainpicsql);
while ($mainpicrow = sqlsrv_fetch_array($mainpicstmt))
{
$truck = $mainpicrow[1];
$mainfilename = $truck ."-". $count . ".png";
file_put_contents($mainfilename, base64_decode($mainpicrow[0]));
$mainpicdate = $mainpicrow[3]->format("d/m/Y h:m:s");
$mainfilename = "http://images.website/images/".$mainfilename;
echo $mainpicdate."\n";
}
$picsql = "SELECT * FROM CRM_MSCRM.dbo.TruckImages WHERE Serial = '".$row[0]."' AND MainPic = 0";
$picstmt = sqlsrv_query( $conn, $picsql);
$extrapicsdate = "";
$filenames = "";
while ($picrow = sqlsrv_fetch_array($picstmt))
{
$count++;
$filename = $picrow[1] ."-". $count . ".png";
file_put_contents($filename, base64_decode($picrow[0]));
$picdate = $picrow[3]->format("d/m/Y h:m:s");
$filenames .= "http://images.website/images/".$filename.";";
$extrapicsdate .= $picdate.";";
}
$filenames = rtrim($filenames, ";");
$extrapicsdate = rtrim($extrapicsdate, ";");
echo $filenames."\n";
echo $extrapicsdate."\n";
if ($truck != "") {
$csvData[] = array($truck, $mainfilename, $mainpicdate, $filenames, $extrapicsdate);
}
if ($filenames != "")
{
$filenames = "";
}
if ($extrapicsdate != "")
{
$extrapicsdate = "";
}
echo "Next truck...\n\n";
$truck = "";
$mainfilename = "";
$mainpicdate = "";
}
$fp = fopen('file.csv', 'w');
foreach ($csvData as $fields) {
fputcsv($fp, $fields);
}
//print_r($csvData);
sqlsrv_free_stmt( $stmt);
sqlsrv_free_stmt( $picstmt);
sqlsrv_close( $conn);
?>
this gets the files out but I still have to merge the resultant CSV with the main "information" CSV.

Easy way to convert exec sp_executesql to a normal query?

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()

Resources