I want to use Common Table Expressions (CTE) instead of Cursor in SQL Server 2012. Your assistance is highly appreciated.
This is my situation:
DECLARE
#tp_ID INTEGER
truncate table T_Rep_Exit_Checklist_Table
DECLARE cursorName CURSOR -- Declare cursor
LOCAL SCROLL STATIC
FOR
SELECT
tp_ID
from V_Rep_Exit_Checklist
OPEN cursorName -- open the cursor
FETCH NEXT FROM cursorName INTO #tp_ID
WHILE ##FETCH_STATUS = 0
BEGIN
insert into T_Rep_Exit_Checklist_Table
SELECT
#tp_ID-- AS tp_ID
,Item_Status_Code
,Item_Status_Desc
,Item_Code
,Item_Desc
,Item_Cat_Code
,Item_Cat_Desc
,Item_Cleared_By_No
,Item_Cleared_By_Name
V_Rep_Exit_Checklist c
FETCH NEXT FROM cursorName
INTO #tp_ID
END
CLOSE cursorName -- close the cursor
DEALLOCATE cursorName -- Deallocate the cursor
Something about your query doesn't seem to make sense. It seems like your INSERT statement with the SELECT is missing a where clause and therefore if you source view have 5 records for instance, you insert 25 records because you take the id of the first record during the first iteration with the cursor, and insert all records with that id, then repeat for each row of the view.
Assuming the above logic is intended, then you should just need a CROSS JOIN:
INSERT T_Rep_Exit_Checklist_Table
SELECT
T1.tp_ID,
T2.Item_Status_Code,
T2.Item_Status_Desc,
T2.Item_Code,
T2.Item_Desc,
T2.Item_Cat_Code,
T2.Item_Cat_Desc,
T2.Item_Cleared_By_No,
T2.Item_Cleared_By_Name
FROM V_Rep_Exit_Checklist T1
CROSS JOIN V_Rep_Exit_Checklist T2
However, you would like to see it as CTE:
;WITH CTE AS (
SELECT * FROM V_Rep_Exit_Checklist
)
INSERT T_Rep_Exit_Checklist_Table
SELECT
T1.tp_ID,
T2.Item_Status_Code,
T2.Item_Status_Desc,
T2.Item_Code,
T2.Item_Desc,
T2.Item_Cat_Code,
T2.Item_Cat_Desc,
T2.Item_Cleared_By_No,
T2.Item_Cleared_By_Name
FROM CTE T1
CROSS JOIN CTE T2
If my assumption is wrong and instead you are trying to just insert all records in the view directly into the table, then why not just a simple INSERT as below?
INSERT T_Rep_Exit_Checklist_Table
SELECT
tp_ID,
Item_Status_Code,
Item_Status_Desc,
Item_Code,
Item_Desc,
Item_Cat_Code,
Item_Cat_Desc,
Item_Cleared_By_No,
Item_Cleared_By_Name
FROM V_Rep_Exit_Checklist
However, if your business requirement is such that you can only insert the records from your view 1 tp_ID at a time, a while statement could be used to replace your cursor:
DECLARE #Records TABLE (tp_ID INT)
INSERT #Records
SELECT tp_ID FROM V_Rep_Exit_Checklist
DECLARE #tp_ID INTEGER
WHILE EXISTS (SELECT * FROM #Records) BEGIN
SET #tp_ID = (SELECT TOP 1 tp_ID FROM #Records)
INSERT T_Rep_Exit_Checklist_Table
SELECT
tp_ID,
Item_Status_Code,
Item_Status_Desc,
Item_Code,
Item_Desc,
Item_Cat_Code,
Item_Cat_Desc,
Item_Cleared_By_No,
Item_Cleared_By_Name
FROM V_Rep_Exit_Checklist
WHERE tp_ID = #tp_ID
DELETE #Records WHERE tp_ID = #tp_ID
END
Related
I have a table (Table1) that has 4 columns (ID1, ID2, Percent, Time, Expired). I want to insert a bunch of new rows in that table where ID1 is taken from another SQL query I have and all the other columns are set to some specified values.
So I have my query:
SELECT someID FROM other_tables WITH other_conditions
And essentially what I want to do is
FOR v in <above query>
Insert New row into Table1 (v, some second id, some percent, some time, some expired value)
EDIT I'm not opposed to not doing this in a loop, just don't know what the best way to insert the data is
You can use a cursor and fetch I think for what you are trying to accomplish. Here is a shell for you...
WITH CURSOR
DECLARE c CURSOR FOR
SELECT DISTINCT colName FROM Table1 JOIN Table2 ON <stuff> WHERE <other_stuff>
DECLARE #ID VARCHAR(4) --or what ever is needed
OPEN c
FETCH NEXT FROM c INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO Table1 (ID, ID2, Percent, Time, Expired)
VALUES (#ID, some second id, some percent, some time, some expired value)
WHERE ID = #ID
FETCH NEXT FROM c INTO #ID
END
CLOSE c
DEALLOCATE c
WITH CROSS APPLY (DUMMY DATA)
if object_id('tempdb..#ids') is not null drop table #ids
if object_id('tempdb..#idDetails') is not null drop table #idDetails
create table #ids (id int)
insert into #ids (id) values
(1),(2),(3)
select i.*, d.*
into #idDetails
from #ids i cross apply (select 2 as id2 ,2.0 as per,'1/1/2016' as dt,'x' as x) d
select * from #idDetails
WITH CROSS APPLY (EXAMPLE WITH YOUR TABLES)
select i.someID, d.*
into #idDetails
from other_tables i
cross apply (select 'some second id' as id2 ,'some percent' as [Percent],'1/1/2016 14:55:22' as [SomeTime] as dt,'SomeExpiredVal' as [ExpiredVal]) d
select * from #idDetails
Maybe I am missing something, but you need a table valued function which returns the desired row for each row in Table1
create function fn_get_new_recs(id int)
RETURNS #results TABLE (Id INT,<other columns you need>)
AS
BEGIN
--Query here to return new records for a single id
END
then use CROSS APPLY
INSERT INTO Table1(Id,Col1,Col2,Col3)
SELECT ST.Id,ST.Col1,ST.Col2,ST.Col3
FROM Table1 T
cross apply fn_get_new_recs(T.Id) ST
I have a TSQL code that relies on a stored procedure to select a row.
When I'm implementing a more complex TSQL script that will select many rows based on a condition, instead of having one result set of x rows I'm ending up with x result sets containing one row.
My first question is: is it a concern or the performances are close to what I would get with one result set of x rows?
Second question: does anybody think that a temporary table where my stored procedure insert the result (instead of a select) should be faster?
Edit:
Basically this stored procedure select all the items of a given HierarchicalObject.
ALTER PROCEDURE [dbo].[MtdMdl_HierarchicalObject_Collection_Items]
#relatedid int
AS
BEGIN
SET NOCOUNT ON
declare #curkeyid int
declare cur CURSOR static read_only LOCAL
for select distinct [Id] from MtdMdl_Item where [Owner] = #relatedid
open cur
fetch next
from cur into #curkeyid
while ##FETCH_STATUS = 0
BEGIN
-- select the item row from its ID
exec MtdMdl_Item_ItemBase_Read #keyid = #curkeyid
fetch next
from cur into #curkeyid
END
close cur
deallocate cur
END
ALTER PROCEDURE [dbo].[MtdMdl_Item_ItemBase_Read]
#keyid int
AS
BEGIN
SET NOCOUNT ON
SELECT TOP(1) [Id], [TimeStamp], [Name], [Owner], [Value]
FROM [MtdMdl_Item]
WHERE ([Id]=#keyid)
ORDER BY TimeStamp Desc
END
For sure you should better place all single output rows into resulting temporary table before selecting final recordset. There is no reason currently in your code to return one recorset containing all separate rows from iteration over cursor with sp;
Your MtdMdl_Item_ItemBase_Read is relevant a bit because after turning it into function you can avoid sp+cursor and complete the task with one single query using inline function.
upd
According to your data structure I understand that your [Id] is not unique which is source of confusing.
There are many ways to do what you need but here is example of one query even avoiding CTE for temporary result:
DECLARE #relatedid int = 2
SELECT top(1) WITH ties
[Id], [TimeStamp], [Name], [Owner], [Value]
FROM MtdMdl_Item
WHERE [Owner]=#relatedid
ORDER BY row_number() over(partition BY [Id] ORDER BY [TimeStamp] DESC)
Consider this SQL Fiddle as demo.
upd2
Example with inline table function:
CREATE FUNCTION MtdMdl_Item_ItemBase_Read (#keyid int)
RETURNS TABLE
AS
RETURN
(
SELECT TOP(1) [Id], [TimeStamp], [Name], [Owner], [Value]
FROM [MtdMdl_Item]
WHERE ([Id]=#keyid)
ORDER BY TimeStamp Desc
)
GO
DECLARE #relatedid int = 2
SELECT DISTINCT A.[Id],B.* FROM MtdMdl_Item A
OUTER apply (SELECT * FROM MtdMdl_Item_ItemBase_Read(A.[Id])) B
WHERE A.[Owner] = #relatedid
SQL Fiddle 2
Your answer is in below link you should use GROUP BY instead of DISTINCT
SQL/mysql - Select distinct/UNIQUE but return all columns?
And in below line of your code enter list of columns you want in your result
declare cur CURSOR static read_only LOCAL
for select distinct [Id] from MtdMdl_Item where [Owner] = #relatedid
So your query will be
declare cur CURSOR static read_only LOCAL
for select rows,you,want,in,result from MtdMdl_Item where [Owner] = #relatedid Order By [column name you want to be distinct]
DECLARE #tag VARCHAR(MAX)
DECLARE #TagID as INT;
DECLARE tag_cursor CURSOR
FOR
SELECT tagname FROM #temptag
FOR READ ONLY
OPEN tag_cursor
FETCH NEXT FROM tag_cursor INTO #tag
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS (SELECT * FROM Tag WHERE TagName=#tag)
BEGIN
SELECT #TagID = TagID FROM Tag WHERE TagName = #tag
Insert into NoteTags(NoteID,TagID) values (#NoteID,#TagID)
END
ELSE
BEGIN
INSERT INTO Tag
SELECT #tag FROM #temptag
SELECT #TagID = SCOPE_IDENTITY();
Insert into NoteTags(NoteID,TagID) values (#NoteID,#TagID)
END
FETCH NEXT FROM tag_cursor INTO #tag
END
CLOSE tag_cursor
DEALLOCATE tag_cursor
I am passing parameters to procedure using XML, I have created a temporary table and stored all values from XML into it. And then I have written Cursor to check if value already exists in the table or not.
If value is not available records will be inserted.
Problem: If I send two values from XML say IND, USA which doesn't exist in my table, duplicate records are being inserted in the table.
Can anyone tell what mistake I made with my code.
After modifying..
BEGIN
INSERT INTO Tag(TagName) values(#tag);
SELECT #TagID = IDENT_CURRENT('Tag');
Insert into NoteTags(NoteID,TagID) values (#NoteID,#TagID)
END
Here's a set based answer to avoid the cursor:-
insert into tag (tagname)
select tt.tagname
from #temptag tt
where not exists(
select *
from tag t
where t.tagname=tt.tagname
)
insert into notetags (noteid,tagid)
select #noteid,t.tagid
from tag t
where exists(
select *
from #temptag tt
where tt.tagname=t.tagname
)
and not exists(
select *
from notetags nt
where nt.noteid=#noteid
and nt.tagid=t.tagid
)
There is no clue in your code where #noteid gets set.
This code is causing your problem:-
INSERT INTO Tag
SELECT #tag FROM #temptag
You already have the value of #tag - but the select is duplicating it for every row in #temptag so you end up with duplicate rows in Tag
remove the select and change the insert to:-
insert into tag (TagName) values (#tag)
I think the better idea would to not use the cursor. How about doing an outer join between the temporary table and the Tag table and only do an insert where the tag entry is null?
I find it pretty rare that you'd ever need to use a cursor.
SQL Server 2008
I have a table with 2 fields among others
TableA
alterid uniqueidentifier
revnum varchar(50)
Another table has the exact fields as the first one
TableB
alterid uniqueidentifier
revnum varchar(50)
I want to check compare the revnum fields. For each record of TableA I check if there is a record in TableB. If it exists and the revnum fields are different I update the TableB.revnum to the TebleA.revnum. If it does not exist then I add the record from TableA to TableB.
Here is the code
BEGIN
SET #Prod_curs =CURSOR FOR SELECT s.alterid, s.revnum, r.revnum FROM TableA s LEFT OUTER JOIN
TableB r ON s.alterid=r.alterid
WHERE s.revnum<>r.revnum OR r.revnum IS NULL
OPEN #Prod_curs
FETCH NEXT FROM #Prod_curs INTO #alterid, #srevnum, #rrevnum
WHILE ##FETCH_STATUS=0
BEGIN
IF #rrevnum IS NULL
BEGIN
INSERT INTO TableB (alterid,revnum) VALUES (#alterid,ISNULL(#srevnum,0))
END
IF #srevnum<>#rrevnum
BEGIN
UPDATE TableB SET revnum=#srevnum WHERE alterid=#alterid
END
FETCH NEXT FROM #Prod_curs INTO #alterid, #srevnum, #rrevnum
END
CLOSE #Prod_curs;
DEALLOCATE #Prod_curs;
END
My code works fine in a set of about 15000 records except some records where a weird thing happens.
In TableA I have this record
alterid revnum
'A770B280-B4DA-4937-9046-B24E60259AB6' '2-414922-1--1-1-2-51-0'
When it is store in TableB the values are
alterid revnum
'BF18A0EB-A684-486B-B053-55BC2969F1E3' '2-414922-1--1-1-2-51-0'
For some reason the alterid changes and I can't figure out why.
Can someone help
seems like code is working fine its just select that you are doing after insert/update.
i do have some unclear thing in your post as well.
Does the column "revnum" is primary key or has unique constraint on it in TableB ?
the result you have shown what is the select stmt you use for TableB ?
was it something like "Select top 1 * from TABLEB where revnum=xyz" ?
i ran you logic on dummy data and update/insert is fine.
check this code. what i doubt is in TABLEB you already have some "AlterID" with same "rvnum" value that is not present in "TableA"
see the below result where instead (3,3) i am also seeing (4,3)
SET NOCOUNT ON
DECLARE #t1 TABLE
(
id INT
,VALUE INT
)
INSERT INTO #t1 ( id, VALUE )
SELECT 1,1 UNION ALL SELECT 2,2 UNION ALL SELECT 3,3
DECLARE #t2 TABLE
(
id INT
,VALUE INT
)
INSERT INTO #t2 ( id, VALUE )
SELECT 1,1 UNION ALL SELECT 2,2 UNION ALL SELECT 4,3
DECLARE #Value1 INT,#Value2 INT,#ID INT ,#Prod_curs CURSOR
SET #Prod_curs =CURSOR FOR
SELECT s.id, s.Value, r.value
FROM #t1 s LEFT OUTER JOIN
#t2 r ON s.id=r.ID
WHERE s.Value<>r.Value OR r.Value IS NULL
OPEN #Prod_curs
FETCH NEXT FROM #Prod_curs INTO #ID, #Value1, #Value2
WHILE ##FETCH_STATUS=0
BEGIN
IF #Value2 IS NULL
BEGIN
INSERT INTO #T2 (ID,Value) VALUES (#ID,ISNULL(#Value1,0))
END
IF #Value1<>#Value2
BEGIN
UPDATE #T2 SET Value=#Value2 WHERE ID=#ID
END
FETCH NEXT FROM #Prod_curs INTO #ID, #Value1, #Value2
END
CLOSE #Prod_curs;
DEALLOCATE #Prod_curs;
SELECT TOP 1 * FROM #t2 WHERE VALUE = 3
I've got a #TempTble which looks like this:
Col1,Col2,5,8,19,....
Also, I have another table with description that matches the keys in #TempTble:
key Descr
=== ====
5 Descr1
8 Descr2
19 Descr3
Which is the best way to loop through the #TempTble and rename its columns with the matching descriptions so it looks like this:
Col1,Col2,Descr1,Descr2,Descr3,...
Thanks in advance.
IF object_id('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
declare #map table ([key] sysname,Descr sysname)
INSERT INTO #map
select 5,'Descr1' UNION ALL
select 8,'Descr2' UNION ALL
select 19,'Descr3'
create table #Temp ([Col1] int,[Col2] int,[5] int,[8] int,[19] int)
DECLARE #name nvarchar(1035), #descr sysname;
DECLARE ColumnCursor CURSOR
LOCAL FORWARD_ONLY STATIC READ_ONLY TYPE_WARNING
FOR SELECT 'tempdb..#Temp.' + QUOTENAME(name), Descr
FROM tempdb.sys.columns
JOIN #map m ON m.[key]=name
where object_id=object_id('tempdb..#Temp');
OPEN ColumnCursor;
FETCH NEXT FROM ColumnCursor INTO #name, #descr;
WHILE ##FETCH_STATUS = 0
BEGIN;
EXECUTE tempdb..sp_rename #name, #descr,'COLUMN';
FETCH NEXT FROM ColumnCursor INTO #name, #descr;
END;
CLOSE ColumnCursor;
DEALLOCATE ColumnCursor;
SELECT * FROM #Temp
This is a bad design
One option is to run this code;copy the result and run it again
select 'exec tempdb..sp_rename ''#temp.['+t1.name+']'','''+t2.descrip+''''
from tempdb..syscolumns as t1 inner join mytable as t2 on t1.name=t2.[key]
where id=OBJECT_ID('tempdb..#temp') and t1.name like '[0-9]%'