Batch-wise fetch records from cursor - sql

DECLARE table_cursor CURSOR LOCAL FAST_FORWARD READ_ONLY FOR
SELECT column5
FROM testdb.tableA
OPEN table_cursor;
FETCH NEXT FROM table_cursor INTO #m_column5
WHILE ##FETCH_STATUS = 0
BEGIN
//two update queries are here
END
CLOSE table_cursor;
DEALLOCATE table_cursor;
Let's say this returns 10 million records. Can I fetch the results batch-wise in SQL Server 2014?
Is there a better approach I can go for? (considering the performance fact)

It is very hard to figure out what you are trying to do with that cursor.
Based on whatever little you've shared, it seems that what you really want to do is an UPDATE based on a TOP clause:
UPDATE TOP (10) Sales.Store
SET SalesPersonID = 276
WHERE SalesPersonID = 275;
or an UPDATE FROM SELECT if you need a join:
UPDATE [dbo].[Events]
SET [datetime]=SYSDATETIME()
FROM (SELECT TOP 1000 [id] FROM [dbo].[Events] WHERE [datetime]<CONVERT(DATE,SYSDATETIME()) [e2]
INNER JOIN [dbo].[Events] ON [Events].[id] = [e2].[id];
If you find that you really do need a cursor to support your process, you can probably then go with:
WHILE EXISTS (SELECT 1 FROM [dbo].[Events] WHERE [datetime]<CONVERT(DATE,SYSDATETIME())
BEGIN
-- Your logic here
END;

Related

How can I improve this query for performance, provide status, and run as SQL job?

I have two tables, in two different databases. I am using one of the tables to update values in the other database table.
There are over 200,000 rows to iterate through, and it is taking several hours to run, on an Amazon c3.xlarge instance.
Below is the query I am running, and I am wondering three things:
Can this query be optimized to perform faster?
I would like to add a count to get the number of actual records
updated.How?
How can I turn this into a SQL job?
DECLARE #id VARCHAR(12) -- unique id
DECLARE #currentval VARCHAR(64) -- current value
DECLARE #newval VARCHAR(64) -- updated value
DECLARE db_cursor1 CURSOR FOR
SELECT b.[id], a.status, b.[New Status]
FROM db1.dbo.['account'] as b inner join db2.dbo.accounttemp as a on a.ACCOUNTID = b.[ID]
OPEN db_cursor1
FETCH NEXT FROM db_cursor1
INTO #id,
#currentval,
#newval
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE db2.dbo.accounttemp
SET status = #newval
WHERE ACCOUNTID = #id
AND STATUS = #currentval
FETCH NEXT FROM db_cursor1
INTO #id,
#currentval,
#newval
END
CLOSE db_cursor1
DEALLOCATE db_cursor1
By reviewing the procedure, you will see that you can completely remove the cursor using the following SQL
UPDATE db2.dbo.accounttemp
SET status = a.Status
FROM db2.dbo.accounttemp at
INNER JOIN db1.dbo.['account'] AS a ON a.Id = at.[ACCOUNTID]
WHERE a.Status = at.Status
call the following line to return the rows affected by the update
RETURN ##ROWCOUNT
You can create an SQL maintenance plan to run this on scheduled basis

SQL Server avoiding Cursors

I am just reading about SQL Server cursors, that I should avoid them as much as I can :)
Is there ALWAYS a way to write a query/function/procedure without cursors?
I have found some examples on the Net, but they are usually rather simple.
My example - can I avoid cursors?:
Let's have an update procedure X taking an account id and a transaction id - this is the unique key of the row I want to update
But there are more transactions for an account
SELECT accID, transID from table
Now I use a cursor to loop on the table, always taking the accID and transID to get the row and update it
Can I do it a non-cursor way?
Thanks
I was asked for more info about what I want to do, it is a bit too long, so I will add it here as a new comment.. I can not copy the code here, so I am trying to capture at least the base.
My code looked like:
DECLARE #headAcc varchar(20)
set #headAcc='ACC111'
declare #id varchar(20), #clId varchar(20)
declare cur CURSOR for
select addID, transID from Table1
where accID like #headAcc+'%' and...
order by 1 desc
OPEN cur
FETCH NEXT from cur into #accID, #transID
WHILE ##FETCH_STATUS = 0
BEGIN
-- 1
update Table2
set colX = ...
where accID=#accID and transID=#transID and...
-- 2
update Table3
set colY = ...
where accID=#accID and transID=#transID and...
FETCH NEXT from cur into #accID, #transID
END
CLOSE cur
DEALLOCATE cur
Thanks for the help in answers and link in comments, I was not very familiar with UPDATE JOINs, and that should be the answer.
After reading the articles I came up with 2 updates in form like:
DECLARE #headACC varchar(20)
set #headACC='ACC111'
update t2
set t2.colX = ...
from Table2 t2
join Table1 t1
on t1.accID=t2.accID
and t1.transID=t2.transID
where t1.accID like #headAcc+'%'
and...
It seems to work. Any other comments appreciated eg. if there is a more effective way.
See this example from MSDN on how to update one table based on data in other tables:
USE AdventureWorks2012;
GO
UPDATE Sales.SalesPerson
SET SalesYTD = SalesYTD + SubTotal
FROM Sales.SalesPerson AS sp
JOIN Sales.SalesOrderHeader AS so
ON sp.BusinessEntityID = so.SalesPersonID
AND so.OrderDate = (SELECT MAX(OrderDate)
FROM Sales.SalesOrderHeader
WHERE SalesPersonID = sp.BusinessEntityID);
GO

How do I use a simple query to search a list of data from a table and return non-duplicate results in a table?

I am trying to loop through the data in a table, and using the data to search to return the results in another table.
How do I prevent duplicates from adding to the table? Note that the order of the query results adding are very important. So if the results are already added, I don't want them to be added again. Note that the original ranking done by the full search category is misleading, I don't want to use that.
I am using cursor, but I was told it can be solved using simple query; how do I do that?
Below is the code.
...
DECLARE #subQ NVARCHAR(200)
SET #subQ = ''
DECLARE cur1 CURSOR FOR
SELECT combination FROM #Subqueries
OPEN cur1
FETCH NEXT FROM cur1 INTO #subQ
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #Results (app_id, rank, importance)
SELECT app_id, rank, 1
FROM CONTAINSTABLE(dbo.Applications, display_name, #subQ) KEY_TBL
INNER JOIN Applications App
ON KEY_TBL.[KEY] = App.app_id
FETCH NEXT FROM cur1 INTO #subQ
END
CLOSE cur1
DEALLOCATE cur1
...
You used 1 = 1 in while loop. so it will be showing always true condition and resultant is that loop converted to infinite loop.
Change your condition in while loop.
try this while loop
DECLARE #subQ NVARCHAR(200)
DECLARE #mn int
DECLARE #mx int
DECLARE #val varchar(100)
SET #subQ = ''
;WITH CTE as(select ROW_NUMBER() over (order by (select 0)) as rn,* from #Subqueries)
select #mn=MIN(rn),#mx=MAX(rn) from CTE
WHILE #mn>=#mx
BEGIN
select #val=somecolm from CTE where rn=#mn
--do here for each value of any column in CTE
SET #mn=#mn+1
END

SQL while loop with Temp Table

I need to create a temporary table and then update the original table. Creating the temporary table is not a problem.
create table #mod_contact
(
id INT IDENTITY NOT NULL PRIMARY KEY,
SiteID INT,
Contact1 varchar(25)
)
INSERT INTO #mod_contact (SiteID, Contact1)
select r.id, r.Contact from dbo.table1 r where CID = 142
GO
Now I need to loop through the table and update r.contact = SiteID + r.contact
I have never used a while loop before and can't seem to make any examples I have seen work.
You can do this in multiple ways, but I think you're looking for a way using a cursor.
A cursor is sort of a pointer in a table, which when incremented points to the next record. ( it's more or less analogeous to a for-next loop )
to use a cursor you can do the following:
-- DECLARE the cursor
DECLARE CUR CURSOR FAST_FORWARD READ_ONLY FOR SELECT id, siteId, contract FROM #mod_contract
-- DECLARE some variables to store the values in
DECLARE #varId int
DECLARE #varSiteId int
DECLARE #varContract varchar(25)
-- Use the cursor
OPEN CUR
FETCH NEXT FROM CUR INTO #varId, #varSiteId, #varContract
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE dbo.table1
SET contract = #varSiteId + #varContract -- It might not work due to the different types
WHERE id = #varId
FETCH NEXT FROM CUR INTO #varId, #varSiteId, #varContract
END
CLOSE CUR
DEALLOCATE CUR
It's not the most efficient way to get this done, but I think this is what you where looking for.
Hope it helps.
Use a set based approach - no need to loop (from the little details):
UPDATE
r
SET
r.Contact = m.SiteID + r.Contact
FROM
table1 r
INNER JOIN
#mod_contact m
ON m.id=r.id
Your brain wants to do this:
while records
update(i); //update record i
records = records + 1
end while
SQL is set based and allows you to take a whole bunch of records and update them in a single command. The beauty of this is you can use the WHERE clause to filter certain rows that are not needed.
As others have mentioned, learning how to do loops in SQL is generally a bad idea; however, since you're trying to understand how to do something, here's an example:
DECLARE #id int
SELECT #ID =1
WHILE #ID <= (SELECT MAX(ID) FROM table_1)
-- while some condition is true, then do the following
--actions between the BEGIN and END
BEGIN
UPDATE table_1
SET contact = CAST(siteID as varchar(100)) + contact
WHERE table_1.CID = #ID
--increment the step variable so that the condition will eventually be false
SET #ID = #ID + 1
END
--do something else once the condition is satisfied
PRINT 'DONE!! Don't try this in production code...'
Try this one:
-- DECLARE the cursor
DECLARE CUR CURSOR FAST_FORWARD READ_ONLY FOR SELECT column1,column2 FROM table
-- DECLARE some variables to store the values in
DECLARE #varId int
DECLARE #varSiteId int
--DECLARE #varContract varchar(25)
-- Use the cursor
OPEN CUR
FETCH NEXT FROM CUR INTO #varId, #varSiteId
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT *
FROM Table2
WHERE column1 = #varId
AND column2 = #varSiteId
FETCH NEXT FROM CUR INTO #varId, #varSiteId
END
CLOSE CUR
DEALLOCATE CUR
need to create a temporary table and then up date the original table.
Why use a temporary table at all? Your CID column doesn't appear in the temporary table, so I don't see how you can successfully update the original table using SiteID, unless there is only one row where CID = 142 in which using a temp table is definitely overkill.
You can just do this:
UPDATE dbo.table1
SET contact = SiteID + contact
WHERE CID = 142;
Here's a related example which may help getting you to 'think in SQL':
UPDATE T
SET A = B, B = A;
Assuming A and B are of the same type, this would successfully swap their values.

SQL Server 2000 - Breaking out of a loop

I am not good at SQL Server 2000. I have a comma-delimited list of ids. I need to see if that ID exists in a table. If it does, I want to break out of the loop with that ID saved in a variable that I can use in my stored procedure. This is what I am trying right now:
DECLARE #coreID INT
SET #coreID=NULL
DECLARE #itemID NVARCHAR(50)
DECLARE itemCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT [String] AS 'itemID' FROM dbo.SplitListIntoTable(#myIDs)
OPEN itemCursor
FETCH NEXT FROM itemCursor INTO #itemID
WHILE ##FETCH_STATUS = 0 BEGIN
-- If #itemID EXISTS IN MyTable set #coreID=#itemID and Break. How do I do this?
FETCH NEXT FROM itemCursor INTO #itemID
END
CLOSE itemCursor
DEALLOCATE itemCursor
Thank you!
Ideally, you shouldn't use a cursor as performance won't be great. If you can do it as a set-based statement, do that instead, maybe like this:
SELECT TOP 1 #CoreID = [String]
FROM dbo.SplitListIntoTable(#myIDs) x
JOIN MyTable t ON x.[String] = t.ID
However, if you have a real reason to use a cursor, you can use the BREAK statement to break out of a WHILE loop
e.g.
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS(SELECT * FROM MyTable WHERE Id = #ItemID)
BEGIN
SET #CoreId = #ItemId
BREAK
END
FETCH NEXT FROM itemCursor INTO #itemID
END
I don't know how to do this using a cursor, but I supect you can do this much better (faster) with a a join. If the output of dbo.SplitListIntoTable(#myIDs) is actually an odered table, then you can output a table with another column what is say the string numer, 1, 2, 3, etc...
(I don't have sql in front of me to test this but something like)
create table t(itemNum int identity, itemId nvarchar(max))
insert into t (item id) select 1 from dbo.SplitListIntoTable(#myIDs)
Then join the two and take the top one
set #coreID =
select top 1 #itemID
from MyTable m
inner join t t.itemid = m.itemid
order by m.itemNum asc
of course you could use a CTE, table var or temp table too.