tsql looping issue, duplicate records are being inserted - sql-server

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.

Related

Loop through a list of Ids and perform a dynamic insert statement

I want to loop through a list of country IDs and perform an insert statement for each one.
I need a loop where each country ID is accessible as a variable in the loop that I can concatenate into a dynamic SQL query
CREATE TABLE [dbo].countryIds(
CountryId INT IDENTITY(1,1)
)
SET IDENTITY_INSERT [dbo].countryIds ON
INSERT [dbo].countryIds (CountryId) VALUES (8)
INSERT [dbo].countryIds (CountryId) VALUES (13)
SET IDENTITY_INSERT [dbo].countryIds OFF
WHILE EXISTS (SELECT CountryId FROM CountryIds)
BEGIN
-- INSERT INTO anotherTable custom sql where country ID = CountryId
END
I tried a while exists loop, but this loops infinitely.
How can I achieve this?
Try to use cursor instead while exists https://learn.microsoft.com/en-us/sql/t-sql/language-elements/declare-cursor-transact-sql
CREATE TABLE [dbo].countryIds(
CountryId INT IDENTITY(1,1)
)
SET IDENTITY_INSERT [dbo].countryIds ON
INSERT [dbo].countryIds (CountryId) VALUES (8)
INSERT [dbo].countryIds (CountryId) VALUES (13)
SET IDENTITY_INSERT [dbo].countryIds OFF
declare #CountryId int
declare country_cursor cursor for
select CountryId from CountryIds
open country_cursor
fetch next from country_cursor
into #CountryId
while ##fetch_status = 0
begin
--INSERT INTO anotherTable custom sql where country ID = CountryId
fetch next from country_cursor
into #CountryId
end
close country_cursor
deallocate country_cursor
Using loops in SQL is a bad practice, work with multiple rows:
INSERT INTO anotherTable (...)
SELECT ...
FROM anotherTable a
INNER JOIN countryIds
WHERE countryID = CountryId
First of all I recommend manual transactions to see if everything goes fine before doing actual commit:
BEGIN TRAN;
SQL
ROLLBACK or COMMIT;
Now to your question:
This should be done via cursor (the following is a simple cursor example)
DECLARE #CountryIds int
-- the declaration of the cursor itselfdeclare a cursor
DECLARE cursor_for_insert CURSOR FOR
SELECT CountryId FROM CountryIds
-- open the cursor and fetch first row into variable
OPEN cursor_for_insert
FETCH NEXT FROM insert_cursor INTO CountryIds
-- run till there is a row to get
WHILE ##FETCH_STATUS=0
BEGIN
-- do your insert here
-- INSERT INTO anotherTable custom sql where country ID = CountryId
END
-- don't forget to close it an unallocate it otherwise it will be still allocated!
CLOSE cursor_for_insert
DEALLOCATE cursor_for_insert
GO

Insert random text into columns from a reference table variable

I have a table ABSENCE that has 40 employee ids and need to add two columns from a table variable, which acts as a reference table. For each emp id, I need to randomly assign the values from the table variable. Here's the code I tried without randomizing:
USE TSQL2012;
GO
DECLARE #MAX SMALLINT;
DECLARE #MIN SMALLINT;
DECLARE #RECODE SMALLINT;
DECLARE #RE CHAR(100);
DECLARE #rearray table (recode smallint,re char(100));
insert into #rearray values (100,'HIT BY BEER TRUCK')
,(200,'BAD HAIR DAY')
,(300,'ASPIRIN OVERDOSE')
,(400,'MAKEUP DISASTER')
,(500,'GOT LOCKED IN THE SALOON')
DECLARE #REFCURSOR AS CURSOR;
SET #REFCURSOR = CURSOR FOR
SELECT RECODE,RE FROM #REARRAY;
OPEN #REFCURSOR;
SET #MAX = (SELECT DISTINCT ##ROWCOUNT FROM ABSENCE);
SET #MIN = 0;
ALTER TABLE ABSENCE ADD CODE SMALLINT, REASONING CHAR(100);
WHILE (#MIN <= #MAX)
BEGIN
FETCH NEXT FROM #REFCURSOR INTO #RECODE,#RE;
INSERT INTO ABSENCE (CODE, REASONING) VALUES (#RECODE,#RE);
SET #MIN+=1;
END
CLOSE #REFCURSOR
DEALLOCATE #REFCURSOR
SELECT EMPID,CODE,REASONING FROM ABSENCE
Though am inserting into two columns only, it is attempting to insert into empid (which has already been filled) and as it cannot be NULL, the insertion fails.
Also, how to randomize the values from the REARRAY table variable to insert them into the ABSENCE table?
Since this is a small dataset, one approach might be to use CROSS APPLY with a SELECT TOP(1) ... FROM #rearray ORDER BY NEWID() approach. This will essentially join your ABSENCE table with your reference table in an UPDATE statement, selecting a random row each time in the join. In full, it would look like:
UPDATE ABSENCE
SET col1 = x1.recode, col2 = x2.recode
FROM ABSENCE a
CROSS APPLY (SELECT TOP(1) * FROM #rearray ORDER BY NEWID()) x1(recode, re)
CROSS APPLY (SELECT TOP(1) * FROM #rearray ORDER BY NEWID()) x2(recode, re)

How to use CTE instead of Cursor

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

INSERT INTO View, INSTEAD OF Trigger, Identity, multiple tables?

Here as my tables (Entier = Integer // Caractère long variable = Varchar) :
http://i.stack.imgur.com/lNjyy.jpg
I created a view V_Enterprise(idContact, phoneNumber, email, name, city, adress)
I tried to create a Trigger on that View to allow users to update the view :
CREATE TRIGGER test
ON V_Entreprise
INSTEAD OF INSERT
AS
DECLARE #T_ContactId INT
BEGIN
INSERT INTO T_Contact
SELECT i.phoneNumber, i.email
FROM Inserted i
SELECT #T_ContactId = ##IDENTITY
INSERT INTO T_Entreprise
SELECT #T_ContactId, i.name, i.city, i.adress
FROM Inserted i
END ;
As I expected, it work on simple inserts, but when I add couples of rows at once, it fails because #T_ContactId only contains the first id. Can someone help me to fix it ? I feel like I should use INNER JOIN inserts but I can't figure out how to deal with it.
OK you should never set scalar variables to a value in inserted or delted in a trigger.
Use the OUTPUT clause instead to get your id values back.
This trigger uses a loop over a cursor and won't require any particular uniqueness in the tables;
CREATE TRIGGER test
ON V_Enterprise
INSTEAD OF INSERT
AS
BEGIN
DECLARE #name VARCHAR(32)
DECLARE #city VARCHAR(32)
DECLARE #address VARCHAR(32)
DECLARE #pn VARCHAR(32)
DECLARE #email VARCHAR(32)
DECLARE cursor1 CURSOR FOR
SELECT name,city,address,phoneNumber,email FROM inserted;
OPEN cursor1;
FETCH NEXT FROM cursor1 INTO #name, #city, #address, #pn, #email;
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO T_Contact (phoneNumber,email) VALUES (#pn, #email);
INSERT INTO T_Enterprise (idcontact,name,city,address) VALUES
(##IDENTITY,#name,#city,#address);
FETCH NEXT FROM cursor1 INTO #name, #city, #address, #pn, #email;
END
CLOSE cursor1;
DEALLOCATE cursor1;
END
GO
I don't know if this is a good way to do it, but you can do this without relying on unique columns or using a cursor using the OUTPUT clause for INSERT. This approach does make use of an in-memory temporary table that could get big with large inserts.
DECLARE #Table table( NewID BIGINT);
INSERT INTO T_Contact (PhoneNumber)
OUTPUT Inserted.ID
INTO #Table
SELECT PhoneNumber FROM inserted WHERE
;
INSERT INTO T_Enterprise (Contact_ID)
SELECT NewID FROM #Table;
If phoneNumber and email are a unique key in T_Contact then you could do this:
CREATE TRIGGER test
ON V_Entreprise
INSTEAD OF INSERT
AS
DECLARE #T_ContactId INT
BEGIN
INSERT INTO T_Contact
SELECT i.phoneNumber, i.email
FROM Inserted i
SELECT #T_ContactId = ##IDENTITY
INSERT INTO T_Entreprise
SELECT
(SELECT idContact FROM T_Contact
WHERE phoneNumber = i.phoneNumber AND email = i.email),
i.name, i.city, i.adress
FROM Inserted i
END ;

Array like functionality in SQL Server 2008

I want to read EmpID in EMP Table based on some condition. For every EmpID I need to do some operation in another table. How can I read single value of EmpID at a time.
Thanks in advance
UPDATE otherTable...
WHERE table2.EmpID IN (SELECT EMP.EmpID FROM EMP WHERE ...)
try to never loop, work on sets of data.
you can insert, update, delete multiple rows at one time. here in an example insert of multiple rows:
INSERT INTO YourTable
(col1, col2, col3, col4)
SELECT
cola, colb+Colz, colc, #X
FROM ....
LEFT OUTER JOIN ...
WHERE...
you can even insert into multiple tables in a single statement:
INSERT INTO YourTable
(col1, col2, col3, col4)
OUTPUT INSERTED.PK, Inserted.Col2
INTO OtherTable (ColA, ColB)
SELECT
cola, colb+Colz, colc, #X
FROM ....
LEFT OUTER JOIN ...
WHERE...
When looking at a loop see what it done inside it. If it is just inserts/deletes/updates, re-write to use single commands. If there are IFs, see if those can be CASE statements or WHERE conditions on inserts/deletes/updates. If so, remove the loop and use set commands.
I've taken loops and replaced them with the set based commands and reduced the execution time from minutes to a few seconds. I have taken procedures with many nested loops and procedure calls and kept the loops (was impossible to only use inserts/deletes/updates), but I removed the cursor, and have seen less locking/blocking and massive performance boosts as well. Here are two looping methods that are better than cursor loops...
if you have to loop, over a set do something like this:
--this looks up each row for every iteration
DECLARE #msg VARCHAR(250)
DECLARE #hostname sysname
--first select of currsor free loop
SELECT #hostname= min(RTRIM(hostname))
FROM master.dbo.sysprocesses (NOLOCK)
WHERE hostname <> ''
WHILE #hostname is not null
BEGIN
--just some example of some odd task that requires a loop
set #msg='exec master.dbo.xp_cmdshell "net send '
+ RTRIM(#hostname) + ' '
+ 'testing "'
print #msg
--EXEC (#msg) --<<will not actually send the messages
--next select of cursor free loop
SELECT #hostname= min(RTRIM(hostname))
FROM master.dbo.sysprocesses (NOLOCK)
WHERE hostname <> ''
and hostname > #hostname
END
if you have a reasonable set of items (not 100,000) to loop over you can do this:
--this will capture each Key to loop over
DECLARE #msg VARCHAR(250)
DECLARE #From int
DECLARE #To int
CREATE TABLE #Rows --use a table #variable depending on the number of rows to handle
(
RowID int not null primary key identity(1,1)
,hostname varchar(100)
)
INSERT INTO #Rows
SELECT DISTINCT hostname
FROM master.dbo.sysprocesses (NOLOCK)
WHERE hostname <> ''
SELECT #From=0,#To=##ROWCOUNT
WHILE #From<#To
BEGIN
SET #From=#From+1
--just some example of some odd task that requires a loop
SELECT #msg='exec master.dbo.xp_cmdshell "net send '
+ RTRIM(hostname) + ' '
+ 'testing "'
FROM #Rows
WHERE RowID=#From
print #msg
--EXEC (#msg) --<<will not actually send the messages
END
Using a set based approach to SQL logic is always the preferred approach. In this sense DanDan's is an acceptable response.
Alternatively you could use SQL cursors. Although resource heavy they will allow you iterate through a set and apply some logic on each row.
DECLARE #EMPID char(11)
DECLARE c1 CURSOR READ_ONLY
FOR
SELECT EmpID
FROM EMP
WHERE *some_clause*
OPEN c1
FETCH NEXT FROM c1
INTO #EMPID
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #EMPID
FETCH NEXT FROM c1
INTO #EMPID
END
CLOSE c1
DEALLOCATE c1
Generally, you should avoid procedural code in SQL, but if you really need to, use CURSOR:
DECLARE myCursor CURSOR FAST_FORWARD
FOR
SELECT --your SQL query, a regular SQL query.
field1,
field2
FROM
table
OPEN myCursor;
FETCH NEXT FROM myCursor
INTO
#var1, --must be pre-declared, of the same types as field1
#var2
WHILE (##FETCH_STATUS = 0)
BEGIN
--your code use #var1, #var2. Perform queries, do whatever you like.
--It will loop through every row fetched by the query in the beginning of the code, and perform this.
FETCH NEXT FROM myCursor --do this exactly as before the WHILE loop
INTO
#var1,
#var2
END
CLOSE myCursor
Following on from DanDan's Answer, T-SQL allows you to do join in the FROM clause of an UPDATE statement (I can't remember if this is ANSI or not). EG
UPDATE
OtherTable
SET
Auditing = Employees.EmployeeName
FROM
OtherTable
INNER JOIN
Employees ON OtherTable.EmpId = Employees.EmpId
WHERE
Employees.DateStarted > '2010-09-01'

Resources