I have a SQL query like this :
ALTER PROCEDURE [dbo].[sp_dynamic_column_list](
#tahun varchar(4),
#bulan varchar(2),
#pks varchar(3))
AS
BEGIN
SET NOCOUNT ON;
DECLARE #totalrow int
DECLARE #inc int = 1
DECLARE #dynamictable NVARCHAR(MAX)
CREATE TABLE #temp
(
tanggal datetime,
)
-- query cari column dulu baru alter table temp diatas
SET #totalrow = dbo.fn_count_row_penerimaan(2014,11,40)
WHILE (#inc <= #totalrow)
BEGIN
ALTER TABLE #temp ADD #inc FLOAT
SET #inc = #inc + 1
END
INSERT INTO #temp
EXEC sp_get_list_penerimaan_pks2 #tahun, #bulan, #pks
SELECT * FROM #temp
DROP TABLE #temp
END
I got error like this:
[Err] 42000 - [SQL Server]Incorrect syntax near '#inc'.
I'm new to SQL Server and like to know the solution for this problem
Thanks in advance
ALTER TABLE my_table ADD #column INT
You need to use Execute statement as mentioned in link.
WHILE #inc <= #totalrow
BEGIN
exec ('ALTER table #temp add '+#inc+' FLOAT set '+#inc+' = '+ #inc+'+1')
END
In a SQL statment variable value cannot be provided as a column name, to achieve this you have to use a dynamic SQL query like the Following:
WHILE (#inc <= #totalrow)
BEGIN
Declare #strquery as varchar(4000)
SET #strquery = 'ALTER TABLE #temp ADD [' + #inc + '] FLOAT'
EXECUTE sp_executesql #strquery -- if #inc =1 then #strQuery : ALTER TABLE #temp ADD [1] FLOAT
SET #inc = #inc + 1
END
You can read more about Dynamic SQL queries in this Article
Related
CREATE PROC AllRowsAndagain
#table1 NVARCHAR(128)
AS
BEGIN
select count(*) FROM #table1
END;
I am getting this error -
Msg 1087, Level 16, State 1, Procedure AllRowsAndagain11, Line 9 [Batch Start Line 0]
Must declare the table variable "#table1".
I want to pass the tablename as a parameter here
You can't use a variable to replace the name of a table. You would need to use dynamic code for that. Fortunately, there's a more efficient way to retrieve the row count from any table.
CREATE PROC AllRowsAndagain
(
#table1 NVARCHAR(128)
)
AS
BEGIN
SELECT SUM(row_count) AS row_count
FROM sys.dm_db_partition_stats
WHERE OBJECT_NAME( object_id) = #table1
AND index_id IN (0,1);
END;
Like many have hinted in the comments you will need to use dynamic SQL. Dynamic SQL however can be unsafe and you should try and avoid using it.
To answer your question you would need to use something like this:
CREATE PROCEDURE AllRowsAndagain
#table1 NVARCHAR(128)
AS
BEGIN
DECLARE #SafeTableName AS NVARCHAR(128)
SELECT #SafeTableName = QUOTENAME( TABLE_NAME )
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = #table1
DECLARE #sql AS NVARCHAR(MAX) = 'select count(*) from ' + #SafeTableName + ';'
EXEC(#SQL)
END
Checking the table against INFORMATION_SCHEMA.TABLES makes your dynamic sql a lot safer. As it will only execute the dynamic statement if a table has been passed as the variable and not some malicious statement.
Your problem is that you are trying to count the number of rows in a string variable.
Seems a bit OTT but here you are.......
CREATE PROC AllRowsAndagain
#table1 NVARCHAR(128)
AS
BEGIN
--DECLARE A VARIABLE TO HOLD THE STATEMENT
DECLARE #Statement NVARCHAR(1024);
--BUILD THGE STATMENT USING THE TABLE NAME YOU PASS IN
SET #Statement = CONCAT('SELECT COUNT(*) FROM ', #table1, ';');
--ONLY RUN FIRST COMMAND
SET #Statement = SUBSTRING(#Statement, 1, CHARINDEX(';', #Statement));
--RUN THE SQL
exec sp_executesql #Statement;
END;
I am trying to log data changes in MS SQL with trigger. I want to create a new History table in every month. After I found the answer how to change table name Dynamically I can't access the DELETED and INSERTED tables anymore. It says invalid object name.
ALTER TRIGGER [dbo].[teszttablatrigger] ON [teszt].[dbo].[teszt] FOR DELETE, INSERT, UPDATE AS
declare #hist nvarchar(40)
set #hist='teszthistory_' + CAST(YEAR(getdate()) as NCHAR(4))+ '_' + (case when Month(GETDATE())<10 then '0' + CAST (Month(GETDATE()) as NCHAR(1))
when Month(GETDATE())>=10 then CAST (Month(GETDATE()) as NCHAR(2)) end)
declare #DynamicSql1 nvarchar(2000)
declare #DynamicSql2 nvarchar(2000)
set #DynamicSql1 = N'IF NOT EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N''[History][dbo].[#hist]'')
AND OBJECTPROPERTY(id, N''IsUserTable'') = 1)
CREATE TABLE [History].[dbo].[#hist] ( kulcs int, szoveg varchar(40), modtip varchar(40), datum datetime default getdate())'
Exec sp_executesql #DynamicSql1, N'#hist nvarchar(40)', #hist=#hist
set #DynamicSql2 = N'INSERT INTO [History].[dbo].[#hist] (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''delete''
FROM DELETED
INSERT INTO [History].[dbo].[#hist] (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''insert''
FROM INSERTED'
Exec sp_executesql #DynamicSql2, N'#hist nvarchar(40)', #hist=#hist
Thanks for the answers in advance.
Dynamic sql is executed in his own scope, so you can't acces inserted/deleted objects.
You could write a SQLCLR trigger in C# look this example SQLCLR Trigger
but I think the easiest way is to use a temp table to write changes to, so the dynamic part is fixed.
Take a look:
DROP TRIGGER [test_history]
GO
CREATE TRIGGER [test_history] ON [test_table]
FOR DELETE, INSERT, UPDATE
AS
BEGIN
declare #date datetime = getdate()
declare #guid uniqueidentifier = newid()
declare #hist nvarchar(40)= 'test_history_' + CAST(YEAR(#date ) as VARCHAR(4))+ '_' + right('0' + CAST(Month(#date) as VARCHAR(2)), 2)
DECLARE #T1 BIT = 0
SELECT top 1 #T1 = 1 FROM sys.tables WHERE [TYPE] = 'U' AND name = 'test_history_9999_99'
IF #T1 = 1 TRUNCATE table test_history_9999_99
DECLARE #T2 BIT = 0
SELECT top 1 #T2 = 1 FROM sys.tables WHERE [TYPE] = 'U' AND name = #hist
IF #T1=0 BEGIN
SELECT ID, [desc], #date DATE_TIME, cast('delete' as varchar(20)) as operation, CAST(#guid AS varchar(64)) BATCH
INTO test_history_9999_99
FROM DELETED
END else begin
INSERT INTO test_history_9999_99
SELECT ID, [desc], #date, cast('delete' as varchar(20)) as operation, CAST(#guid AS varchar(64)) BATCH
FROM DELETED
end
INSERT INTO test_history_9999_99
SELECT ID, [desc], #date, cast('insert' as varchar(20)) as operation, CAST(#guid AS varchar(64)) BATCH
FROM inserted
IF #T2 = 0 BEGIN
EXEC sp_rename 'test_history_9999_99', #hist
END ELSE BEGIN
declare #DynamicSql nvarchar(2000)
SET #DynamicSql = 'INSERT INTO ' + #hist + ' SELECT * FROM test_history_9999_99;'
Exec sp_executesql #DynamicSql
END
END
My test_table contains only two columns ID and [Desc].
In the history tables I have added a DATETIME column with change date and a UNIQUEIDENTIFIER column so you can group all changes in a batch if you INSERT/UPDATE many records with a single operation
Tanks for the answer #MtwStark. Now it works, I can check if the table exists, and create it if not. And have eaccess to the DELETED and INSERTED tables.
I'm not sure, if in your solution I have to create the test_history_9999_99 table in advance. Because when I used your trigger I've got an error about column insertion (I didn't understand the error completly).
Now my code looks like this. I'm not sure if it can handle INSERT/UPDATE many records with a single operation. Probably I still need to insert this code for it? CAST(#guid AS varchar(64)) BATCH . I'm not sure what it really does, I have to look into it deeper.
CREATE TRIGGER [dbo].[teszttablatrigger] ON [teszt].[dbo].[teszt] FOR DELETE, INSERT, UPDATE AS
declare #hist nvarchar(40)
set #hist='teszthistory_' + CAST(YEAR(getdate()) as NCHAR(4))+ '_' + (case when Month(GETDATE())<10 then '0' + CAST (Month(GETDATE()) as NCHAR(1))
when Month(GETDATE())>=10 then CAST (Month(GETDATE()) as NCHAR(2)) end)
select * into #ins from inserted
select * into #del from deleted
declare #DynamicSql nvarchar(2000)
DECLARE #T2 BIT = 0
SELECT top 1 #T2 = 1 FROM sys.tables WHERE [TYPE] = 'U' AND name = #hist
if #T2=0 begin
set #DynamicSql = N'CREATE TABLE [' + #hist + '] ( kulcs int, szoveg varchar(40), modtip varchar(40), datum datetime default getdate())'
Exec sp_executesql #DynamicSql
end
set #DynamicSql = N'INSERT INTO ' + #hist + ' (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''delete''
FROM #del
INSERT INTO ' + #hist + ' (kulcs, szoveg, modtip)
SELECT kulcs, szoveg, ''insert''
FROM #ins'
Exec sp_executesql #DynamicSql
Try refreshing intellisense. Ctrl+Shift+R see if that might help. Or do a database table refresh.
If you have SQL Server enterprise (check your version) Then better way will be to enable CDC.
https://msdn.microsoft.com/en-us/library/cc645937(v=sql.110).aspx
Really silly, but I'm trying to come up with an SQL Server script that creates a stored procedure that decreases the RackRate of a hotel room by 6.66%. When I go to execute the stored procedure, I keep getting the following error:
Must declare the scalar variable '#NewRackRate'.
Any help would be appreciated.
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE SPECIFIC_NAME = 'sp_UpdateRackRate')
DROP PROCEDURE sp_UpdateRackRate;
GO
CREATE PROC sp_UpdateRackRate
#RackRate smallmoney
AS
BEGIN
DECLARE #NewRackRate smallmoney;
DECLARE #OldRackRate smallmoney = (SELECT RackRate FROM RackRateTable);
UPDATE RackRateTable
SET #NewRackRate = #OldRackRate - (#OldRackRate * .0666)
END
GO
EXEC sp_UpdateRackRate
#RackRate = #NewRackRate; // How do I access #NewRackRate?
I'm guessing you want to output #NewRackRate from your stored procedure. Based on the example provided, here's a quick way you could do it...
CREATE PROC sp_UpdateRackRate
#NewRackRate smallmoney OUTPUT
AS BEGIN
SELECT #NewRackRate = RackRate * 0.0334 FROM RackRateTable;
END
GO
DECLARE #RackRate smallmoney;
EXEC sp_UpdateRackRate #RackRate OUTPUT;
--SELECT #RackRate;
Realistically, though, you don't need a stored procedure for this. Simply,
DECLARE #RackRate smallmoney;
SELECT #RackRate = RackRate * 0.0334 FROM RackRateTable;
--SELECT #RackRate;
/*
drop table rackratetable;
create Table rackratetable (ID int ,rackrate smallmoney)
truncate table rackratetable
insert into rackratetable values (1,100.00)
drop procedure PUpdateRAckRAte ;
CREATE procedure PUpdateRackRate
#adjustment smallmoney , --input parameter
#updatedrackrate smallmoney OUTPUT --output parameter
as
update rackratetable
set rackrate = rackrate * #adjustment
select #updatedrackrate = rackrate from rackratetable
--select #updatedrackrate
return
*/
set nocount on
truncate table rackratetable
insert into rackratetable values (1,100.00)
declare #newrate smallmoney
exec PUpdateRackRate
#adjustment = 0.666, --Input parameter
#updatedrackrate = #newrate OUTPUT --Returned from procedure note the counterintuitiveness
select 'Updated rack rate is ' + cast(#newrate as varchar(max))
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE SPECIFIC_NAME = 'sp_UpdateRackRate')
DROP PROCEDURE sp_UpdateRackRate;
GO
CREATE PROC sp_UpdateRackRate
#RackRate smallmoney
AS
BEGIN
DECLARE #NewRackRate smallmoney;
DECLARE #OldRackRate smallmoney = (SELECT Top 1 RackRate FROM RackRate);
UPDATE RackRateTable
SET NewRackRate = #OldRackRate - (#OldRackRate * .0666)
END
select (#OldRackRate - (#OldRackRate * .0666)) NewRackRate
GO
EXEC sp_UpdateRackRate 100.06
I have a scenario wherein i have to execute an SP for specified number of time(number of execution will be mentioned by user) without using loop.
My SP is setting an OUTPUT variable of varchar type. I am willing to insert the output of my SP into a temp table and use it for further processing.
I am unable to modify this SP into function as it contain an Update statement.
Kindly suggest if we can do so without loop.
Whit this solution you do not need an output; #res is your result directly set in a temp table.
CREATE PROCEDURE [dbo].[myStoredProc]
#Counter int,
#params nvarchar(64),
#CreateTable bit
AS
DECLARE #res varchar(64)
IF #CreateTable = 1
BEGIN
IF EXISTS (SELECT 1 FROM dbo.sysobjects WHERE id = object_id(N'[#tempTable]'))
DROP TABLE #tempTable
CREATE TABLE #tempTable ([Res] [nvarchar] (64))
END
SET #res = CONVERT(varchar(64), #Counter)
SET #Counter = #Counter - 1
IF #Counter > 0
exec myStoredProc #Counter, #params, 0
INSERT #tempTable VALUES (#res)
IF #CreateTable = 1
BEGIN
SELECT * FROM #tempTable
DROP TABLE #tempTable
END
GO
DECLARE #o varchar(64)
exec [myStoredProc] 5, '', 1
The code is as follows:
ALTER PROCEDURE dbo.pdpd_DynamicCall
#SQLString varchar(4096) = null
AS
Begin
create TABLE #T1 ( column_1 varchar(10) , column_2 varchar(100) )
insert into #T1
execute ('execute ' + #SQLString )
select * from #T1
End
The problem is that I want to call different procedures that can give back different columns.
Therefore I would have to define the table #T1 generically.
But I don't know how.
Can anyone help me on this problem?
Try:
SELECT into #T1 execute ('execute ' + #SQLString )
And this smells real bad like an sql injection vulnerability.
correction (per #CarpeDiem's comment):
INSERT into #T1 execute ('execute ' + #SQLString )
also, omit the 'execute' if the sql string is something other than a procedure
You can define a table dynamically just as you are inserting into it dynamically, but the problem is with the scope of temp tables. For example, this code:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE #T1 (Col1 varchar(20))'
EXEC(#sql)
INSERT INTO #T1 (Col1) VALUES ('This will not work.')
SELECT * FROM #T1
will return with the error "Invalid object name '#T1'." This is because the temp table #T1 is created at a "lower level" than the block of executing code. In order to fix, use a global temp table:
DECLARE #sql varchar(max)
SET #sql = 'CREATE TABLE ##T1 (Col1 varchar(20))'
EXEC(#sql)
INSERT INTO ##T1 (Col1) VALUES ('This will work.')
SELECT * FROM ##T1
Hope this helps,
Jesse
Be careful of a global temp table solution as this may fail if two users use the same routine at the same time as a global temp table can be seen by all users...
create a global temp table with a GUID in the name dynamically. Then you can work with it in your code, via dyn sql, without worry that another process calling same sproc will use it. This is useful when you dont know what to expect from the underlying selected table each time it runs so you cannot created a temp table explicitly beforehand. ie - you need to use SELECT * INTO syntax
DECLARE #TmpGlobalTable varchar(255) = 'SomeText_' + convert(varchar(36),NEWID())
-- select #TmpGlobalTable
-- build query
SET #Sql =
'SELECT * INTO [##' + #TmpGlobalTable + '] FROM SomeTable'
EXEC (#Sql)
EXEC ('SELECT * FROM [##' + #TmpGlobalTable + '] ')
EXEC ('DROP TABLE [##' + #TmpGlobalTable + ']')
PRINT 'Dropped Table ' + #TmpGlobalTable
INSERT INTO #TempTable
EXEC(#SelectStatement)
Try Below code for creating temp table dynamically from Stored Procedure Output using T-SQL
declare #ExecutionName varchar(1000) = 'exec [spname] param1,param2 '
declare #sqlStr varchar(max) = ''
declare #tempTableDef nvarchar(max) =
(
SELECT distinct
STUFF(
(
SELECT ','+a.[name]+' '+[system_type_name]
+'
' AS [text()]
FROM sys.dm_exec_describe_first_result_set (#ExecutionName, null, 0) a
ORDER BY a.column_ordinal
FOR XML PATH ('')
), 1, 1, '') tempTableDef
FROM sys.dm_exec_describe_first_result_set (#ExecutionName, null, 0) b
)
IF ISNULL(#tempTableDef ,'') = '' RAISERROR( 'Invalid SP Configuration. At least one column is required in Select list of SP output.',16,1) ;
set #tempTableDef='CREATE TABLE #ResultDef
(
' + REPLACE(#tempTableDef,'
','') +'
)
INSERT INTO #ResultDef
' + #ExecutionName
Select #sqlStr = #tempTableDef +' Select * from #ResultDef '
exec(#sqlStr)
DECLARE #EmpGroup INT =3 ,
#IsActive BIT=1
DECLARE #tblEmpMaster AS TABLE
(EmpCode VARCHAR(20),EmpName VARCHAR(50),EmpAddress VARCHAR(500))
INSERT INTO #tblEmpMaster EXECUTE SPGetEmpList #EmpGroup,#IsActive
SELECT * FROM #tblEmpMaster
CREATE PROCEDURE dbo.pdpd_DynamicCall
AS
DECLARE #SQLString_2 NVARCHAR(4000)
SET NOCOUNT ON
Begin
--- Create global temp table
CREATE TABLE ##T1 ( column_1 varchar(10) , column_2 varchar(100) )
SELECT #SQLString_2 = 'INSERT INTO ##T1( column_1, column_2) SELECT column_1 = "123", column_2 = "MUHAMMAD IMRON"'
SELECT #SQLString_2 = REPLACE(#SQLString_2, '"', '''')
EXEC SP_EXECUTESQL #SQLString_2
--- Test Display records
SELECT * FROM ##T1
--- Drop global temp table
IF OBJECT_ID('tempdb..##T1','u') IS NOT NULL
DROP TABLE ##T1
End
Not sure if I understand well, but maybe you could form the CREATE statement inside a string, then execute that String? That way you could add as many columns as you want.