I have a table "OFICIAL3" with 500k rows. and 30 columns. and table INSIS with 150k rows and 20 columns.
OFICIAL3.NUMERO_TITULO has an index.
INSIS.NumeroDocumento has an index too.
update sentence take long time. this process will take 9 hours in my machine
my machine is a core 2 duo 2.GHZ and 2GB RAM
ALTER PROCEDURE [dbo].[CompletarDatos] AS
declare #cantidad int;
declare #CONTADOR int;
declare #NRO_TITULO VARCHAR(600);
declare #POYECTO VARCHAR(200);
DECLARE #I_PROYECTO VARCHAR(500);
DECLARE #I_AREA_INT VARCHAR(500);
SET NOCOUNT ON
BEGIN
SET #cantidad =(select count(*) from OFICIAL3)
SET #CONTADOR=1
declare CURSORITO cursor for
select NUMERO_TITULO from OFICIAL3
open CURSORITO
fetch next from CURSORITO
into #NRO_TITULO
while ##fetch_status = 0
begin
SET #CONTADOR=#CONTADOR+1
PRINT 'ROW='+CONVERT(NVARCHAR(30),#CONTADOR)+' NRO TITULO='+#NRO_TITULO
SET #I_PROYECTO = (SELECT PROYECTO FROM INSIS WHERE NumeroDocumento=#NRO_TITULO)
SET #I_AREA_INT = (SELECT I_AREA_INTERVENCION FROM INSIS WHERE NumeroDocumento=#NRO_TITULO)
UPDATE OFICIAL3 SET PROYECT=#I_PROYECTO , COD_AREA=#I_AREA_INT WHERE NUMERO_TITULO=#NRO_TITULO
fetch next from CURSORITO into #NRO_TITULO
end
-- cerramos el cursor
close CURSORITO
deallocate CURSORITO
END
Assuming OFICIAL4 is a typo, this should work as a single update:
UPDATE o
SET PROYECT = i.PROYECTO,
COD_AREA = i.I_AREA_INTERVENCION
FROM OFICIAL3 o
INNER JOIN
INSIS i
ON o.NUMERO_TITULO = i.NumeroDocumento
As others have commented, an approach that avoids the CURSOR is vastly preferable from a performance point of view. Another thought is that a covering index on `INSIS (NumeroDocumento, PROYECTO, I_AREA_INTERVENCION) would speed things up further for this query.
Is there any way you can do this without a cursor? Removing the iteration should help it considerably.
Related
I wanted to create a new set of tables in a db using a stored procedure and a build table.
The build table includes the following columns and some sample rows:
tblNm colNm colTyp colLen colReq colWarning colUni colComUni
account personID Decimal NULL 0 0 0 0
account studentNum String 15 0 0 0 0
I was considering using multiple cursors as a form of nested looping, but I cannot figure out how to define the column parameters in the nested procedure because cursors only return one value.
I am considering to build an alter statement that parses these values. How could I do this?
You can solve the problem by using two cursors or one cursor. Two cursors will make the code more readable. One cursor will be more efficient.
Two cursors
The code below demonstrates how to use two cursors to iterate through the tables and columns.
DECLARE #tblNm VARCHAR(MAX)
DECLARE cTables CURSOR FOR
SELECT tblNm
FROM CompositeSchema
GROUP BY tblNm
ORDER BY tblNm
OPEN cTables
FETCH cTables INTO #tblNm
WHILE ##FETCH_STATUS=0
BEGIN
PRINT 'Processing table '+#tblNm
-- Start of code to execute for each table
DECLARE #sqlCreateTable VARCHAR(MAX)
SET #sqlCreateTable = 'CREATE TABLE ['+#tblNm+'] ('
DECLARE #colNm VARCHAR(MAX),#colTyp VARCHAR(MAX),#colLen INT,#colReq BIT,#colWarning BIT,#colUni BIT,#colComUni BIT
DECLARE #isFirst BIT
SET #isFirst = 1
DECLARE cCols CURSOR FOR
SELECT colNm,colTyp,colLen,colReq,colWarning,colUni,colComUni
FROM CompositeSchema
WHERE tblNm=#tblNm
ORDER BY colComUni DESC,colNm ASC
OPEN cCols
FETCH cCols INTO #colNm,#colTyp,#colLen,#colReq,#colWarning,#colUni,#colComUni
WHILE ##FETCH_STATUS=0
BEGIN
PRINT 'Processing column ['+#tblNm+'].['+#colNm+']'
-- Start of code to process each column (simplified!)
IF #isFirst=0
SET #sqlCreateTable = #sqlCreateTable+','
SET #isFirst = 0
SET #sqlCreateTable = #sqlCreateTable+'['+#colNm+'] '+#colTyp
IF NOT #colLen IS NULL
SET #sqlCreateTable = #sqlCreateTable+'('+CAST(#colLen AS VARCHAR)+')'
-- End of code to process each column
FETCH cCols INTO #colNm,#colTyp,#colLen,#colReq,#colWarning,#colUni,#colComUni
END
CLOSE cCols
DEALLOCATE cCols
SET #sqlCreateTable = #sqlCreateTable+')'
PRINT #sqlCreateTable
-- EXEC(#sqlCreateTable)
-- End of code to execute for each table
FETCH cTables INTO #tblNm
END
CLOSE cTables
DEALLOCATE cTables
One cursor
In this case we use just one cursor. We keep track of what the current table is that we are processing in the #currentTblNm variable. Whenever the variable changes, we create all columns at once.
DECLARE #currentTblNm VARCHAR(MAX),#sqlCreateTable VARCHAR(MAX)
SET #currentTblNm = ''
DECLARE #tblNm VARCHAR(MAX),#colNm VARCHAR(MAX),#colTyp VARCHAR(MAX),#colLen INT,#colReq BIT,#colWarning BIT,#colUni BIT,#colComUni BIT
DECLARE #isFirst BIT
SET #isFirst = 1
DECLARE cCols CURSOR FOR
SELECT tblNm,colNm,colTyp,colLen,colReq,colWarning,colUni,colComUni
FROM CompositeSchema
ORDER BY tblNm ASC,colComUni DESC,colNm ASC
OPEN cCols
FETCH cCols INTO #tblNm,#colNm,#colTyp,#colLen,#colReq,#colWarning,#colUni,#colComUni
WHILE ##FETCH_STATUS=0
BEGIN
IF #currentTblNm<>#tblNm
BEGIN
IF #sqlCreateTable<>''
BEGIN
SET #sqlCreateTable = #sqlCreateTable+')'
PRINT #sqlCreateTable
--EXEC (#sqlCreateTable)
END
SET #isFirst = 1
SET #sqlCreateTable = 'CREATE TABLE ['+#tblNm+'] ('
SET #currentTblNm = #tblNm
PRINT 'Processing table ['+#tblNm+']'
END
-- Start of code to process each column (simplified!)
IF #isFirst=0
SET #sqlCreateTable = #sqlCreateTable+','
SET #isFirst = 0
SET #sqlCreateTable = #sqlCreateTable+'['+#colNm+'] '+#colTyp
IF NOT #colLen IS NULL
SET #sqlCreateTable = #sqlCreateTable+'('+CAST(#colLen AS VARCHAR)+')'
-- End of code to process each column
FETCH cCols INTO #tblNm,#colNm,#colTyp,#colLen,#colReq,#colWarning,#colUni,#colComUni
END
CLOSE cCols
DEALLOCATE cCols
IF #sqlCreateTable<>''
BEGIN
SET #sqlCreateTable = #sqlCreateTable+')'
PRINT #sqlCreateTable
-- EXEC(#sqlCreateTable)
END
Both pieces of code, Two cursors and One cursor are simplified. The logic to properly create all of the constraints (like primary key, unique constraints, foreign keys etc.), the logic to properly map the column data types, and not to forget, the making the distinction between creating a new table and altering an existing table is beyond the scope of this post.
Worth mentioning is that you could also use declarative SQL code with FOR XML to create the table structure. This is possible and would be able to generate the CREATE TABLE statements with a much better performance. From experience I know that this code will be much harder to maintain and you might run into the limitations of declarative SQL.
Here is my procedure, I don't know how to use bulk collecton in cursor, that we can batch process the cursor data. Please help me, thanks!
CREATE PROCEDURE PROC_AUTOACTIVE
BEGIN ATOMIC
DECLARE v_sql VARCHAR(800);
DECLARE v_customer_id BIGINT;
DECLARE v_cardnum varchar(500);
DECLARE v_cardtype varchar(20);
DECLARE v_status varchar(10);
DECLARE v_lastname varchar(200);
DECLARE v_email varchar(150);
DECLARE v_mobile varchar(30);
DECLARE v_phone varchar(30);
DECLARE v_zipcode varchar(20);
DECLARE v_crm_mobile varchar(30);
DECLARE v_address varchar(500);
DECLARE v_order_count BIGINT;
DECLARE v_order_no varchar(500);
DECLARE not_found CONDITION FOR SQLSTATE '02000';
DECLARE at_end INT DEFAULT 0;
DECLARE c_customers CURSOR FOR s_cardsinfo;
DECLARE CONTINUE HANDLER FOR not_found SET at_end = 1;
SET v_sql = 'select t.customer_id, v.CUSTOMER_ID, v.CARD_TYPE, v.STATUS
from customer_tempcard t,
vip_fields v
where t.tempcard_num=v.CUSTOMER_ID
and t.status=1
and v.STATUS=1
and exists (select id
from orders o
where o.FK_CUSTOMER=t.CUSTOMER_ID
and o.FK_ORDER_STATUS in (3,4,6)) ';
PREPARE s_cardsinfo FROM v_sql;
OPEN c_customers;
--fetch card info
LOOP_CUSTOMER_INFO:
LOOP
FETCH c_customers INTO v_customer_id,v_cardnum,v_cardtype,v_status;
IF at_end <> 0 THEN
SET at_end = 0;
LEAVE LOOP_CUSTOMER_INFO;
END IF;
select c.LOGON_ID, o.DEV_CUSTOMER_NAME,
o.DEV_MOBILE, o.DEV_PHONE, o.DEV_ZIP, o.DEV_ADDRESS, o.ORDER_NO
into v_email, v_lastname,
v_mobile, v_phone, v_zipcode, v_address, v_order_no
from orders o,customer c
where o.FK_CUSTOMER=c.ID
and o.FK_CUSTOMER=v_customer_id
and o.FK_ORDER_STATUS in (3,4,6)
order by o.ID desc
fetch first 1 rows only;
IF v_mobile <> null THEN
SET v_crm_mobile = v_mobile;
ELSE
SET v_crm_mobile = v_phone;
END IF;
update customer_tempcard ct
set ct.STATUS='0',
ct.UPDATE_TIME=current_timestamp
where ct.CUSTOMER_ID=v_customer_id;
update card_store cs
set cs.STATUS='0',
cs.UPDATE_TIME=current_timestamp
where cs.CARD_NUM=v_cardnum;
update vip_fields v
set v.LAST_NAME=v_lastname,
v.EMAIL=v_email, v.MOBILE=v_crm_mobile,
v.CUSTOMER_UPDATE_TIME=current_timestamp,
v.UPDATE_TIME=current_timestamp,
v.OPERATION_TYPE='2',
v.CREATE_SOURCE='2',
v.STATUS='0',
v.ZIP_CODE=v_zipcode,
v.ADDRESS=v_address
where customer_id = v_cardnum;
update customer c
set c.VIP_CARD_NUMBER=v_cardnum,
c.VIP_CARD_NAME=v_lastname,
c.VIP_EMAIL=v_email,
c.VIP_CARD_TYPE=v_cardtype,
c.LEVEL=v_cardtype,
c.VIP_ZIP=v_zipcode,
c.VIP_MOBILE=v_crm_mobile,
c.VIP_ADDRESS=v_address,
c.FK_CUSTOMER_GRADE='1'
where c.id=v_customer_id;
insert into beactiveinfo
values (default,v_cardnum,v_order_no,current_timestamp);
END LOOP;
CLOSE c_customers;
END
BULK COLLECT is part of the Oracle compatibility feature in DB2, so, firstly, you cannot use it in the DB2 SQL PL native context, which you are using in your procedure. Secondly, you don't use BULK COLLECT in a cursor. You use SELECT ... BULK COLLECT INTO an_array_variable ... to populate a PL/SQL array. If you intend then to loop over that array, you won't get any performance benefit over the cursor, while incurring the memory overhead for storing the entire result set in the application memory.
I'm in the middle of writing an achievement module for a website we run. This particular bit of code will run on SQL Server at the end of a given timeframe to award the achievements (which will be notified through the client the next time the site is visited).
So I need to look at all the teams and all the "end" achievements (which are independent of the teams). Each achievement has one or more rules that must be met. Once I've determined that the achievement is passed, I award the achievement to all of the participants of that team.
I was handling this through cursors, and I got an error, so when I went to google the problem, I got endless links on forums of "YOU DUMB $&#$ WHY ARE YOU USING CURSORS" (to paraphrase). I figured while I was solving my problem, I may as well replace what I have using a set-based approach (if possible).
The alternative examples I found were all basic update scenarios using single nested loops on tables that have keys back to each other, and honestly, I wouldn't have considered using cursors in those scenarios to begin with.
What I have below is not entirely syntactically correct, but I can figure that out on my own by playing around. This should give a clear idea of what I'm trying to accomplish, however:
declare #TimeframeID int;
declare #AchievementID int;
declare #q1 int;
declare #q2 int;
declare #TeamID int;
declare #TotalReg int;
declare #TotalMin int;
declare #AvgMin decimal(10, 2);
declare #AggType varchar(50);
declare #Pass bit = 1;
declare #Email varchar(50);
declare #ParticipantID int;
select #TimeframeID = MAX(TimeframeID) from Timeframes
where IsActive = 1;
declare team_cur CURSOR FOR
select
t.TeamID,
(select COUNT(1) from Registrations r
where r.TeamID = t.TeamID and r.TimeframeID = #TimeframeID) TotalReg,
(select SUM(Minutes) from Activities a
inner join Registrations r on r.RegistrationID = a.RegistrationID
where r.TeamID = t.TeamID and r.TimeframeID = #TimeframeID) TotalMin
from Teams t
where Active = 1
group by TeamID;
declare ach_cur CURSOR FOR
select AchievementID from luAchievements
where TimeframeID = #TimeframeID and AchievementType = 'End';
declare rule_cur CURSOR for
select Quantity1, Quantity2, AggregateType
from AchievementRule_Links arl
inner join luAchievementRules ar on ar.RuleID = arl.RuleID
where arl.AchievementID = #AchievementID;
open team_cur;
fetch next from team_cur
into #TeamID, #TotalReg, #TotalMin;
while ##FETCH_STATUS = 0
begin
open ach_cur;
fetch next from ach_cur
into #AchievementID;
while ##FETCH_STATUS = 0
begin
open rule_cur;
fetch next from rule_cur
into #q1, #q2, #AggType;
while ##FETCH_STATUS = 0
begin
if #AggType = 'Total'
begin
if #q1 > #TotalReg
begin
set #Pass = 0;
end
end
else if #AggType = 'Average'
begin
print 'do this later; need to get specs';
end
fetch next from rule_cur
into #q1, #q2, #AggType;
end
close rule_cur;
deallocate rule_cur;
-- if passed, award achievement to all team members
if #Pass = 1
begin
declare reg_cursor cursor for
select max(p.Email) from Participants p
inner join Registrations reg on reg.ParticipantID = p.ParticipantID
where reg.TeamID = #TeamID;
open reg_cursor;
fetch next from reg_cursor
into #Email;
while ##FETCH_STATUS = 0
begin
exec ProcessAchievement #AchievementID, #Email, 0;
fetch next from reg_cursor
into #Email;
end
close reg_cursor;
deallocate reg_cursor;
-- award achievement to team
exec ProcessTeamAchievement #AchievementID, #TeamID;
end
fetch next from ach_cur
into #AchievementID;
end
close ach_cur;
deallocate ach_cur;
fetch next from team_cur
into #TeamID, #TotalReg, #TotalMin;
end
close team_cur;
deallocate team_cur;
Is there a set-based alternative to what I'm doing here? I need to add that this is running against a small set of records, so performance isn't my concern; best practices for future gargantuan updates are.
To make this set-based you need to fix the procs you are calling so that they either have a table-valued parameter or the code in the proc joins to a table where you have the records you want to process. In the second case you would either mark them as processsed or delete them when you are finished.
I have the following SP for SQL Server. Strangely the SP has weired behaviour when executing the query
Select #max_backup_session_time = Max(MachineStat.BackupSessionTime) from MachineStat where MachineStat.MachineID = #machine_id;
It takes 1 second if the MachineStat table has rows pertaining to #machine_id but if there are no rows for a #machine_id then it takes more than half a minute to execute. Can someone please help me understand this.
SET NOCOUNT ON;
DECLARE #MachineStatsMId TABLE (
MachineId INT NULL,
BackupSessiontime BIGINT NULL,
MachineGroupName NVARCHAR(128) NULL )
DECLARE #machine_id AS INT;
DECLARE #Machine_group_id AS INT;
DECLARE #machine_group_name AS NVARCHAR(128);
DECLARE #max_backup_session_time AS BIGINT;
SET #machine_id = 0;
SET #Machine_group_id = 0;
SET #machine_group_name = '';
DECLARE MachinesCursor CURSOR FOR
SELECT m.MachineId,
m.MachineGroupId,
mg.MachineGroupName
FROM Machines m,
MachineGroups mg
WHERE m.MachineGroupId = mg.MachineGroupId;
OPEN MachinesCursor;
FETCH NEXT FROM MachinesCursor INTO #machine_id, #machine_group_id, #machine_group_name;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #max_backup_session_time = Max(MachineStat.BackupSessionTime)
FROM MachineStat
WHERE MachineStat.MachineID = #machine_id;
INSERT INTO #MachineStatsMId
VALUES (#machine_id,
#max_backup_session_time,
#machine_group_name);
FETCH NEXT FROM MachinesCursor INTO #machine_id, #machine_group_id, #machine_group_name;
END;
SELECT *
FROM #MachineStatsMId;
CLOSE MachinesCursor;
DEALLOCATE MachinesCursor;
GO
Here is an alternate version that avoids a cursor and table variable entirely, uses proper (modern) joins and schema prefixes, and should run a lot quicker than what you have. If it still runs slow in certain scenarios, please post the actual execution plan for that scenario as well as an actual execution plan for the fast scenario.
ALTER PROCEDURE dbo.procname
AS
BEGIN
SET NOCOUNT ON;
SELECT
m.MachineId,
BackupSessionTime = MAX(ms.BackupSessionTime),
mg.MachineGroupName
FROM dbo.Machines AS m
INNER JOIN dbo.MachineGroups AS mg
ON m.MachineGroupId = mg.MachineGroupId
INNER JOIN dbo.MachineStat AS ms -- you may want LEFT OUTER JOIN here, not sure
ON m.MachineId = ms.MachineID
GROUP BY m.MachineID, mg.MachineGroupName;
END
GO
The code posted here is 'example' code, it's not production code. I've done this to make the problem I'm explaining readable / concise.
Using code similar to that below, we're coming across a strange bug. After every INSERT the WHILE loop is stopped.
table containst 100 rows, when the insert is done after 50 rows then the cursor stops, having only touched the first 50 rows. When the insert is done after 55 it stops after 55, and so on.
-- This code is an hypothetical example written to express
-- an problem seen in production
DECLARE #v1 int
DECLARE #v2 int
DECLARE MyCursor CURSOR FAST_FORWARD FOR
SELECT Col1, Col2
FROM table
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO #v1, #v2
WHILE(##FETCH_STATUS=0)
BEGIN
IF(#v1>10)
BEGIN
INSERT INTO table2(col1) VALUES (#v2)
END
FETCH NEXT FROM MyCursor INTO #v1, #v2
END
CLOSE MyCursor
DEALLOCATE MyCursor
There is an AFTER INSERT trigger on table2 which is used to log mutaties on table2 into an third table, aptly named mutations. This contains an cursor which inserts to handle the insert (mutations are logged per-column in an very specific manner, which requires the cursor).
A bit of background: this exists on an set of small support tables. It is an requirement for the project that every change made to the source data is logged, for auditing purposes. The tables with the logging contain things such as bank account numbers, into which vast sums of money will be deposited. There are maximum a few thousand records, and they should only be modified very rarely. The auditing functionality is there to discourage fraud: as we log 'what changed' with 'who did it'.
The obvious, fast and logical way to implement this would be to store the entire row each time an update is made. Then we wouldn't need the cursor, and it would perform an factor better. However the politics of the situation means my hands are tied.
Phew. Now back to the question.
Simplified version of the trigger (real version does an insert per column, and it also inserts the old value):
--This cursor is an hypothetical cursor written to express
--an problem seen in production.
--On UPDATE a new record must be added to table Mutaties for
--every row in every column in the database. This is required
--for auditing purposes.
--An set-based approach which stores the previous state of the row
--is expressly forbidden by the customer
DECLARE #col1 int
DECLARE #col2 int
DECLARE #col1_old int
DECLARE #col2_old int
--Loop through old values next to new values
DECLARE MyTriggerCursor CURSOR FAST_FORWARD FOR
SELECT i.col1, i.col2, d.col1 as col1_old, d.col2 as col2_old
FROM Inserted i
INNER JOIN Deleted d ON i.id=d.id
OPEN MyTriggerCursor
FETCH NEXT FROM MyTriggerCursor INTO #col1, #col2, #col1_old, #col2_old
--Loop through all rows which were updated
WHILE(##FETCH_STATUS=0)
BEGIN
--In production code a few more details are logged, such as userid, times etc etc
--First column
INSERT Mutaties (tablename, columnname, newvalue, oldvalue)
VALUES ('table2', 'col1', #col1, #col1_old)
--Second column
INSERT Mutaties (tablename, columnname, newvalue, oldvalue)
VALUES ('table2', 'col2', #col2, #col1_old)
FETCH NEXT FROM MyTriggerCursor INTO #col1, #col2, #col1_old, #col2_old
END
CLOSE MyTriggerCursor
DEALLOCATE MyTriggerCursor
Why is the code exiting in the middle of the loop?
Your problem is that you should NOT be using a cursor for this at all! This is the code for the example given above.
INSERT INTO table2(col1)
SELECT Col1 FROM table
where col1>10
You also should never ever use a cursor in a trigger, that will kill performance. If someone added 100,000 rows in an insert this could take minutes (or even hours) instead of millseconds or seconds. We replaced one here (that predated my coming to this job) and reduced an import to that table from 40 minites to 45 seconds.
Any production code that uses a cursor should be examined to replace it with correct set-based code. in my experience 90+% of all cursors can be reqwritten in a set-based fashion.
Ryan, your problem is that ##FETCH_STATUS is global to all cursors in an connection.
So the cursor within the trigger ends with an ##FETCH_STATUS of -1. When control returns to the code above, the last ##FETCH_STATUS was -1 so the cursor ends.
That's explained in the documentation, which can be found on MSDN here.
What you can do is use an local variable to store the ##FETCH_STATUS, and put that local variable in the loop. So you get something like this:
DECLARE #v1 int
DECLARE #v2 int
DECLARE #FetchStatus int
DECLARE MyCursor CURSOR FAST_FORWARD FOR
SELECT Col1, Col2
FROM table
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO #v1, #v2
SET #FetchStatus = ##FETCH_STATUS
WHILE(#FetchStatus=0)
BEGIN
IF(#v1>10)
BEGIN
INSERT INTO table2(col1) VALUES (#v2)
END
FETCH NEXT FROM MyCursor INTO #v1, #v2
SET #FetchStatus = ##FETCH_STATUS
END
CLOSE MyCursor
DEALLOCATE MyCursor
It's worth noting that this behaviour does not apply to nested cursors. I've made an quick example, which on SqlServer 2008 returns the expected result (50).
USE AdventureWorks
GO
DECLARE #LocationId smallint
DECLARE #ProductId smallint
DECLARE #Counter int
SET #Counter=0
DECLARE MyFirstCursor CURSOR FOR
SELECT TOP 10 LocationId
FROM Production.Location
OPEN MyFirstCursor
FETCH NEXT FROM MyFirstCursor INTO #LocationId
WHILE (##FETCH_STATUS=0)
BEGIN
DECLARE MySecondCursor CURSOR FOR
SELECT TOP 5 ProductID
FROM Production.Product
OPEN MySecondCursor
FETCH NEXT FROM MySecondCursor INTO #ProductId
WHILE(##FETCH_STATUS=0)
BEGIN
SET #Counter=#Counter+1
FETCH NEXT FROM MySecondCursor INTO #ProductId
END
CLOSE MySecondCursor
DEALLOCATE MySecondCursor
FETCH NEXT FROM MyFirstCursor INTO #LocationId
END
CLOSE MyFirstCursor
DEALLOCATE MyFirstCursor
--
--Against the initial version of AdventureWorks, counter should be 50.
--
IF(#Counter=50)
PRINT 'All is good with the world'
ELSE
PRINT 'Something''s wrong with the world today'
this is a simple misunderstanding of triggers... you don't need a cursor at all for this
if UPDATE(Col1)
begin
insert into mutaties
(
tablename,
columnname,
newvalue
)
select
'table2',
coalesce(d.Col1,''),
coalesce(i.Col1,''),
getdate()
from inserted i
join deleted d on i.ID=d.ID
and coalesce(d.Col1,-666)<>coalesce(i.Col1,-666)
end
basically what this code does is it checks to see if that column's data was updated. if it was, it compares the new and old data, and if it's different it inserts into your log table.
you're first code example could easily be replaced with something like this
insert into table2 (col1)
select Col2
from table
where Col1>10
This code does not fetch any further values from the cursor, nor does it increment any values. As it is, there is no reason to implement a cursor here.
Your entire code could be rewritten as:
DECLARE #v1 int
DECLARE #v2 int
SELECT #v1 = Col1, #v2 = Col2
FROM table
IF(#v1>10)
INSERT INTO table2(col1) VALUES (#v2)
Edit: Post has been edited to fix the problem I was referring to.
You do not have to use a cursor to insert each column as a separate row.
Here is an example:
INSERT LOG.DataChanges
SELECT
SchemaName = 'Schemaname',
TableName = 'TableName',
ColumnName = CASE ColumnID WHEN 1 THEN 'Column1' WHEN 2 THEN 'Column2' WHEN 3 THEN 'Column3' WHEN 4 THEN 'Column4' END
ID = Key1,
ID2 = Key2,
ID3 = Key3,
DataBefore = CASE ColumnID WHEN 1 THEN I.Column1 WHEN 2 THEN I.Column2 WHEN 3 THEN I.Column3 WHEN 4 THEN I.Column4 END,
DataAfter = CASE ColumnID WHEN 1 THEN D.Column1 WHEN 2 THEN D.Column2 WHEN 3 THEN D.Column3 WHEN 4 THEN D.Column4 END,
DateChange = GETDATE(),
USER = WhateverFunctionYouAreUsingForThis
FROM
Inserted I
FULL JOIN Deleted D ON I.Key1 = D.Key1 AND I.Key2 = D.Key2
CROSS JOIN (
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
) X (ColumnID)
In the X table, you could code additional behavior with a second column that specially describes how to handle just that column (let's say you wanted some to post all the time, but others only when the value changes). What's important is that this is an example of the cross join technique of splitting rows into each column, but there is a lot more that can be done. Note that the full join allows this to work on inserts and deletes as well as updates.
I also fully agree that storing each row is FAR superior. See this forum for more about this.
As ck mentioned, you are not fetching any further values. The ##FETCH_STATUS thus get's its value from your cursor contained in your AFTER INSERT trigger.
You should change your code to
DECLARE #v1 int
DECLARE #v2 int
DECLARE MyCursor CURSOR FAST_FORWARD FOR
SELECT Col1, Col2
FROM table
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO #v1, #v2
WHILE(##FETCH_STATUS=0)
BEGIN
IF(#v1>10)
BEGIN
INSERT INTO table2(col1) VALUES (#v2)
END
FETCH NEXT FROM MyCursor INTO #v1, #v2
END