I have this dynamic SQL in a stored procedure:
Declare #template nvarchar(max)
Declare #publishdetails nvarchar(max)
set #publishdetails= ',B.[PublishedBy]=suser_name(),
B.[PublishedDate]=GETDATE() '
set #template='if NOT EXISTS(select * from ' +#DestinationDB+ '.[CLs] where id='+ str(#slid)+')
insert into ' +#DestinationDB+ '.CLs (id,slid,slversion) VALUES ( '+ str(#id)+','+ str(#slid)+','+str(#slversion)+')
update B set
B.[Clientid]=A.clientid,
--.........
B.[CreatedDate] = A.CreatedDate,
B.[ModifiedDate] = A.ModifiedDate,
B.[CreatedBy] = A.CreatedBy,
B.[ModifiedBy] = A.ModifiedBy '+#publishdetails+ --Added publishdetails
'FROM ' + #SourceDB + '.[CLs] as A, '+ #DestinationDB+ '.[CLs] as B
where A.slversion = '+ str(#slversion)+' and A.id='+str(#slid) + 'B.slversion = '+ str(#slversion)+' and B.id='+str(#slid)
print 'template is: ' + #template
exec sp_Executesql #template
When exec sp_Executesql #template is executing, it fails. Because #template is > 4000 chars and is truncated. How can I split it in chunks and execute it the correct way?
You don't need to split the text into parts. You do need to make sure that truncation doesn't occur whilst you're concatenating strings:
If the result of the concatenation of strings exceeds the limit of 8,000 bytes, the result is truncated. However, if at least one of the strings concatenated is a large value type, truncation does not occur.
So, make sure that the first concatenation is working with a large value type (and thus produces a large value type as its result) and every subsequent concatenation should be saved from truncation:
set #template=CONVERT(nvarchar(max),'if NOT EXISTS(select * from ' ) + #DestinationDB + ...
(In this way, you don't have to insert conversions everywhere)
This generates an error:
declare #t nvarchar(max)
set #t = 'select LEN(''' + REPLICATE('A',3000) + REPLICATE('B',3000) + REPLICATE('C',3000) + ''')'
exec sp_executesql #t
And this produces the result 9000:
declare #t nvarchar(max)
set #t = CONVERT(nvarchar(max),'select LEN(''') + REPLICATE('A',3000) + REPLICATE('B',3000) + REPLICATE('C',3000) + ''')'
exec sp_executesql #t
I suggest to use this approach:
Declare #template nvarchar(max) = N''
set #template = #template +N'.... -- Or SELECT instead of SET
Update#1
I run this simple query on my test DB:
DECLARE #query nvarchar(max) = N'',
#i int = 1
WHILE 1000 > #i
BEGIN
SET #query = #query + N'SELECT ##version;'
SET #i = #i+1
END
SELECT LEN (#query)
EXEC sp_executesql #query
I got batch with length of 16983 characters. And execution goes well - no truncation. I guess the problem is inside #SourceDB + '.[CLs] and #DestinationDB+ '.[CLs] tables. Somewhere there you got data truncation.
Try to PRINT your query and run it manually.
Related
I am trying to call an sql function accepting a nullable parameter - from a dynamic SQL statement.
Creating the dynamic statement is difficult because when the parameter value is 'NULL' the concatentation causes the whole statement to be empty. I have the following:
SET dynamicQuery =
'select * from [qlik].udf_getStatistic( ''' + #myParameter + ''' )'
The sample above is inside a stored procedure to which #myParameter is passed. It may be null, or a string value. Clearly, when it is a string it needs to be enclosed in quotes, but when it is null it must not be enclosed in quotes. As follows:
select * from [qlik].udf_getStatistic( 'Heights' )
select * from [qlik].udf_getStatistic( NULL )
The question is equally applicable to calling a stored procedure accepting a nullable parameter from dynamic SQL.
The examples are from SQL Server.
Just escape the NULL value with an explicit literal NULL, making sure that the quotes are only included when the value is not NULL.
DECLARE #myParameter VARCHAR(10) = 'ABC'
DECLARE #dynamicQuery VARCHAR(MAX)
SET #dynamicQuery =
'select * from [qlik].udf_getStatistic(' + ISNULL('''' + #myParameter + '''', 'NULL') + ')'
SELECT #dynamicQuery -- select * from [qlik].udf_getStatistic('ABC')
SET #myParameter = NULL
SET #dynamicQuery =
'select * from [qlik].udf_getStatistic(' + ISNULL('''' + #myParameter + '''', 'NULL') + ')'
SELECT #dynamicQuery -- select * from [qlik].udf_getStatistic(NULL)
You might want to escape additional single quotes that might be on your variable, replacing them with double single quotes, so it doesn't break your dynamic build.
The answer is actually different between stored procedures and functions.
From Books On Line or whatever they call it this month (Scroll down a ways):
When a parameter of the function has a default value, the keyword DEFAULT must be specified when the function is called to retrieve the default value. This behavior is different from using parameters with default values in stored procedures in which omitting the parameter also implies the default value. However, the DEFAULT keyword is not required when invoking a scalar function by using the EXECUTE statement.
So for a proc, when you want to pass a NULL parameter, you can just not pass it. For a function, though, you have to tell it to use the DEFAULT value explicitly. Either way, you do not pass it an explicit NULL. Luckily for your dynamic SQL, though, the explicit DEFAULT also works with a stored procedure. In both cases, in order to make sure that the parameters you are passing get assigned correctly, you want to use explicit parameter names in your call.
Let's use this function definition:
CREATE FUNCTION (or procedure) [qlik].udf_getStatistic (
#param1 integer = 0,
#param2 varchar(100) = 'foo'
) AS ...
Both parameters are optional. Since this is a function, this call will throw an insufficient number of parameters error:
select * from [qlik].udf_getStatistic( 'Heights' );
If it were a procedure call, it would throw a cannot convert value 'Heights' to data type integer because it will apply the only parameter value passed to the first parameter it encounters, which is expecting an integer. In both cases, you get what you want this way:
select * from [qlik].udf_getStatistic( #param1 = DEFAULT, #param2 = 'Heights' );
Which brings us to your dynamic SQL. Add your parameter name(s) to the static text, then use COALESCE (or CASE if you like) to decide whether to pass an explicit value, or the DEFAULT call.
DECLARE #myParameter1 VARCHAR(100) = 'foo',
#myParameter2 INTEGER,
#SQL NVARCHAR(MAX);
SET #SQL =
'select
*
from [qlik].udf_getStatistic(
#param1 = ''' + COALESCE(#myParameter1, 'DEFAULT') + ''',
#param2 = ' + COALESCE(CAST(#myParameter2 AS VARCHAR(30)),'DEFAULT') + ' );';
SELECT #SQL;
Result:
select * from [qlik].udf_getStatistic( #param1 = 'foo', #param2 = DEFAULT );
From my understanding, I try this on SQL Server 2012,
CREATE PROCEDURE ToNullProc
(#i VARCHAR(20))
AS
BEGIN
PRINT 'you entered ' + #i
END
CREATE FUNCTION ToNullFun
(#i VARCHAR(20))
RETURNS #table TABLE (i VARCHAR(20))
AS
BEGIN
INSERT INTO #table
SELECT ('You entered ' + #i) a
RETURN
END
DECLARE #j VARCHAR(20) = 'Hi',
#QueryFun NVARCHAR(50) = N'',
#QueryProd NVARCHAR(50) = N''
IF #j IS NOT NULL
BEGIN
SET #QueryFun = N'select * from ToNullFun ('''+#j+''')'
SET #QueryProd = N'exec ToNullProc '''+#j+''''
END
ELSE BEGIN
SET #QueryFun = N'select * from ToNullFun ('+#j+')'
SET #QueryProd = N'exec ToNullProc '+#j+''
END
PRINT #queryfun
PRINT #queryprod
EXEC sp_executesql #queryfun
EXEC sp_executesql #queryprod
update for dynamic procedure and dynamic function :
create table #temp (Num int identity (1,1), NullVal int)
insert into #temp (NullVal) values (1),(null),(3)
alter proc ToNullProc (
#Operator varchar (max), #NullVal varchar (max)
) as
begin
declare #Query nvarchar (max) = N'select * from #temp where NullVal ' +
#Operator + #NullVal
-- print #query + ' ToNullProc print '
exec sp_executesql #query -- Here we run the select query from Proc
end
create function ToNullFun (
#Operator varchar (max), #NullVal varchar (max)
)
returns nvarchar (max)
as
begin
declare #Query nvarchar (max)
set #Query = N'select * from #temp where NullVal ' + #Operator + #NullVal
/*
I try to into to Table variable by using ITVF,
'insert into #table exec sp_executesql #query'.
But this type of insert is not allowed in ITVF.
*/
return #query
end
declare #NullVal varchar (max) = '1'
, #QueryFun nvarchar (max) = N''
, #QueryProd nvarchar (max) = N''
declare #FunResultTalbe table (
Query nvarchar (100)
) /* To store the result Funtion */
if #NullVal is not null
begin
set #QueryFun = N'select dbo.ToNullFun ('' = '','''+#NullVal+''')'
set #QueryProd = N'exec ToNullProc '' = '','''+#NullVal+''''
end
else begin
set #QueryFun = N'select dbo.ToNullFun ('' is null '','''')'
set #QueryProd = N'exec ToNullProc '' is null '','''''
end
print #queryfun + ' At start'
print #queryprod + ' At start'
exec sp_executesql #queryprod -- It calls Proc
insert into #FunResultTalbe
exec sp_executesql #queryfun -- It calls the Function and insert the query into the table.
set #QueryFun = (select top 1 * from #FunResultTalbe) -- Here we get the query from the table.
print #queryfun
exec sp_executesql #queryfun -- Here we run the select query. Which is dynamic
Result sets
-- Result of Procedure
Num NullVal
1 1
-- Result of Function
Num NullVal
1 1
Let me know, what did you got.
My intent is to monitor the cdc process through a stored proc managed by a sql agent job. I am looking to see if I have captured any data since the last time the job executed. The IntervalHours represents how many hours since the last batch ran. Here is the code snippet that is the setup for the notify/not-notify decision:
declare #FromLSN binary(10)
declare #ToLSN binary(10)
declare #BeginTime datetime
declare #EndTime datetime
declare #hasCurrentChanges int
declare #SendLowFloorMessage bit
select #BeginTime = dateadd(hh,-#IntervalHours,getdate())
SET #EndTime = GETDATE();
-- Map the time interval to a change data capture query range.
SET #FromLSN = [myInstance].sys.fn_cdc_map_time_to_lsn('smallest greater
than or equal', #BeginTime);
SET #ToLSN = [myInstance].sys.fn_cdc_map_time_to_lsn('largest less than
or equal', #EndTime);
-- Return the count of the net changes occurring within the query window.
SELECT #hasCurrentChanges = count(*) FROM
[myInstance].cdc.fn_cdc_get_net_changes_dbo_CRM_POSTran(#FromLSN, #ToLSN,
'all');
-- Here is the decision --
IF isnull(#hasCurrentChanges,0) <= #LowFloor
begin
set #SendLowFloorMessage = 1;
end
-- Here is the notification. This is where I would need the #qry to dub the value of #FromLSN and #ToLSN into the text of the query so it can execute. What do I need to cast the value to in order for this to succeed?
DECLARE #bdy nvarchar(1000);
DECLARE #sbj nvarchar(50)
DECLARE #MailRecipients VARCHAR(50)
DECLARE #qry nvarchar(max)
SET #MailRecipients = 'paula.ditallo#gmail.com'
--Send email with results of long-running jobs
EXEC msdb.dbo.sp_send_dbmail
#profile_name = #mailProfile
,#recipients = #MailRecipients
,#query = #qry
,#execute_query_database = 'InternalResource'
,#body = #bdy
,#subject = #sbj
,#attach_query_result_as_file = 1;
I am assuming that you have a good handle on sp_send_dbmail and just do not know how to create a dynamic TSQL for the #query using the varbinary data types. Here is one way to convert the data using the XML DOM.
Here is a reference to my website showing how to send an email with an attachment for 2008 R2.
http://craftydba.com/?p=1025.
This is almost the same for 2012 - Books On Line.
http://technet.microsoft.com/en-us/library/ms190307.aspx.
The sample code below takes two hex number and converts them into a call to the cdc.fn_cdc_get_net_changes_.
http://msdn.microsoft.com/en-us/library/bb522511.aspx
-- Sample data
DECLARE #from_lsn VARBINARY(10);
SET #from_lsn = 0x5BAA61E4C9B93F3F0682;
DECLARE #to_lsn VARBINARY(10);
SET #to_lsn = 0x5BAA61E4C9B93F3F0782;
-- Display the results
DECLARE #tsql VARCHAR(MAX);
SELECT #tsql =
'SELECT * FROM cdc.fn_cdc_get_net_changes_HR_Department(' +
char(39) + '0x' + CAST('' AS XML).value('xs:hexBinary(sql:variable("#from_lsn"))', 'VARCHAR(20)') + char(39) + ',' +
char(39) + '0x' + CAST('' AS XML).value('xs:hexBinary(sql:variable("#to_lsn"))', 'VARCHAR(20)') + char(39) + + ',' +
char(39) + 'all' + char(39) +');';
-- Show the data
PRINT #tsql
Here is the output from the snippet.
SELECT *
FROM cdc.fn_cdc_get_net_changes_HR_Department
('0x5BAA61E4C9B93F3F0682','0x5BAA61E4C9B93F3F0782','all');
I need to create about 300 columns for a table and I don't want to to it manually.
How can I do this?
I want to have columns like
Bigint1..to..Bigint60
Text1 ..to..Text60
and so on.
IF (NOT EXISTS (SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = 'mytbl'))
begin
create table OBJ_AttributeValues(
ObjectID numeric(18,0) not null
);
end
else
begin
DECLARE #A INT
set #A = 1;
WHILE(#A <=60)
BEGIN
alter table OBJ_AttributeValues
add ...............................
set #A = #A+1;
END
end
What should I write instead of "..."?
You will need to use dynamic SQL for that, something like
DECLARE #SSQL VARCHAR(1000)
DECLARE #A INT
set #A = 1;
WHILE(#A <=60)
BEGIN
SET #SSQL = 'alter table OBJ_AttributeValues add Bigint' + CAST(#A as varchar) + ' bigint'
EXEC (#SSQL)
set #A = #A+1;
END
This isn't really a good idea, you should take the time to write the sql or just copy-paste the columns from Excel or something like that. You also shouldn't be using the TEXT data type, is deprecated and filled with restriction (use VARCHAR(MAX) instead if you need). That said, here is a way using dynamic SQL:
DECLARE #BigintCols NVARCHAR(MAX), #TextCols NVARCHAR(MAX)
DECLARE #Query NVARCHAR(MAX)
SET #BigintCols = ''
SET #TextCols = ''
SELECT #BigintCols = #BigintCols + N'Bigint' + CAST(number AS NVARCHAR(2)) + N' BIGINT,',
#TextCols = #TextCols + N'Text' + CAST(number AS NVARCHAR(2)) + N' TEXT,'
FROM master..spt_values
WHERE type = 'P'
AND number BETWEEN 1 AND 60
ORDER BY number
SET #Query = '
CREATE TABLE OBJ_AttributeValues(ObjectID numeric(18,0) not null,'+#BigintCols+
LEFT(#TextCols,LEN(#TextCols)-1)+')'
EXECUTE sp_executesql #Query
Oh, you should probably read about dynamic sql first.
i am trying to execute a sql query string which has thousands of characters in t-sql. data type using to hold this query is nvarchar(max). but using this i can hold only 67594 characters. but my query has more characters than this.
does anybody has any idea why nvarchar(max) is holding up to 67594 characters only? the nvarchar(max) should hold up to 2GB data. isn't it?
the procedure which i am running is:
create procedure test
(
#snapshotid1 nvarchar(10),
#snapshotid2 nvarchar(10),
#version1 nvarchar(100),
#version2 nvarchar(100)
) AS DECLARE
#sql nvarchar(max),
#whereClause1 nvarchar(500),
#whereClause2 nvarchar(500),
#fromClause1 nvarchar(100),
#fromClause2 nvarchar(100)
BEGIN
set #sql = '';
set #sql = #sql + N'
select v1.v1_brand, version1total, version2total, version1total - version2total as varience from (
select "C - Brand" as v1_brand,
case ' + #period + ' when ''Jan'' then sum(Period1InvoicedAmount)
when ''Feb'' then
.
.
.
END
regards
Subash Amara
Can't reproduce. But! don't think a proper TSQL concatenation can't handle large string. You probably have a problem somewhere in your script. It depends what error are you getting.
Here's a little proof of concept you can run.
First write this script:
DECLARE #sql nvarchar(max) = ''
SET #sql = N'SELECT fld = newid()'
-- (PLACEHOLDER) --
SELECT DATALENGTH(#sql)
EXEC(#sql)
Then run this script and copy the rows...
SELECT TOP 2000
q = 'SET #sql = #sql + N''UNION ALL SELECT fld = newid()'''
FROM sys.syscolumns t1, sys.syscolumns t2
...and paste it instead of -- (PLACEHOLDER) --, so you have...
DECLARE #sql nvarchar(max) = ''
SET #sql = N'SELECT fld = newid()'
SET #sql = #sql + N'UNION ALL SELECT fld = newid()'
SET #sql = #sql + N'UNION ALL SELECT fld = newid()'
...(totals 2000 lines)...
SET #sql = #sql + N'UNION ALL SELECT fld = newid()'
SELECT DATALENGTH(#sql)
EXEC(#sql)
When you execute this, you will see that data length is 120040 (well above 67594) and it outputs 2001 rows, as it should.
However, if you try to PRINT or SELECT your dynamically created string in SSMS, like:
PRINT #sql
SELECT #sql
...you will get truncated results.
I'm trying to execute an inline SQL statement within a stored procedure. I'm working with SQL Server 2008.
The problem is that I can't execute the first inline statement (with WHERE clause). It crashes because the string within EXEC(...) is dynamically created and all concatenated variables must be of type varchar.
Error that appears when calling procedure:
An expression of non-boolean type specified in a context where a
condition is expected, near 'ORDER'.
The procedure looks like:
CREATE PROCEDURE loadMyRows
#table_name nvarchar(50),
#bounding_box varchar(8000)
AS
BEGIN
-- *********************************** COMMENT *********************************
-- ** This two code lines are correct and will return true (1) or false (0), **
-- ** but they doesn't work within inline EXEC(...) **
--DECLARE #bb geometry = geometry::STGeomFromText(#bounding_box, 4326);
--select TOP(5) wkt.STWithin(#bb) AS 'bool'
-- *********************************** COMMENT *********************************
IF #bounding_box <> ''
BEGIN
DECLARE #bb geometry = geometry::STGeomFromText(#bounding_box, 4326);
EXEC(
'SELECT TOP (' + #row_limit + ') * ' +
'FROM ' + #real_table_name + ' ' +
'WHERE wkt.STWithin('+#bb+') ' + -- <-- doesn't work :-(
-- 'WHERE wkt.STWithin(geometry::STGeomFromText('''+#bounding_box+''', 4326)) ' +
-- ^^ doesn't work, too :-(
'ORDER BY id ASC '
);
END
ELSE
BEGIN
EXEC(
'SELECT TOP (' + #row_limit + ') * ' +
'FROM ' + #real_table_name + ' ' +
'ORDER BY id ASC'
);
END
END
I've found a working solution for this problem. The way the MSDN showed me was http://msdn.microsoft.com/en-US/library/ms175170.aspx. There's written:
[...] the string is executed as its own self-contained batch.
That let me know, if I want to execute a dynamic statement with a table variable as string, it's the same as I would execute the query without the EXECUTE command, like:
SELECT TOP(#row_limit) *
FROM #real_table_name
WHERE ...
ORDER BY id ASC;
And this would probably not work for the table name.
So, if I write instead:
DECLARE #sql_statement nvarchar(MAX) = 'SELECT TOP(#limit) *
FROM ' + #real_table_name + '
ORDER BY id ASC';
-- declaration of parameters for above sql
DECLARE #sql_param_def nvarchar(MAX) = '#limit int';
EXECUTE sp_executesql #sql_statement, #sql_param_def, #limit = #row_limit;
Then, this would work. This is because I define the #sql_statement simply as a concatenated string which will just resolve the dynamic table name at runtime to a string with the name of the real existing table. The #limit parameter is untouched and is still a parameter.
If we then execute the batch we only must pass a value for the #limit parameter and it works!
For the geometry parameter it works in the same way:
DECLARE #bb geometry = geometry::STGeomFromText(#bounding_box, 4326);
SET #sql_statement = 'SELECT TOP(#limit) *
FROM ' + #real_table_name + '
WHERE wkt.STWithin(#geobb) = 1
ORDER BY id ASC';
-- NOTE: This ' = 1' must be set to avoid my above described error (STWithin doesn't return a BOOLEAN!!)
-- declaration of parameters for above sql
SET #sql_param_def = '#limit int, #geobb geometry';
EXECUTE sp_executesql #sql_statement, #sql_param_def, #limit = #row_limit, #geobb = #bb;
Hope this was clear ;-)
create proc usp_insert_Proc_Into_temp
#tempTable nvarchar(10) output
as
begin
set #tempTable = '##temp'
declare #query nvarchar(200)
--Select statement
set #query = 'select 1 as A,2 as B, 3 as C into'+ ' '+#tempTable+''
exec(#query)
end
go
declare #tempTable nvarchar(10)
exec usp_insert_Proc_Into_temp #tempTable output
exec('select * from' + ' '+ #tempTable+'')
exec ('drop table'+ ' '+#tempTable+'')