I've made a stored procedure to get practice with cursors, I've a problem with special characters, for instance, if last_name contains a single quote, I've got an error, I need to escape it in some way, how could I do that? I don't know which special characters are contained in these fields, I've tried with QUOTENAME(d.last_name) but it didn't work
CREATE OR alter PROCEDURE list_employees
AS
BEGIN
DECLARE cursore CURSOR FAST_FORWARD FOR SELECT TOP(20) d.id, d.first_name, d.last_name, cd.contact
FROM employees d
JOIN contacts cd ON cd.fk_employee= d.id
ORDER BY d.id;
DECLARE #id_employee VARCHAR(36);
DECLARE #first_name VARCHAR(50);
DECLARE #last_name VARCHAR(50);
DECLARE #contact VARCHAR(255);
DECLARE #insert_statement varchar(1000);
IF OBJECT_ID('dbo.list_employees', 'U') IS NOT NULL
BEGIN
DROP TABLE dbo.list_employees;
END
OPEN cursore;
FETCH NEXT FROM cursore INTO #id_employee , #first_name , #cognome, #contatto ;
if(##FETCH_STATUS = 0)
BEGIN
CREATE TABLE dbo.list_employees(id_employee VARCHAR(36), first_name VARCHAR(50), last_name VARCHAR(50), contact VARCHAR(255))
END
WHILE ##FETCH_STATUS = 0
BEGIN
SET #insert_statement = 'INSERT INTO list_employees SELECT '''+#id_employee +''', '''+#first_name +''', '''+#last_name +''','''+ #contact +''''
exec(#insert_statement )
FETCH NEXT FROM cursore INTO #id_employee , #first_name , #last_name , #contact ;
END
CLOSE cursore;
DEALLOCATE cursore;
END;
Since your code drops an existing table and then recreates it I suspect this procedure is an odd way of getting the "current top 20". Instead of using a cursor and all sorts of hassle this would be massively simplified to use a view. There is no need to constantly drop a table and repopulate it.
Here is what your view might look like.
create or alter view list_employees as
SELECT TOP(20) d.id
, d.first_name
, d.last_name
, cd.contact
FROM employees d
JOIN contacts cd ON cd.fk_employee = d.id
ORDER BY d.id;
Firstly, let's cover off why what you have isn't working; this is because you are injecting values that should be parameters. Specifically these 2 lines are the cause:
SET #insert_statement = 'INSERT INTO list_employees SELECT '''+#id_employee +''', '''+#first_name +''', '''+#last_name +''','''+ #contact +''''
exec(#insert_statement )
There are, in truth, 2 bad habits here:
injection of non-sanitised values (a huge security vulnerability)
Use of EXEC(#SQL) syntax, rather than sys.sp_executesql, meaning you can't parametrise your statement.
If you parametrise your statement, then the problem you have goes away:
SET #insert_statement = 'INSERT INTO dbo.list_employees (id_employee,first_name,last_name,contact) VALUES (#id_employee,#first_name,#last_name,#contact);';
EXEC sys.sp_executesql #insert_statement, N'#id_employee VARCHAR(36),#first_name VARCHAR(50),#last_name VARCHAR(50),#contact VARCHAR(255)', #id_employee,#first_name,#last_name,#contact;
Of course, this poses the question of why use dynamic SQL at all, there's nothing dynamic about the statement. It's not that the table might not exist prior to the execution, as if a table doesn't exist the validation can be deferred by the engine, and you CREATE the table in the same scope. Perhaps the definition of the table is changing? I hope not.
As also mentioned, however, the CURSOR isn't really required here. Although you state you are practicing them, there are very few times that they are ever needed, and changing something like this to use an RBAR CURSOR will be terrible for performance. You really should be using a set based INSERT:
INSERT INTO dbo.list_employees (id_employee,first_name,last_name,contact)
SELECT TOP (20)
e.id,
e.first_name,
e.last_name,
c.contact
FROM dbo.employees e
JOIN dbo.contacts c ON c.fk_employee= e.id
ORDER BY e.id;
Or, better yet, use a VIEW as Sean demonstrates in their answer.
Related
I need to find a T-SQL way to remove GO command from scripts which I read from .sql files. I'm using similar to this approach Execute SQL scripts from file, which suits my needs very well, however some of the files contains GO command and breaks execution as sp_executesql doesn't handle non T-SQL commands.
How to create a REPLACE which would remove GO, which mostly sits alone in the row? Or any other method I could apply here? Please keep in mind, that in the script there could be other GOs, which are actually not a command.
DECLARE #sql NVARCHAR(1000) =
'DECLARE #table AS TABLE(
[Id] INT,
[Info] NVARCHAR(100)
);
INSERT INTO #table
([Id],[Info])
VALUES
(1,''Info''),
(2,''Show must go on''),
(3,''GO'');
SELECT * FROM #table;
GO';
PRINT #sql;
EXEC sp_executesql #sql;
Using xp_cmdshell to execute scripts is not an option due to server security restrictions. SQLCMD is not an option too this time.
Well, I would NOT claim that this is the way this should be done, but it was some fun to ticker it down:
Disclaimer: This is a demonstration why TSQL is the wrong tool for this 😁
I added some more GO-separated statements and used quotes within to get it even more tricky:
DECLARE #sql NVARCHAR(1000) =
'DECLARE #table AS TABLE(
[Id] INT,
[Info] NVARCHAR(100)
);
INSERT INTO #table
([Id],[Info])
VALUES
(1,''Info''),
(2,''Show must go on''),
(3,''This includes a GO and some "quoted" text''),
(4,''GO'');
SELECT * FROM #table;
GO
SELECT TOP 10 * FROM sys.objects
GO
PRINT ''Each GO will be used to have separate patches''';
--let's start by removing various kinds of line breaks
SET #sql = REPLACE(REPLACE(STRING_ESCAPE(#sql,'json'),'\r','\n'),'\n\n','\n');
--Using a CS_AS-collation will avoid to take "go" as we (hopefully!) can rely on "GO":
DECLARE #json NVARCHAR(MAX) = CONCAT('["',REPLACE(REPLACE(#sql COLLATE Latin1_General_CS_AS,'GO' COLLATE Latin1_General_CS_AS,'","GO'),'\n','","'),'"]');
--Above I used each upper-case "GO" and each line break to separate the string.
--Doing so we transform your string into a json array
--Now we can fill this into a table using OPENJSON to read the json-array (ommitting empty lines)
DECLARE #tbl TABLE(RowIndex INT IDENTITY,fragment NVARCHAR(MAX));
INSERT INTO #tbl(fragment)
SELECT STRING_ESCAPE(A.[value],'json')
FROM OPENJSON(#json) A
WHERE LEN(TRIM(A.[value]))>0 AND TRIM(A.[value])!=NCHAR(9);
--We need these variable for the cursor
DECLARE #patch NVARCHAR(MAX);
--Now I open a cursor
--We do this by running down a recursive CTE once again building up a json array.
--This time we will separate the strings when the upper-case "GO" is sitting alone in its line.
DECLARE cur CURSOR FOR
WITH cte AS
(
SELECT RowIndex, CAST(CONCAT('["',fragment) AS NVARCHAR(MAX)) growingString
FROM #tbl WHERE RowIndex=1
UNION ALL
SELECT n.RowIndex
,CONCAT(cte.growingString,CASE WHEN TRIM(n.fragment) COLLATE Latin1_General_CS_AS=N'GO' THEN N'","' ELSE n.fragment END)
FROM #tbl n
INNER JOIN cte ON n.RowIndex=cte.RowIndex+1
)
,thePatches AS
(
SELECT TOP 1 CONCAT(growingString,'"]') AS jsonArray
FROM cte ORDER BY RowIndex DESC
)
SELECT A.[value] AS patch
FROM thePatches p
CROSS APPLY OPENJSON(p.jsonArray) A;
--we can - finally - walk down the patches and execute them one by one
OPEN cur;
FETCH NEXT FROM cur INTO #patch;
WHILE ##FETCH_STATUS=0
BEGIN
PRINT #patch; --PRINT out for visual control before execution!
--EXEC(#patch);
FETCH NEXT FROM cur INTO #patch;
END
CLOSE cur;
DEALLOCATE cur;
There are millions of things (e.g. line-breaks within content, commented sections, max recursion) which can destroy this approach. So clearly DO NOT follow this suggestion :-)
I have a database that uses Insert, Update, and Delete Triggers for almost all tables. They log the host and program performing the operation in a separate auditing table. The triggers all include this select statement to set variables that get inserted into the auditing table:
select #HostName = HostName, #ProgramName = Program_Name
from master..sysprocesses where SPID = ##SPID
We are now looking to migrate to Azure SQL Database, which does not support the master..sysprocesses syntax. It also appears that table is deprecated as well: https://learn.microsoft.com/en-us/sql/relational-databases/system-compatibility-views/sys-sysprocesses-transact-sql?view=sql-server-ver15
What we need to do is update the triggers to use this instead:
select #HostName = [host_name], #ProgramName = [program_name]
from sys.dm_exec_sessions where session_id = ##SPID
However, the database has hundreds of tables and each table has three triggers that need updating. The text-replacement for each trigger is identical. Is there a feasible way to script out something to perform this update on all triggers in the database?
OK, I just tested this by jamming your string in a few triggers (as a comment of course) and then running it. I am not advocating this as the correct way to do it, as this link will help you with the correct way to do dynamic sql https://dba.stackexchange.com/questions/165149/exec-vs-sp-executesql-performance
However, this does work and will help you understand how you would piece these things together to get to that point.
Note, any formatting difference between your triggers may cause this to miss some, so youll want to verify that 0on your own.
DECLARE #string VARCHAR(8000)='select #HostName = HostName, #ProgramName = Program_Name
from master..sysprocesses where SPID = ##SPID'
, #counter INT=1
, #Max INT
, #Sql VARCHAR(mAX)
;
IF OBJECT_ID('TempDB..#TrigUpdate') IS NOT NULL DROP TABLE #TrigUpdate;
CREATE TABLE #TrigUpdate
(
SqlVar VARCHAR(MAX)
, RowID INT
)
;
INSERT INTO #TrigUpdate
SELECT REPLACE(REPLACE(t.definition, #string, ''), 'CREATE TRIGGER', 'ALTER TRIGGER')
, Row_Number() OVER (ORDER BY t.Definition ASC) AS RowID
FROM sys.objects o
INNER JOIN sys.sql_modules t on o.object_id =t.object_id
WHERE o.type_desc='SQL_TRIGGER'
AND CHARINDEX(#string, t.definition,1)>0
;
SET #Max = (SELECT COUNT(*) FROM #TrigUpdate);
WHILE #Counter<=#Max
BEGIN
SET #sql = (SELECT SqlVar FROM #TrigUpdate WHERE RowID=#counter);
EXEC(#Sql);
SET #Counter=#Counter+1;
END
It could be done with Object_Definition and Replace.
Create Table #Triggers_new (TriggerName sysname, QueryText VarChar(max))
Declare #string_pattern VarChar(max), #string_replacement VarChar(max)
Select #string_pattern = '<string_pattern>'
Select #string_replacement = '<string_replacement>'
Insert Into #Triggers_new (TriggerName, QueryText)
Select [name], Replace(Object_Definition(object_id), #string_pattern, #string_replacement)
From sys.objects
Where [type] = 'TR'
Order by [name]
-- Update #Triggers_new Set QueryText = Replace(QueryText, 'Create Trigger ', 'Alter Trigger ')
Why do you use a so heavy query on system table/view that can be changed without your consent ?
Can't you simplify you by using metada functions like :
SELECT HOST_NAME(), PROGRAM_NAME()...
That will give the requested information values ?
I'm using a search object query (found on the internet, wish I could give credit to the developer) to search database for the columns needed when I write queries. The output search object query allows me to enter the type of table to look in (partial name) as well as the column name (partial name) I'm trying to find. I've been attempting to modify the search object query so it returns the 1st value (Top 1) it finds. This would help me to easily see at a glance if the column has the particular type of data I'm looking for.
I've attempted to write it both as a stored procedure that I could pass two parameters (partial table and partial column name) and I've also tried using dynamic SQL (my first attempt at using it, so I'm a novice when it comes to use it). I had moderate success with the use of dynamic SQL, but can only get it to produce one result rather than be called multiple times for all the results in my search object output. The code I used is shown here:
-- This is the search object query found on internet
Use masterdb
Select a.name, b.name
From sysobjects a
Inner Join syscolumns b On a.id = b.id
Where b.name like '%Result%'
And a.name like '%Lab%'
Order By a.name, b.name
-- This is a separate query I used to test calling the data with dynamic SQL
DECLARE #value VARCHAR(100), #tablename VARCHAR(100)
SET #value = 'Result'
SET #tablename = 'LabSpecimen'
DECLARE #sqlText NVARCHAR(1000);
SET #sqlText = N'SELECT Top 1 ' + #value + ' FROM testndb.dbo.' + #tablename
EXEC (#sqlText)
If I use the search object query and search for tables that have lab and column names that have result, I might get output like this:
LabMain,ResultID
LabSpecimen,ResultCategory
LabSpecimen,ResultDate
LabSpecimen,Results
I would like to have the search object query pull data from the table in the first column and the column name in the 2nd column and return the first value it finds to give me a sample output for the given column name/table. Output would look like this:
LabMain,ResultID,E201812310001
LabSpecimen,ResultCategory,ExampleCategory
LabSpecimen,ResultDate,20181231
LabSpecimen,Results,34.20
Okay, I really didn't want to have to post an answer to this, but here goes.
So, the first, really-really-huge thing is: SQL Injection. SQL Injection is the #1 security vulnerability for something like a dozen years running, per OWASP. Basically, SQL Injection is where you use dynamic SQL that has any fragment of the sql command being populated by a user. So in the OP's case, this section here:
SET #value = 'Result'
SET #tablename = 'LabSpecimen'
DECLARE #sqlText NVARCHAR(1000);
SET #sqlText = N'SELECT Top 1 ' + #value + ' FROM testndb.dbo.' + #tablename
EXEC (#sqlText)
... if the end incarnation would be that #tableName and #value are populated by the user as part of their search? Then the user can do a 'search' that ends up injecting sql statements that the server runs directly; for a cheap example, imagine this for #value:
3' ; drop table #tableName --
... which would go ahead and drop every table that matches the #tablename you passed in.
Anyway, so, as we go through this problem, we're going to keep SQL Injection in mind at every step.
Problem #1: How to get the tables/columns that match.
You pretty much already nailed this. The only thing missing is to put it into a temp table so that you can loop through it (and limit it down to U-types, since otherwise you'll get stored procs and system tables.) I went ahead and had it also hit the Schema information - that way, if you have tables in different schemas, it'll still be able to get the results.
declare #tableNameFragment varchar(100) -- note: these top 4 lines will eventually
declare #columnNameFragment varchar(100) -- be changed to stored proc args
set #tableNameFragment = 'Performance' -- and populated by the user calling
set #columnNameFragment = 'status' -- the proc (instead of hard-coded.)
declare #entityMatches TABLE (TableName varchar(200), ColName varchar(128))
insert into #entityMatches
Select sch.TABLE_SCHEMA + '.' + sysobj.name as TableName, syscol.name as ColName
From sysobjects sysobj
Join syscolumns syscol On sysobj.id = syscol.id
Join INFORMATION_SCHEMA.TABLES sch on sch.TABLE_NAME = sysobj.name
where sysobj.xtype = 'U'
and (sysobj.name like '%' + isnull(#tableNameFragment,'') + '%')
and (syscol.name like '%' + isnull(#columnNameFragment,'') + '%')
Now, notice that while #tableNameFragment and #columnNameFragment are used, they're not used in a dynamic query. It doesn't matter if the user puts in something malicious into those values
Problem #2 - How to loop through your table
Basically, you're going to need a cursor. I hate cursors, but sometimes (like this one), they're necessary.
Problem #3 - How to actually do a dynamic query and get a result back
This is actually trickier than it looks. You can't do a raw EXEC() for a return value, nor can you simply have the cmd you're executing populating a variable - because EXEC (and SP_ExecuteSql operate in a different context, so they can't populate variables outside in your script.)
You need to use SP_ExecuteSQL, but specify a return variable getting populated by the interior sql command. For example:
declare #sqlCmd nvarchar(max)
declare #dynamicReturn varchar(max)
set #sqlCmd = 'select #retVal=1'
EXEC Sp_executesql #sqlCmd,
N'#retVal varchar(max) output',
#dynamicReturn output
select #dynamicReturn
Problem #4 - How to write your Dynamic command
Here's where things get dicey, since it's where we're using a dynamic SQL command. The important thing here is: you cannot use anything the user provided as an input. Which means, you can't use the variables #tableNameFragment or #columnNameFragment. You can use the values in the #entityMatches table, though. Why? Because the user didn't populate them. They got populated by the data in the sys tables - it doesn't matter if the user puts something nefarious in the input variables, that #entityMatches data simply holds the existing table/column names that match.
Also important: When you're working on code that could be a problem if a future dev down the line tweaks or copies/pastes - you should put comment warnings to illuminate the issue.
So, putting it all together? You'll have something that looks like this:
declare #tableNameFragment varchar(100) -- note: these top 4 lines will eventually
declare #columnNameFragment varchar(100) -- be changed to stored proc args
set #tableNameFragment = 'Performance' -- and populated by the user calling
set #columnNameFragment = 'status' -- the proc (instead of hard-coded.)
declare #entityMatches TABLE (TableName varchar(200), ColName varchar(128))
insert into #entityMatches
Select sch.TABLE_SCHEMA + '.' + sysobj.name as TableName, syscol.name as ColName
From sysobjects sysobj
Join syscolumns syscol On sysobj.id = syscol.id
Join INFORMATION_SCHEMA.TABLES sch on sch.TABLE_NAME = sysobj.name
where sysobj.xtype = 'U'
and (sysobj.name like '%' + isnull(#tableNameFragment,'') + '%')
and (syscol.name like '%' + isnull(#columnNameFragment,'') + '%')
declare #returnResults TABLE (TableName varchar(200), ColName varchar(128), FirstValue varchar(max))
declare Cur Cursor For select TableName,ColName from #entityMatches
declare #cursorTable varchar(200), #cursorColumn varchar(128)
open Cur
fetch Next from cur into #cursorTable,#cursorColumn
while ##FETCH_STATUS = 0
begin
-- Note: the variables #cursorTable, #cursorColumn are NOT user populated
-- but instead are populated from the Sys tables. Because of this,
-- this dynamic sql below is not SQL-Injection vulnerable (the entries
-- are not populated from user entry of any sort.)
-- Be very careful modifying the lines below to make sure you don't
-- introduce a vulnerability.
declare #sqlCmd nvarchar(max)
declare #dynamicReturn varchar(max)
set #sqlCmd = 'select top 1 #retVal=[' + #cursorColumn + '] from ' + #cursorTable
EXEC Sp_executesql #sqlCmd,
N'#retVal varchar(max) output',
#dynamicReturn output
insert into #returnResults values (#cursorTable, #cursorColumn, #dynamicReturn)
fetch Next from cur into #cursorTable,#cursorColumn
End
close cur
deallocate cur
select * from #returnResults
Create a stored procedure like below mention stored procedure.
Get the table and column name from sysobject & syscolumn and add it in hash table on the base of parameter of stored procedure. After that declare a cursor and in loop of cursor create a dynamic query of column and table name and get first row of current column from table of cursor loop. After that execute the query and update the result in the hash table. At the end of lookup select the Record from hash table. Check the below stored procedure. I hope that its helpful for you.
Create procedure Sp_GetSampleData
#TName varchar(200) = ''
as
Select
a.name TableName, b.name ColumnName,
CAST('' as varchar(max)) as SampleValue
into
#Tbl
from
sysobjects a
inner join
syscolumns b on a.id = b.id
where
(#TName='' or a.name = #TName)
order ny
a.name, b.name
declare #TableName varchar(200), #ColumnName varchar(200),
#sqlText nvarchar(max), #Val varchar(max)
declare Cur Cursor For
select TableName, ColumnName
from #Tbl
open Cur
fetch Next from cur into #TableName,#ColumnName
while ##FETCH_STATUS =0
begin
set #sqlText=''
set #Val=''
SET #sqlText = N'SELECT Top 1 #Val=[' + #ColumnName + '] FROM testndb.dbo.' + #TableName
EXEC Sp_executesql
#sqlText,
N'#Val varchar(max) output',
#Val output
print #sqlText
update #Tbl set SampleValue=#Val where TableName=#TableName and ColumnName =#ColumnName
fetch Next from cur into #TableName,#ColumnName
End
close cur
deallocate cur
select * from #Tbl
I have a trigger in mssql in which I want to compare each column from the inserted table with the deleted table to check if the value has changed...
If the value has changed I want to insert the column name into a temp table.
My code until now:
declare columnCursor CURSOR FOR
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'MyTable' AND TABLE_SCHEMA='dbo'
--save inserted and deleted into temp tables
select * into #row1 from Inserted
select * into #row2 from Deleted
declare #tmp table(column_name nvarchar(max))
declare #column nvarchar(50)
OPEN COlumnCUrsor
FETCH NEXT FROM ColumnCursor INTO #column
while ##FETCH_STATUS=0 begin
declare #out bit
declare #sql nvarchar(max) = N'
select #out = case when r1.'+#column+'r2.'+#column+' then 1 else 0 end
from #row1 r1
left join #row2 r2 on r1.sys_volgnr=r2.sys_volgnr'
exec sp_executesql #sql,N'#out bit OUTPUT', #out=#out OUTPUT
if( #out = 1 ) begin
insert into #tmp VALUES(#column)
end
FETCH NEXT FROM ColumnCursor INTO #column
end
CLOSE ColumnCursor;
DEALLOCATE ColumnCursor;
Is there an easier way to accomplish this?
Yes, there is.
You can use the COLUMNS_UPDATED function to determine the columns that had actually changed values, though it's not a very friendly function in terms of code readability.
Read this article from Microsoft support called Proper Use of the COLUMNS_UPDATED() Function to see what I mean.
I've came across an article called A More Performant Alternative To COLUMNS_UPDATED(), perhaps it can help you or at least inspire you.
I will note that you should resist the temptation to use the UPDATE() function, as it may return true even if no data was changed.
here is the relevant part from it's MSDN page:
UPDATE() returns TRUE regardless of whether an INSERT or UPDATE attempt is successful.
Looks like you're trying to build a dynamic solution, which might be useful if you expect to change often (=new columns to be added etc). You could do something like this (in pseudo-code)
Build a dynamic SQL based on DMVs (INFORMATION_SCHEMA.COLUMNS) for the column names:
insert into table ...
select
function_to_split_by_comma (
case when I.col1 = U.col1 then 'col1,' else '' end +
case when I.col2 = U.col2 then 'col2,' else '' end +
...
)
where
I.key_column1 = U.key_column1 ...
These names (col1, col2) should be the columns from the DMV query, + the case for each of the row, and then fixed SQL part for the beginning + you'll need to figure out how to join inserted and deleted, which requires the primary key.
For splitting the data into rows, you can use for example the delimited_split_8k by Jeff Moden (http://www.sqlservercentral.com/articles/Tally+Table/72993/).
Also as Damien pointed out, there can be more than one row in the inserted / deleted tables.
In Sql server, i write a procedure and i use one tem table and a cursor and dynamically add one column to that temporary table but it is giving erro :
(10 row(s) affected)
Msg 213, Level 16, State 1, Procedure USP_F_Roll_AllIndia_Report, Line 27
Column name or number of supplied values does not match table definition.
This is my proc :
alter procedure USP_F_Roll_AllIndia_Report
(#segcode int,#rollplanyear int)
as
begin
declare #cfcode varchar(10)
declare #cfname varchar(30)
declare #SQl nvarchar(max)
create table #TEP (productcode varchar(10) collate database_default,proddesc varchar(100))
declare db_cursor cursor for
select distinct canfm.CFCode, SUBSTRING (CANFM.CFName,4,5)as CFName from Tbl_F_CandF_M CANFM left outer join Tbl_F_Org_CandF_T CT on CANFM.CFCode = ct.CFCode where CANFM .status =1 and ct.Status =1 order by canfm.cfcode
open db_cursor
fetch next from db_cursor into #cfcode, #cfname
while ##FETCH_STATUS =0
begin
set #SQL ='alter table #TEP add '+#cfname+' float'
exec sp_executesql #Sql
--exec ( #Sql)
insert into #TEP
select pd.productcode,PM.productdesc,convert(varchar,sum(isnull(AmendedQty,isnull(Quantity,0))))as quantity from Tbl_F_Roll_PlanDetails_T pd left outer join Tbl_F_ProductMaster_M PM on
pd.ProductCode =pm.ProductCode left outer join Tbl_F_CandF_M CANDF on pd.CandFLocation =CANDF.CFCode where pd. RollPlanYear =#rollplanyear and pd.CandFLocation =#cfcode and pd.ProductCode in (
select ProductCode from Tbl_F_Segment_Product_t where SegCode =#segcode ) group by pd.ProductCode,pm.ProductDesc
fetch next from db_cursor into #cfcode, #cfname
end
close db_cursor
deallocate db_cursor
select * from #TEP
end
this will not work . if you are adding column then your select query in insert statement must be a dynamic one since it will keep adding column .
you can create a dynamic query for insert and in select as well.
you will also need to specify columns names like
insert into #TEP (col1,clo2,col3..)
there might be better ways for your requiremnet if you specify them .
cursor and adding column is not good logic.
This will not work as you're trying to. SQL Server is trying to compile the entire batch as early as possible - it compiles the insert once your create table has executed, and at that time, there are 2 columns in the table, but 3 in the insert.
But, stop and think about it further - even if it worked the first time through the loop, what happens the next time through your loop? At that point, there are 4 columns in your table, but still only 3 in the insert. I can't remember if that will fail completely, or just insert into the first additional column you've added to the table, but either way, I'm almost certain it's not what you want.
It looks like you're trying to do some form of pivot with an unknown number of columns - there are plenty of questions and answers on SO about doing that already. You have to go down the route of dynamic SQL (even more so than what you've attempted), and it's never pretty. I'd almost always recommend returning a normal result set (e.g. fixed number of columns) to another system (code, report generator, etc) which is better suited to do that kind of mucking about with formatting.
I think the problem is that you are adding a new column on each loop, but the insert statement has a static number of columns.
i don't know why this isn't working for you but
I'm Running SQL SERVER 2008 R2 AND the following is working fine with me
CREATE TABLE #temp (ID int)
DECLARE #Sql as varchar(250)
DECLARE #colName varchar(50)
SET #colName = 'name'
SET #Sql = 'ALTER TABLE #temp ADD [' + #colName + '] VARCHAR(50)'
EXEC (#Sql)
SELECT * FROM #temp
DROP TABLE #temp
SQL Fiddle
Declare #colname varchar(max)='col1,col2,col3',#sqlq varchar(max)=''
Declare #tblname varchar(max)='tbl1'
set #sqlq='Select '+#colname+' from table tbl_DefaultPermission '
EXECUTE (#sqlq)