How to set variables in While Loop in Sql Server - sql-server

I am trying to use while loop instead of CURSOR in SQL SERVER. I am trying to select TOP 1 in while and set them to the variables like below. It doesnt let me set the variables in while loop. What am I doing wrong?
WHILE (
SELECT TOP 1 #WAOR_CODE = WAOR_.WAOR_CODE
, #WAOD_INVENTORYITEMID = WAOD_.WAOD_INVENTORYITEMID
FROM #wmsorder
)
BEGIN
SELECT #WAOR_CODE
, #WAOD_INVENTORYITEMID
DELETE TOP (1) #wmsorder
END

Another option:
WHILE EXISTS(select 1 FROM #wmsorder)
BEGIN
DELETE TOP (1)
FROM #wmsorder
END
However, deleting all records from a table one by one might be a performance hell. You might want to consider using TRUNCATE TABLE instead:
TRUNCATE TABLE #wmsorder
Also, note that each delete is written to the database log, while truncate table doesn't get written to the log at all.
Testing with a temporary table containing 100,000 rows, deleting the rows one by one took me 9 seconds, while truncate table completed immediately:
-- create and populate sample table
SELECT TOP 100000 IDENTITY(int,1,1) AS Number
INTO #wmsorder
FROM sys.objects s1
CROSS JOIN sys.objects s2
-- delete rows one by one
WHILE EXISTS(select 1 FROM #wmsorder)
BEGIN
DELETE TOP (1)
FROM #wmsorder
END
-- clean up
DROP TABLE #wmsorder
-- create and populate sample table
SELECT TOP 100000 IDENTITY(int,1,1) AS Number
INTO #wmsorder
FROM sys.objects s1
CROSS JOIN sys.objects s2
-- truncate the table
TRUNCATE TABLE #wmsorder
-- clean up
DROP TABLE #wmsorder

DECLARE #t TABLE (a INT PRIMARY KEY)
INSERT INTO #t
VALUES (1), (2), (3)
Variant #1:
label:
DELETE TOP(1)
FROM #t
OUTPUT DELETED.a
IF ##ROWCOUNT > 0
GOTO label
Variant #2:
WHILE ##ROWCOUNT != 0
DELETE TOP(1)
FROM #t
OUTPUT DELETED.a
Variant #3:
DECLARE #a TABLE(a INT)
WHILE ##ROWCOUNT != 0 BEGIN
DELETE FROM #a
DELETE TOP(1)
FROM #t
OUTPUT DELETED.a INTO #a
SELECT * FROM #a
END

See the below code. I just corrected the SQL statements shared by you
WHILE 1=1
BEGIN
IF NOT EXISTS (SELECT 1 FROM #wmsorder)
BREAK
SELECT TOP 1 #WAOR_CODE = WAOR_.WAOR_CODE
,#WAOD_INVENTORYITEMID = WAOD_.WAOD_INVENTORYITEMID
FROM #wmsorder WAOR_
SELECT #WAOR_CODE
,#WAOD_INVENTORYITEMID
DELETE #wmsorder WHERE WAOR_CODE = #WAOR_CODE AND WAOD_INVENTORYITEMID = #WAOD_INVENTORYITEMID
END
But as Zohar Peled mentioned, it will be a pain to the engine if you are deleting the records one by one from a table. So below I have shared another query, through this even you can track the records before deleting from the table
DECLARE #TableVar AS TABLE (WAOR_CODE VARCHAR(100), WAOD_INVENTORYITEMID VARCHAR(100))
WHILE 1=1
BEGIN
SELECT TOP 1 #WAOR_CODE = WAOR_.WAOR_CODE
,#WAOD_INVENTORYITEMID = WAOD_.WAOD_INVENTORYITEMID
FROM #wmsorder WAOR_
WHERE NOT EXISTS (SELECT 1 FROM #TableVar t WHERE t.WAOR_CODE = WAOR_.WAOR_CODE AND t.WAOD_INVENTORYITEMID = WAOR_.WAOD_INVENTORYITEMID)
IF #WAOR_CODE IS NULL AND #WAOD_INVENTORYITEMID IS NULL
BREAK
INSERT INTO #TableVar
(WAOR_CODE, WAOD_INVENTORYITEMID)
SELECT #WAOR_CODE
,#WAOD_INVENTORYITEMID
END
DELETE #wmsorder WHERE EXISTS (SELECT 1 FROM #TableVar t WHERE t.WAOR_CODE = #wmsorder.WAOR_CODE AND t.WAOD_INVENTORYITEMID = #wmsorder.WAOD_INVENTORYITEMID)
Sorry I did not test the second code. Please forgive me if it breaks something. But I am pretty sure it may require a small repair to make this query functional. All the best.

Related

Insert OUTPUT into a table with ROWLOCK

I have a temporary table A with 100K items which I need to insert into another table B, so I insert them into table B by batches, in order not to block the table, using the below:
WHILE EXISTS (SELECT TOP 1 * FROM #A)
BEGIN
DELETE TOP (1000) TMP
OUTPUT DELETED.FieldA, DELETED.FieldB
INTO [dbo].[TableB] ([FieldA] ,[FieldB])
FROM #A TMP
END
That works OK but I would like to know if there is a way to specify a table hint with ROWLOCK when inserting into table B?
Like this
WHILE EXISTS (SELECT TOP 1 * FROM #A)
BEGIN
DELETE TOP (1000) TMP
OUTPUT DELETED.FieldA, DELETED.FieldB
INTO [dbo].[TableB] WITH (ROWLOCK) ([FieldA] ,[FieldB])
FROM #A TMP
END
It won't let me now.

Is it possible to use a stored procedure to update three different tables?

I have an proc doing a select and update statements as follows. I need to incorporate the update statements and select statement which put the data into a temp table into a single sql select statement.
Is this possible?
'
Yes:
CREATE TABLE TableA
(
valueA int
)
INSERT INTO
TableA
VALUES
(1),
(2),
(3)
GO
CREATE PROCEDURE test_procedure (#in_value int)
AS
BEGIN
--insert into temp table
SELECT
#in_value [out_value]
INTO
#TestTable
-- update with join on temp table
UPDATE
T
SET
T.[out_value] = 2
FROM
#TestTable T
INNER JOIN TableA A on A.valueA = T.out_value
WHERE
A.valueA = 1
-- update with join on temp table again
UPDATE
T
SET
[out_value] = 3
FROM
#TestTable T
INNER JOIN TableA A on A.valueA = T.out_value
WHERE
A.valueA = 2
--select results including the original "in_value"
SELECT
*,
#in_value [in_value]
FROM
TableA A
LEFT JOIN #TestTable T on T.out_value = A.valueA
END;
GO
--execute stored procedure
EXEC test_procedure 1
I have found one way out by using union.
this is bit wierd but since i am not able to find anything else, settled with this method

How to use two temporary tables with the same name but different structures

Following is what my logic is supposed to do
IF #id = 1
BEGIN
SELECT * INTO #abc from table1
END
IF #id = 2
BEGIN
SELECT * INTO #abc frm table2
END
However, when I execute my statements I get the error saying
there is already an object named #abc..
Any suggestions to overcome this error please?
You can't. The parser doesn't understand your IF logic and it treats both SELECT INTO statements as things that will happen.
What you should do is:
IF #id = 1
BEGIN
SELECT * INTO #abc1 from table1
END
IF #id = 2
BEGIN
SELECT * INTO #abc2 frm table2
END
IF #id = 1
SELECT * FROM #abc1;
ELSE
SELECT * FROM #abc2;
After all, you need to know the different columns that are in the #temp table in order to do anything meaningful with it, right?
(Or avoid temp tables altogether.)
Another possible solution:
CREATE TABLE #abc (
--put schema here
)
IF #id = 1
BEGIN
insert into #abc
select * from table1
END
IF #id = 2
BEGIN
insert into #abc
select * from table2
END
select * from #abc
drop table #abc;
You should always use the column names instead of * because it's better in terms of performance.
And also, select * finds all the columns currently in a table, changes in the structure of a table such as adding, removing, or renaming columns automatically modify the results of select *. Listing columns individually gives you more precise control over the results.

Merge statement on a single record table

I need to write a single statement to insert or update a record in a single record table
the merge statement allows me to write this:
create table t1 (n int)
-- insert into t1 (n) Values (1); -- uncomment to test the matched branch
MERGE t1 AS P
USING (SELECT 3 AS n) AS S
ON 1 = 1
WHEN MATCHED THEN
UPDATE SET n = S.n
WHEN NOT MATCHED THEN
INSERT (n)
VALUES (S.n);
select * from t1
this work, but I think that the 1=1 condition purpose is not very easy to understand.
Is there a different syntax to insert a record when the table is empty or update the record when it does already exist?
The other option would be to do it the old fashioned way.
if exists (select null from t1)
update t1 set n = 3
else
insert into t1 (n) values (3)
Replace
ON 1 = 1
with
ON S.n = P.n
Example of recent procedure I wrote to either update an existing row or insert a new row.
Table has the same structure as MembershipEmailFormat the table variable.
Found it easiest to create a table variable to be the source in the Using clause. I realize that the main purpose of Merge statements really are merging muliple rows between two tables. My use case is that I need to insert a new email address for a user or modify and existing email address.
CREATE PROCEDURE [dbo].[usp_user_merge_emailformat]
#UserID UNIQUEIDENTIFIER,
#Email varchar(256),
#UseHTML bit
AS
BEGIN
--SELECT #UserID='04EFF187-AEAC-408E-9FA8-284B31890FBD',
-- #Email='person#xxxx.com',
-- #UseHTML=0
DECLARE #temp TABLE
(
UserID UNIQUEIDENTIFIER,
Email varchar(256),
HtmlFormat bit
)
INSERT INTO #temp(UserID,Email, HtmlFormat)
Values(#UserID,#Email,#UseHTML)
SELECT * FROM #temp
MERGE dbo.MembershipEmailFormat as t
USING #temp AS s
ON (t.UserID = s.UserID and t.Email = s.Email)
WHEN MATCHED THEN UPDATE SET t.HtmlFormat = s.HtmlFormat
WHEN NOT MATCHED THEN INSERT VALUES(s.UserID,s.Email,s.HtmlFormat);
END

Checking if something has changed in a trigger

I have a need to monitor a subset of fields on a table and perform a task when one of them changes.
I am using a trigger on the table update which and then am looking at the changes as follows:
-- join the deleted and inserted to get a full list of rows
select * into #tmp from (select * from inserted union select * from deleted) un
-- select a count of differing rows, > 1 means something is different
select distinct count(*) from #tmp
This is fine and a count of 2 or more means something is different on single line updates. Issue is if I am doing a multiple line update then this breaks down.
Is there a way I can get this to work for a multi line update or do I need to try a different approach completely.
You could do something like this (syntax completely untested)
IF NOT UPDATE(col)
RETURN
SELECT inserted.key, inserted.col as i_col, deleted.col as d_col
INTO #interestingrows
FROM inserted JOIN deleted on inserted.key = deleted.key
and inserted.col <> deleted.col /*If col is nullable cater for that as well*/
IF ##ROWCOUNT=0
RETURN
/*Process contents of #interestingrows*/
I ended up with a fairly simple solution. I wrote an additional loop around the check that did the check per line in inserted.
-- get a list of updated line id's
select field1 as id into #loop from inserted
-- loop through all the id's and do a compare
while (select count(*) from #loop) > 0 begin
select top 1 #id = id from #loop
select * into #tmp from (select * from inserted where field1 = #id union
select * from deleted where field1 = #id) un
-- do a select ditinct to count the differing lines.
if (select distinct count(*) from #tmp) > 1 begin
-- the 2 lines don't match, so mark for update
update test1 set flag = 1 where field1 = #id
end
drop table #tmp
delete #loop where id = #id
end

Resources