My requirement is to create a global temporary table and store data there which I would access later. I give a dynamic name to my global temporary table and getting error that
Invalid object name '##Tmp1_84'.
84 is #SPID
here is my script. please have a look and tell me what to rectify in code to get rid of runtime error Invalid object name '##Tmp1_84'
CREATE Proc USP_GetValuationValue
(
#Ticker VARCHAR(10),
#ClientCode VARCHAR(10),
#GroupName VARCHAR(10)
)
AS
DECLARE #SPID VARCHAR(MAX)
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SPID=CAST(##SPID AS VARCHAR)
SET #SQL = N'SELECT * INTO ##Tmp1_'+#SPID+' FROM (SELECT min(id) ID,f.ticker,f.ClientCode,f.GroupName,f.RecOrder,' + STUFF((SELECT N',' + #CRLF + N' ' +
N'MAX(CASE FieldName WHEN ' + QUOTENAME(FieldName,'''') + N' THEN FieldValue END) AS ' + QUOTENAME(FieldName)
FROM tblValuationSubGroup g
WHERE ticker=#Ticker AND ClientCode=#ClientCode AND GroupName=#GroupName
GROUP BY FieldName
ORDER BY MIN(FieldOrder)
FOR XML PATH(''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,10,N'') + #CRLF +
N'FROM (select * from tblValuationFieldValue' + #CRLF +
N'WHERE Ticker = '''+#Ticker+''' AND ClientCode = '''+#ClientCode+''' AND GroupName='''+#GroupName+''') f' + #CRLF +
N'GROUP BY f.ticker,f.ClientCode,f.GroupName,f.RecOrder) X';
--EXEC sys.sp_executesql #SQL
EXEC(#SQL)
EXEC('select * from ##Tmp1_'+#SPID+' ORDER BY Broker')
EXEC('DROP TABLE IF EXISTS ##Tmp1_'+#SPID)
George has mentioned why your attempt doesn't work in their answer, so I'm not going to touch on that.
I'm instead going to fix the problem, which I touch on in my comments. In truth, there is no need for a (global) temporary table, you just SELECT ... INTO it and then SELECT from it; you make no further transformations making it pointless. AS such you could just SELECT the data in the first place, no temporary table needed.
I also fix your injection issue; this is a fatal flaw. Dynamic SQL accepts parameters and you using them is a must; not using parameters and instead using injection opens you up to all sorts of errors and security issues.
I can't test this, but I suspect this will work. If not, use your best friend to debug and propagate fixes to the dynamic SQL on any bits I've missed (or provide code we can run above with an MRE).
CREATE Proc USP_GetValuationValue
(
#Ticker VARCHAR(10),
#ClientCode VARCHAR(10),
#GroupName VARCHAR(10)
)
AS
BEGIN
DECLARE #SPID VARCHAR(MAX), --Is this even used now?
#SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SPID=CAST(##SPID AS VARCHAR);
SET #SQL = N'SELECT * FROM (SELECT min(id) ID,f.ticker,f.ClientCode,f.GroupName,f.RecOrder,' + STUFF((SELECT N',' + #CRLF + N' ' +
N'MAX(CASE FieldName WHEN ' + QUOTENAME(FieldName,'''') + N' THEN FieldValue END) AS ' + QUOTENAME(FieldName)
FROM tblValuationSubGroup g
WHERE ticker=#Ticker AND ClientCode=#ClientCode AND GroupName=#GroupName
GROUP BY FieldName
ORDER BY MIN(FieldOrder)
FOR XML PATH(''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,10,N'') + #CRLF +
N'FROM (select * from tblValuationFieldValue' + #CRLF +
N'WHERE Ticker = #Ticker AND ClientCode = #ClientCode AND GroupName= #GroupName) f' + #CRLF +
N'GROUP BY f.ticker,f.ClientCode,f.GroupName,f.RecOrder) X' + #CRLF +
N'ORDER Y Broker;';
--PRINT #SQL; --YOur best friend
--EXEC sys.sp_executesql #SQL --Why did you comment this out? This is correct!
EXEC sys.sp_executesql #SQL, N'#Ticker varchar(10), #ClientCode varchar(10), #GroupName varchar(10)', #Ticker, #ClientCode, #GroupName
END;
Related
I have this dynamic query, that is union from all my databases (that is start with "Db") the same table ("Tbl_SameTable").
DECLARE #tableName nvarchar(256) = 'Tbl_SameTable'
DECLARE #sql nvarchar(max) = ''
SELECT #sql = #sql + CASE WHEN #sql <> '' THEN 'UNION ALL ' ELSE '' END
+ 'SELECT * FROM [' + dbs.name + ']..[' + #tableName + '] '
FROM sys.sysdatabases dbs
WHERE left(dbs.name,2) = 'Db'
EXEC(#sql)
I want to add two things to this query:
Add a column of database name
Assign the query result to a "temp table" or "table variable"
I do not know if this is important but, the "Tbl_SameTable" is a 5 column table (int, nvarchar, int,nvarchar,nvarchar)
This is untested, however, you'll want something like this. As this is pseudo SQL, you'll need to replace {Columns} with the actual names (not *) for it to work. For the CREATE TABLE you'll need to define the data type of said columns too.
DECLARE #SchemaName sysname = N'dbo',
#TableName sysname = N'YourTable';
CREATE TABLE #Temp (DatabaseName sysname,
{Columns});
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = STUFF((SELECT #CRLF + N'UNION ALL' + #CRLF +
N'SELECT N' + QUOTENAME(d.[name],'''') + N' AS DatabaseName, {Columns}' + #CRLF +
N'FROM ' + QUOTENAME(d.[name]) + N'.' + QUOTENAME(#SchemaName) + N'.' + QUOTENAME(#TableName)
FROM sys.databases d
WHERE d.[name] LIKE 'Db%'
ORDER BY database_id
FOR XML PATH(N''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,13, N'') + N';'
--PRINT #SQL; --Your Best Friend
INSERT INTO #Temp(DatabaseName, {Columns})
EXEC sys.sp_executesql #SQL;
And, of course, if it doesn't work your best friend will be there to help you out.
How about using sp_MSforeachdb and adding the results to another table?
DROP TABLE IF EXISTS #tmp
CREATE TABLE #tmp (col1 INT, col2 INT,...)
DECLARE #command varchar(1000)
SELECT #command = 'IF ''?'' LIKE ''Db%'' BEGIN USE ?
EXEC(''INSERT INTO #tmp (col1, col2,...) SELECT col1, col2,... from Tbl_SameTable'') END'
EXEC sp_MSforeachdb #command
SELECT * FROM #tmp
I have a stored procedure where I want to add a column name as a parameter.
The procedure updates some columns like this.
UPDATE Reservedele
SET Bemærkning = #Bemærkning
,Art = #Art
,Type = #Type
,Lev = #Lev
,#stockNumber = #stockCount
WHERE Varenummer = #Varenummer
I want to update a column named as stock1 as I pass in as #StockNumber, but all it does is setting #stockNumber equal to #Varenummer. (Which probably what I am telling it to do).
How can I do this?
I am new to SQL and I need to read up on dynamic SQL.
My full code looks like this, but the update statement is not doing anything.
ALTER PROCEDURE [dbo].[updateItemsInSqlNoImage]
#Bemærkning nvarchar(200),
#Varenummer nchar(20),
#Art nvarchar(50),
#Type nvarchar(50),
#Lev nvarchar(50),
#Bruger nvarchar(50),
#Dato nvarchar(50),
#Tid nvarchar(50),
#Ip nvarchar(50),
#stockCount int,
#stockNumber varchar(10)
as
BEGIN
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = N'UPDATE Reservedele' + #CRLF+
N',Bemærkning=#Bemærkning'+ #CRLF+
N',Art=#Art' + #CRLF+
N',Type=#Type' + #CRLF+
N',Lev=#Lev'+ #CRLF+
N' ,' + QUOTENAME(#stockNumber) + N' = #stockCount' + #CRLF +
N'WHERE Varenummer=#Varenummer;';
INSERT INTO Ændrede_dele(Varenummer,Dato,Tid,Bruger,Ip)
VALUES (#Varenummer,#Dato,#Tid,#Bruger,#stockNumber)
END
That isn't how SQL works, it isn't a scripting language, it's a query language. A variable cannot be used to replace a something that needs to be a literal value. #stockNumber is being seen as the parameter #stockNumber, not the value of the parameter, #stockNumber. As a result it makes it look like you have a SET operation that is part of an UPDATE but also trying to assign the value of variables/parameters (which you can't do).
You would need to use dynamic SQL to achieve this:
--All data types are guessed, replace appropriately
CREATE PROC dbo.YourProc #Bemærkning varchar(10),
#Art int,
#Type int,
#Lev decimal(6,2),
#stockNumber sysname, --apart from this one, this is correct
#stockCount int,
#Varenummer varchar(25) AS
BEGIN
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = N'UPDATE Reservedele' + #CRLF +
N'SET Bemærkning = #Bemærkning' + #CRLF +
N' ,Art = #Art' + #CRLF +
N' ,Type = #Type' + #CRLF +
N' ,Lev = #Lev' + #CRLF +
N' ,' + QUOTENAME(#stockNumber) + N' = #stockCount' + #CRLF +
N'WHERE Varenummer = #Varenummer;';
--PRINT #SQL; --Your debugging friend
--Again, all data types are guessed, replace appropriately
EXEC sys.sp_executesql #SQL, N'#Bemærkning varchar(10), #Art int, #Type int, #Lev decimal(6,2), #stockCount int, #Varenummer varchar(25)', #Bemærkning, #Art, #Type, #Lev, #stockCount, #Varenummer;
END;
I have built a stored procedure that aims to identify duplicates in a table and to display the duplicated rows in a meaningful order. It looks like this:
CREATE PROCEDURE [dbo].[spFindDuplicates]
#tableName nvarchar(255),
#field1 nvarchar(255),
#field2 nvarchar(255) = '1',
#field3 nvarchar(255) = '2',
#field4 nvarchar(255) = '3',
#field5 nvarchar(255) = '4'
AS
BEGIN
DECLARE #query AS nvarchar(MAX);
SET #query = '
SELECT *
FROM ' + #tableName + '
WHERE CAST(' + #field1 + ' AS nvarchar(255)) + CAST(' + #field2 + ' AS nvarchar(255)) + CAST(' + #field3 + ' AS nvarchar(255)) + CAST(' + #field4 + ' AS nvarchar(255)) + CAST(' + #field5 + ' AS nvarchar(255))
IN
(
SELECT CAST(' + #field1 + ' AS nvarchar(255)) + CAST(' + #field2 + ' AS nvarchar(255)) + CAST(' + #field3 + ' AS nvarchar(255)) + CAST(' + #field4 + ' AS nvarchar(255)) + CAST(' + #field5 + ' AS nvarchar(255))
FROM ' + #tableName + '
GROUP BY CAST(' + #field1 + ' AS nvarchar(255)) + CAST(' + #field2 + ' AS nvarchar(255)) + CAST(' + #field3 + ' AS nvarchar(255)) + CAST(' + #field4 + ' AS nvarchar(255)) + CAST(' + #field5 + ' AS nvarchar(255))
HAVING COUNT(*) > 1
)
ORDER BY ' + #field1 + ', ' + #field2 + ', ' + #field3 + ', ' + #field4 + ', ' + #field5
EXECUTE(#query);
END
GO
--Example:
EXEC spFindDuplicates #tableName = 'someRandomTable', #field1 = 'firstField', #field2 = 'secondField', #field3 = 'thirdField'
As you can see, I can use at most 5 different fields that I concatenate in order for me to get a key used to determine whether we have a duplicate or not. Please note that I use the CAST function to be able to concatenate fields with various datatypes (varchar, int, dates, etc.).
When I execute the above stored procedure with 5 different fields, it works fine. But I would like to be able to run it with a variable number of fields (from 1 to 5), which is why I provided default values for #field2 to #field5.
But when I execute it with the above example (3 fields provided), I get the following error message:
A column has been specified more than once in the order by list. Columns in the order by list must be unique.
QUESTION: How can I keep ordering the resulting table without getting an error?
BONUS QUESTION: If you find a dynamic way to use that stored procedure with any number of fields (4, 17, or whatever), that'd be even more useful to me.
Like I said in the comments, injection is a huge problem here, and you need to consider it. Saying "Let's consider I don't mind about injection" is naïve and you need to change that attitude. Always make your SQL safe; then there are no excuses and chances for your application being compromised.
As what you are after, I suspect this achieves the goal. There's no need for the subquery to scan your table with an IN here, you can make use of COUNT and the OVER clause within a CTE.
CREATE PROCEDURE [dbo].[FindDuplicates] --I've removed te sp prefix, as sp_ is reserved by MS
#tableName sysname,
#field1 sysname,
#field2 sysname = NULL,
#field3 sysname = NULL,
#field4 sysname = NULL,
#field5 sysname = NULL
AS BEGIN
DECLARE #query AS nvarchar(MAX);
SET #query = N'WITH CTE AS(' + NCHAR(10) +
N' SELECT *' + NCHAR(10) +
N' COUNT(*) OVER (PARTITION BY ' + STUFF(CONCAT(N',' + QUOTENAME(#field1),N',' + QUOTENAME(#field2),N',' + QUOTENAME(#field3),N',' + QUOTENAME(#field4),N',' + QUOTENAME(#field5)),1,1,N'') + N' AS RowCount' + NCHAR(10) +
N' FROM ' + QUOTENAME(#tableName) + N')' + NCHAR(10) +
N'SELECT *' + NCHAR(10) +
N'FROM CTE' + NCHAR(10) +
N'WHERE RowCount > 1' + NCHAR(10) +
N'ORDER BY ' + STUFF(CONCAT(N',' + QUOTENAME(#field1),N',' + QUOTENAME(#field2),N',' + QUOTENAME(#field3),N',' + QUOTENAME(#field4),N',' + QUOTENAME(#field5)),1,1,N'') + N';';
PRINT #query;
--EXEC sys.sp_executesql #query; --Uncomment to rrun the actual query
END
GO
For the command you gave us EXEC dbo.FindDuplicates #tableName = 'someRandomTable', #field1 = 'firstField', #field2 = 'secondField', #field3 = 'thirdField';, this returns the SQL:
WITH CTE AS(
SELECT *
COUNT(*) OVER (PARTITION BY [firstField],[secondField],[thirdField] AS RowCount
FROM [someRandomTable])
SELECT *
FROM CTE
WHERE RowCount > 1
ORDER BY [firstField],[secondField],[thirdField];
Which, I believe gives you the behaviour you are after.
Edited the code to check if the column list exists on the sys.columns there by making sure we get only the columns which are appropriate.
CREATE FUNCTION dbo.fn_SplitString
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
ALTER PROCEDURE [dbo].[spFindDuplicates]
#tableName nvarchar(255),
#columnlist nvarchar(max)
AS
BEGIN
DECLARE #query AS nvarchar(MAX);
SET #columnlist = (SELECT STUFF((SELECT ','+'['+[name]+']'
FROM SYS.columns
WHERE object_id = object_id(#tableName)
AND [Name] IN
(
SELECT Item
FROM dbo.fn_SplitString(#columnlist,',')
)
FOR XML PATH('')
)
,1,1,''))
PRINT #columnlist
SET #query = 'SELECT * FROM (SELECT '+CAST(#columnlist AS NVARCHAR(MAX))+'
FROM '+CAST(#tableName AS nvarchar(MAX))+'
GROUP BY '+CAST(#columnlist AS NVARCHAR(MAX))+'
HAVING COUNT(*) > 1)Res1
ORDER BY '+#columnlist
EXEC SP_EXECUTESQL #query;
END
GO
This is a progression from the question asked here: How to SELECT and UNION from a group of Tables in the schema in SQL Server 2008 R2
I would like to do very much the same thing and the answer given by MarkD works perfectly for the database I am currently working with. Although admittedly I'd like to understand exactly how. How does the query below build the union query from the list of tables returned by the information_schema?
DECLARE #Select_Clause varchar(600) = N'SELECT [Patient_Number] AS [ID number]
,[Attendance Date] AS [Date Seen]
,[Attendance_Type] AS [New/Follow up]
,[Episode Type] AS [Patient Type]
,[Local Authority District]
,Postcode, N''Shaw'' AS Clinic '
,#Where_Clause varchar(100) = N' WHERE [EPISODE TYPE] LIKE N''HIV'''
,#Union_Clause varchar(100) = N' UNION ALL '
,#Query nvarchar(max) = N''
,#RawDataBase varchar(50) = N'BHT_1819_RawData'
,#Schema varchar(50) = N'HIVGUM'
,#Table_Count tinyint;
DECLARE #Table_Count_def nvarchar(100) = N'#TableSchema varchar(50)
,#Table_CountOUT tinyint OUTPUT'
,#Start_Position int = LEN(REPLACE(#Select_Clause, N' ', N'-'))
,#Length int;
SET #Query = N'SELECT #Table_CountOUT = COUNT(*) FROM ' + #RawDataBase +
N'.INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA LIKE #TableSchema';
EXEC sp_executesql #query, #Table_Count_def, #TableSchema=#Schema,
#Table_CountOUT=#Table_Count OUTPUT;
SET #Query = N'';
IF #Table_Count > 0
Begin
IF OBJECT_ID(N'dbo.HIV_Cumulative', N'U') is not null
DROP TABLE dbo.HIV_Cumulative;
SELECT #Query = #Query + #Select_Clause + N' FROM ' + #RawDataBase +
N'.HIVGUM.' + TABLE_NAME + #Where_Clause + #Union_Clause
FROM BHT_1819_RawData.INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA LIKE #Schema;
SET #Length = LEN(REPLACE(#query, N' ', N'-')) - #Start_Position -
LEN(REPLACE(#Where_Clause + #Union_Clause, N' ', N'-'));
SELECT #Query = SUBSTRING(#QUERY , #Start_Position+1, #Length)
SET #Query = #Select_Clause + N' INTO BHT_SLR..HIV_Cumulative ' + #QUERY
+ #Where_Clause;
EXEC sp_executesql #Query
End
ELSE
PRINT N'No tables present in database ' + #RawDataBase + N' for Schema ' +
#Schema + N'. You must import source data first.';
The added complication is that I am querying the tables on a separate DB - currently BHT_1819_RawData - so have hard coded the database where it queries the information_schema. What I would really like to do is to specify the separate database using a variable. So that it can be reconfigured to extract from BHT_1920_RawData. I am fairly familiar with exec and sp_executesql, but have only occasionally used output parameters so am not sure what is required here. The attempts that I have made haven't worked. Once I have got this right, I will need to create several other similar scripts that work on the same principle.
Once I realised what needed to happen, I went through some trial and error and came up with a solution:
SET #ParmDef = N'#QueryOut nvarchar(2500) OUTPUT';
SET #sql_string = N'SELECT #QueryOut = #QueryOut + N'''
+ #Select_Clause + ' FROM '
+ #RawDataBase
+ N'.[' + #Schema + N'].'' + TABLE_NAME + N'' '
+ #Where_Clause
+ #Union_Clause
+ N''' FROM '
+ #RawDataBase
+ N'.INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA LIKE '''
+ #Schema
+ N''' AND TABLE_NAME NOT LIKE N''%_YTD%''';
EXEC sp_executesql #sql_string, #ParmDef, #QueryOut=#Query OUTPUT;
SET #Length = LEN(REPLACE(#query, N' ', N'-')) - #Start_Position -
LEN(REPLACE(#Where_Clause + #Union_Clause, N' ', N'-'));
SELECT #Query = SUBSTRING(#QUERY , #Start_Position, #Length+1);
SET #Query = REPLACE(#Select_Clause, N'''''', '''') + N' INTO ' + #New_Table + N' ' +
#QUERY + REPLACE(#Where_Clause, N'''''', '''');
EXEC sp_executesql #Query;
I have an SQL exec in stored procedure like this
DECLARE
#SQLQuery NVARCHAR(max),
#table NVARCHAR(max)
SET #table = + 'log_'+ #idCampaign
SET #SQLQuery =
'DECLARE #ID NVARCHAR(200);
INSERT INTO ' + #table + ' ( campaignRowID, idCampaign, idUser )
VALUES( ''' + #campaignRowID + ''', ''' + #idCampaign + ''', ''' + #idUser + ''' )
SET #ID = SCOPE_IDENTITY();
exec InsertData '''+ convert(nvarchar, #idCampaign) + ''',''' + convert(nvarchar, #ID) + ''''
exec (#SQLQuery)
i can get the #ID but can't use it for another execute SP btw #ID is nvarchar,
How i can get the return ID and use it for another execute sp?
I'm guessing a bit because the question is lacking detail, but I think your issue may be with how you're attempting to use the #ID variable.
Because #ID is declared inside the scope of the dynamic SQL statement, it's only accessible inside that scope.
The problem might be that on this line, you're attempting to use #ID as if it were declared outside the scope of the dynamic SQL:
exec InsertData '''+ convert(nvarchar, #idCampaign) + ''',''' + convert(nvarchar, #ID) + ''''
Instead, reference #ID inside the scope of the statement:
exec InsertData '''+ convert(nvarchar, #idCampaign) + ''',#ID'
EDIT
I created the following to test the code; it appears to work for me
CREATE TABLE log_100 (id INT IDENTITY, campaignRowId VARCHAR(100), idCampaign VARCHAR(100), idUser VARCHAR(100))
GO
CREATE PROC InsertData
#idCampaign VARCHAR(100),
#id NVARCHAR(200)
AS
SELECT #idCampaign ic, #id id
GO
declare #idCampaign varchar(100) = '100'
declare #campaignRowID varchar(100) = '200'
declare #idUser varchar(100) = '200'
DECLARE
#SQLQuery NVARCHAR(max),
#table NVARCHAR(max)
SET #table = + 'log_'+ #idCampaign
SET #SQLQuery =
'DECLARE #ID NVARCHAR(200);
INSERT INTO ' + #table + ' ( campaignRowID, idCampaign, idUser )
VALUES( ''' + #campaignRowID + ''', ''' + #idCampaign + ''', ''' + #idUser + ''' )
SET #ID = SCOPE_IDENTITY();
exec InsertData '''+ convert(nvarchar, #idCampaign) + ''',#ID'
exec (#SQLQuery)