Array like functionality in SQL Server 2008 - sql-server

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'

Related

SQL Server : calling a stored procedure using table data

I may be wording this question very poorly but I am not 100% sure what I need to question.
I am trying to iterate over rows in a table and call a stored procedure using the data from the rows.
This is the code I already have, the problem with this is a timing issue (1000 rows takes around 1 minute);
--Set up a temp table with all non email alerts
SELECT TOP(1000)
RowNum = ROW_NUMBER() OVER(ORDER BY AlertID),
a.*, i.ImgData
INTO
#temp
FROM
dbo.ALERTS a
JOIN
dbo.IMAGES i ON i.VehicleID = a.VehicleID
WHERE
a.EmailImageSent = 0 OR a.EmailSent = 0
DECLARE #MaxRownum INT
SET #MaxRownum = (SELECT MAX(RowNum) FROM #temp)
DECLARE #Iter INT
SET #Iter = (SELECT MIN(RowNum) FROM #temp)
DECLARE #ImgData VARBINARY(MAX)
WHILE #Iter <= #MaxRownum
BEGIN
SELECT #VehicleID = VehicleID, #ImgData = ImgData
FROM #temp
WHERE RowNum = #Iter
IF #ImgData IS NOT NULL
BEGIN
EXEC dbo.someProcedure #VehicleID, #ImgData
--SELECT 'Image data found for', #VehicleID, #ImgData
END
SET #Iter = #Iter + 1
END
DROP TABLE #temp
Is there anyway I can run the stored procedure (dbo.someProcedure) while using a set based statement as the input?
Sorry if this has been asked before, I've had a look and couldn't find an answer or if this question isn't informative enough.
Thanks in advance
AFAIK sp_send_dbmail will need to be called once for each email, so either you have a loop here or you have a loop inside dbo.someProcedure.
Still I think that you could make some improvements. Use a FAST_FORWARD cursor rather than creating iteration variables and returning to the table each time to find the next row (thus creating 1000 table scans). Don't store redundant data in your #temp table, only what you need. This makes the table quicker to read.
Try this:
--Set up a temp table with all non email alerts
Create Table #temp (VehicleID int Primary Key Clustered, ImgData varbinary(max));
INSERT INTO #temp (VehicleID, ImgData)
SELECT TOP(1000)
a.VehicleID, i.ImgData
FROM
dbo.ALERTS a
JOIN
dbo.IMAGES i ON i.VehicleID = a.VehicleID
WHERE
a.EmailImageSent = 0 OR a.EmailSent = 0;
DECLARE #VehicleID int;
DECLARE #ImgData VARBINARY(MAX);
DECLARE Alert_Cursor Cursor Fast_Forward For (
Select VehicleID, ImgData From #temp);
OPEN Alert_Cursor;
FETCH NEXT FROM Alert_Cursor INTO #VehicleID, #ImgData;
WHILE ##FETCH_STATUS = 0
BEGIN
IF #ImgData IS NOT NULL
EXEC dbo.someProcedure #VehicleID, #ImgData;
FETCH NEXT FROM Alert_Cursor INTO #VehicleID, #ImgData;
END
CLOSE Alert_Cursor;
DEALLOCATE Alert_Cursor;
DROP TABLE #temp;

Execute SQL Statement more than once like a For Loop

I have a SQL query (normal one). I have to run this query 4 times continuously (like a For Loop in programming). How can I have something like an array and repeat the query execution?
SQL Server
Update :
I am updating some data based on a column TargetLocation. This target location has values from 1 to 5. For each value I need to update the records that have same target location.
If you are running the query in SQL Server Management Studio, then you can use GO N to run a query N times. For example:
insert into MyTable (MyCol) select 'NewRow'
go 4
This will insert 4 rows into MyTable with the text 'NewRow' in them.
If you really need to loop over something in another application, then I recommend using the while loop as suggested by Peter Tirrell.
Note that loops are usually unnecessary in SQL. They may indicate code that is written with procedural logic instead of set-based logic.
Something like a simple SQL WHILE loop?
declare #counter int
set #counter = 0
while #counter < 10
begin
select 'foo'
set #counter = #counter + 1
end
I think you want a join in your UPDATE, as in:
--create two sample tables that we can work on
declare #tabletoupdate table(ID int,TARGETLOCATION int);
declare #sourcetable table(ID int,SOURCELOCATION int);
--drop in sample data
insert into #tabletoupdate select 1,10 union select 2,20 union select 3, 30;
insert into #sourcetable select 1,100 union select 2,200 union select 3, 300;
--see the 'before'
select * from #tabletoupdate
select * from #sourcetable
--make target look like source
update #tabletoupdate
set
targetlocation = s.sourcelocation
from
#tabletoupdate t
inner join #sourcetable s on s.id = t.id;
--show 'after'
select * from #tabletoupdate
select * from #sourcetable
/*
--if you really insist on doing it with a loop
--bad because its
--1) slower
--2) less readable
--3) less reliable when other users are accessing the data
declare #currentID int = 0;
declare #maxID int = (select max(id) from #sourcetable);
while #currentID < #maxID
begin
set #currentID = #currentID + 1;
declare #newval int = (select sourcelocation
from #sourcetable
where id = #currentID
);
if #newval is not null
begin
update #tabletoupdate
set TARGETLOCATION = #newval
where id = #currentID;
end
end
--*/

Performance hit of having many Result Set of a single row

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]

tsql looping issue, duplicate records are being inserted

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.

Using row count from a temporary table in a while loop SQL Server 2008

I'm trying to create a procedure in SQL Server 2008 that inserts data from a temp table into an already existing table. I think I've pretty much figured it out, I'm just having an issue with a loop. I need the row count from the temp table to determine when the loop should finish.
I've tried using ##ROWCOUNT in two different ways; using it by itself in the WHILE statement, and creating a variable to try and hold the value when the first loop has finished (see code below).
Neither of these methods have worked, and I'm now at a loss as to what to do. Is it possible to use ##ROWCOUNT in this situation, or is there another method that would work better?
CREATE PROCEDURE InsertData(#KeywordList varchar(max))
AS
BEGIN
--create temp table to hold words and weights
CREATE TABLE #tempKeywords(ID int NOT NULL, keyword varchar(10) NOT NULL);
DECLARE #K varchar(10), #Num int, #ID int
SET #KeywordList= LTRIM(RTRIM(#KeywordList))+ ','
SET #Num = CHARINDEX(',', #KeywordList, 1)
SET #ID = 0
--Parse varchar and split IDs by comma into temp table
IF REPLACE(#KeywordList, ',', '') <> ''
BEGIN
WHILE #Num > 0
BEGIN
SET #K= LTRIM(RTRIM(LEFT(#KeywordList, #Num - 1)))
SET #ID = #ID + 1
IF #K <> ''
BEGIN
INSERT INTO #tempKeywords VALUES (#ID, #K)
END
SET #KeywordList = RIGHT(#KeywordList, LEN(#KeywordList) - #Num)
SET #Num = CHARINDEX(',', #KeywordList, 1)
--rowcount of temp table
SET #rowcount = ##ROWCOUNT
END
END
--declaring variables for loop
DECLARE #count INT
DECLARE #t_name varchar(30)
DECLARE #key varchar(30)
DECLARE #key_weight DECIMAL(18,2)
--setting count to start from first keyword
SET #count = 2
--setting the topic name as the first row in temp table
SET #t_name = (Select keyword from #tempKeywords where ID = 1)
--loop to insert data from temp table into Keyword table
WHILE(#count < #rowcount)
BEGIN
SET #key = (SELECT keyword FROM #tempKeywords where ID = #count)
SET #key_weight = (SELECT keyword FROM #tempKeywords where ID = #count+2)
INSERT INTO Keyword(Topic_Name,Keyword,K_Weight)
VALUES(#t_name,#key,#key_weight)
SET #count= #count +2
END
--End stored procedure
END
To solve the second part of your problem:
INSERT INTO Keyword(Topic_Name,Keyword,K_Weight)
SELECT tk1.keyword, tk2.keyword, tk3.keyword
FROM
#tempKeywords tk1
cross join
#tempKeywords tk2
inner join
#tempKeywords tk3
on
tk2.ID = tk3.ID - 1
WHERE
tk1.ID = 1 AND
tk2.ID % 2 = 0
(This code should replace everything in your current script from the --declaring variables for loop comment onwards)
You could change:
WHILE(#count < #rowcount)
to
WHILE(#count < (select count(*) from #tempKeywords))
But like marc_s commented, you should be able to do this without a while loop.
I'd look at reworking your query to see if you can do this in a set based way rather than row by row.
I'm not sure I follow exactly what you are trying to achieve, but I'd be tempted to look at the ROW_NUMBER() function to set the ID of your temp table. Used with a recursive CTE such as shown in this answer you could get an id for each of your non empty trimmed words. An example is something like;
DECLARE #KeywordList varchar(max) = 'TEST,WORD, ,,,LIST, SOME , WITH, SPACES'
CREATE TABLE #tempKeywords(ID int NOT NULL, keyword varchar(10) NOT NULL)
;WITH kws (ord, DataItem, Data) AS(
SELECT CAST(1 AS INT), LEFT(#KeywordList, CHARINDEX(',',#KeywordList+',')-1) ,
STUFF(#KeywordList, 1, CHARINDEX(',',#KeywordList+','), '')
union all
select ord + 1, LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), '')
from kws
where Data > ''
), trimKws(ord1, trimkw) AS (
SELECT ord, RTRIM(LTRIM(DataItem))
FROM kws
)
INSERT INTO #tempKeywords (ID, keyword)
SELECT ROW_NUMBER() OVER (ORDER BY ord1) as OrderedWithoutSpaces, trimkw
FROM trimKws WHERE trimkw <> ''
SELECT * FROM #tempKeywords
I don't fully understand what you are trying to acheive with the second part of your query , but but you could just build on this to get the remainder of it working. It certainly looks as though you could do what you are after without while statements at least.

Resources