I'm a beginner in SQL. I use Microsoft SQL Server and database for delivering bulk data from another system (different software on other pc).
I would like to update one column for just inserted rows. I created a trigger to update inserted rows, but this trigger do not update anything. It can update any other table, but update of the same table for rows have been just inserted do not work.
If I insert data directly in SSMS then it works - the same table where rows are inserted, are updated after insert. But data inserted from other system/server are not updated. Actually data are not even inserted. Have no idea why.
I was looking for any solution, found that MySQL cant update the same table what is being used for reading/writing. I don't know if the same logic is valid for SQL Server - but why manual insert works then?
This is my trigger:
ALTER trigger [cw].[mytrigger]
ON [cw].[TABLE1]
AFTER INSERT
AS
BEGIN
DECLARE #FBN nvarchar(10)
DECLARE db_cursor CURSOR FOR
SELECT FEEDBNO FROM inserted
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #FBN
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE cw.TABLE1
SET WCGroup = CASE
WHEN WCGroup = 'GROUP1' THEN 'GROUP_11'
WHEN WCGroup = 'GROUP2' THEN 'GROUP_22.'
WHERE FeedbackNo = #FBN
FETCH NEXT FROM db_cursor INTO #FBN
END
CLOSE db_cursor
DEALLOCATE db_cursor
END
I appreciate any help or solution. Thanks.
UPDATE:
OK, solved another way. Problem was most likely ETL job runnig on source system. I created a new column in destination table and trigger insert required value into this column based on inserted data. Updating column used in ETL by trigger didn't work properly.
Thanks for anwers, I learned something new.
There's a few flaws with your trigger. Firstly, the cursor is a bad idea, so we should get rid of that; treat your data as it is; a dataset. Next, your CASE is missing an END, so you should be getting a syntax error. i believe this fixes the problem (You'll need to correct YourIDColumn to the appropriate column name):
ALTER TRIGGER cw.mytrigger ON cw.Table1
AFTER INSERT
AS BEGIN
UPDATE T1
SET WCGroup = CASE i.WCGroup WHEN 'GROUP1' THEN 'GROUP_11'
WHEN 'GROUP2' THEN 'GROUP_22.'
ELSE i.WCGroup END
FROM cw.table1 T1
JOIN inserted i ON T1.YourIDColumn = i.YourIDColumn;
END;
Related
I'm working on a huge SQL code and unfortunately it has a CURSOR which handles another two nested CURSORS within it (totally three cursors inside a stored procedure), which handles millions of data to be DELETE,UPDATE and INSERT. This takes a whole lot of time because of row by row execution and I wish to modify this in to SET based approach
From many articles it shows use of CURSORs is not recommend and the alternate is to use WHILE loops instead, So I tried and replaced the three CUROSRs with three WHILE loops nothing more, though I get the same result but there is no improvement in performance, it took the same time as it took for CUROSRs.
Below is the basic structure of the code I'm working on (i Will try to put as simple as possible) and I will put the comments what they are supposed to do.
declare #projects table (
ProjectID INT,
fieldA int,
fieldB int,
fieldC int,
fieldD int)
INSERT INTO #projects
SELECT ProjectID,fieldA,fieldB,fieldC, fieldD
FROM ProjectTable
DECLARE projects1 CURSOR LOCAL FOR /*First cursor - fetch the cursor from ProjectaTable*/
Select ProjectID FROM #projects
OPEN projects1
FETCH NEXT FROM projects1 INTO #ProjectID
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
BEGIN TRAN
DELETE FROM T_PROJECTGROUPSDATA td
WHERE td.ID = #ProjectID
DECLARE datasets CURSOR FOR /*Second cursor - this will get the 'collectionDate'field from datasetsTable for every project fetched in above cursor*/
Select DataID, GroupID, CollectionDate
FROM datasetsTable
WHERE datasetsTable.projectID = #ProjectID /*lets say this will fetch ten records for a single projectID*/
OPEN datasets
FETCH NEXT FROM datasets INTO #DataID, #GroupID, #CollectionDate
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE period CURSOR FOR /*Third Cursor - this will process the records from another table called period with above fetched #collectionDate*/
SELECT ID, dbo.fn_GetEndOfPeriod(ID)
FROM T_PERIODS
WHERE DATEDIFF(dd,#CollectionDate,dbo.fn_GetEndOfPeriod(ID)) >= 0 /*lets say this will fetch 20 records for above fetched single #CollectionDate*/
ORDER BY [YEAR],[Quarter]
OPEN period
FETCH NEXT FROM period INTO #PeriodID, #EndDate
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS (some conditions No - 1 )
BEGIN
BREAK
END
IF EXISTS (some conditions No - 2 )
BEGIN
FETCH NEXT FROM period INTO #PeriodID, #EndDate
CONTINUE
END
/*get the appropirate ID from T_uploads table for the current projectID and periodID fetched*/
SET #UploadID = (SELECT ID FROM T_UPLOADS u WHERE u.project_savix_ID = #ProjectID AND u.PERIOD_ID = #PeriodID AND u.STATUS = 3)
/*Update some fields in T_uploads table for the current projectID and periodID fetched*/
UPDATE T_uploads
SET fieldA = mp.fieldA, fieldB = mp.fieldB
FROM #projects mp
WHERE T_UPLOADS.ID = #UploadID AND mp.ProjectID = #ProjectID
/*Insert some records in T_PROJECTGROUPSDATA table for the current projectID and periodID fetched*/
INSERT INTO T_PROJECTGROUPSDATA tpd ( fieldA,fieldB,fieldC,fieldD,uploadID)
SELECT fieldA,fieldB,fieldC,fieldD,#UploadID
FROM #projects
WHERE tpd.DataID = #DataID
FETCH NEXT FROM period INTO #PeriodID, #EndDate
END
CLOSE period
DEALLOCATE period
FETCH NEXT FROM datasets INTO #DataID, #GroupID, #CollectionDate, #Status, #Createdate
END
CLOSE datasets
DEALLOCATE datasets
COMMIT
END TRY
BEGIN CATCH
Error handling
IF ##TRANCOUNT > 0
ROLLBACK
END CATCH
FETCH NEXT FROM projects1 INTO #ProjectID, #FAID
END
CLOSE projects1
DEALLOCATE projects1
SELECT 1 as success
I request you to suggest any methods to rewrite this code to follow the SET based approach.
Until the table structure and expected result sample data is not provided, here are a few quick things I see that can be improved (some of those are already mentioned by others above):
WHILE Loop is also a cursor. So, changing into to while loop is not
going make things any faster.
Use LOCAL FAST_FORWARD cursor unless you have need to back track a record. This would make the execution much faster.
Yes, I agree that having a SET based approach would be the fastest in most cases, however if you must store intermediate resultset somewhere, I would suggest using a temp table instead of a table variable. Temp table is 'lesser evil' between these 2 options. Here are a few reason why you should try to avoid using a table variable:
Since SQL Server would not have any prior statistics on the table variable during building on Execution Plan, it will always consider that only one record would be returned by the table variable during construction of the execution plan. And accordingly Storage Engine would assign only as much RAM memory for execution of the query. But in reality, there could be millions of records that the table variable might hold during execution. If that happens, SQL Server would be forced spill the data to hard disk during execution (and you will see lots of PAGEIOLATCH in sys.dm_os_wait_stats) making the queries way slower.
One way to get rid of the above issue would be by providing statement level hint OPTION (RECOMPILE) at the end of each query where a table value is used. This would force SQL Server to construct the Execution Plan of those queries each time during runtime and the less memory allocation issue can be avoided. However the downside of this is: SQL Server will no longer be able to take advantage of an already cached execution plan for that stored procedure, and would require recompilation every time, which would deteriorate the performance by some extent. So, unless you know that data in the underlying table changes frequently or the stored procedure itself is not frequently executed, this approach is not recommended by Microsoft MVPs.
Replacing Cursor with While blindly, is not a recommended option, hence it would not impact your performance and might even have negative impact on the performance.
When you define the cursor using Declare C Cursor in fact you are going to create a SCROLL cursor which specifies that all fetch options (FIRST, LAST, PRIOR, NEXT, RELATIVE, ABSOLUTE) are available.
When you need just Fetch Next as scroll option, you can declare the cursor as FAST_FORWARD
Here is the quote about FAST_FORWARD cursor in Microsoft docs:
Specifies that the cursor can only move forward and be scrolled from
the first to the last row. FETCH NEXT is the only supported fetch
option. All insert, update, and delete statements made by the current
user (or committed by other users) that affect rows in the result set
are visible as the rows are fetched. Because the cursor cannot be
scrolled backward, however, changes made to rows in the database after
the row was fetched are not visible through the cursor. Forward-only
cursors are dynamic by default, meaning that all changes are detected
as the current row is processed. This provides faster cursor opening
and enables the result set to display updates made to the underlying
tables. While forward-only cursors do not support backward scrolling,
applications can return to the beginning of the result set by closing
and reopening the cursor.
So you can declare your cursors using DECLARE <CURSOR NAME> FAST_FORWARD FOR ... and you will get noticeable improvements
I think all the cursors code above can be simplified to something like this:
DROP TABLE IF EXISTS #Source;
SELECT DISTINCT p.ProjectID,p.fieldA,p.fieldB,p.fieldC,p.fieldD,u.ID AS [UploadID]
INTO #Source
FROM ProjectTable p
INNER JOIN DatasetsTable d ON d.ProjectID = p.ProjectID
INNER JOIN T_PERIODS s ON DATEDIFF(DAY,d.CollectionDate,dbo.fn_GetEndOfPeriod(s.ID)) >= 0
INNER JOIN T_UPLOADS u ON u.roject_savix_ID = p.ProjectID AND u.PERIOD_ID = s.ID AND u.STATUS = 3
WHERE NOT EXISTS (some conditions No - 1)
AND NOT EXISTS (some conditions No - 2)
;
UPDATE u SET u.fieldA = s.fieldA, u.fieldB = s.fieldB
FROM T_UPLOADS u
INNER JOIN #Source s ON s.UploadID = u.ID
;
INSERT INTO T_PROJECTGROUPSDATA (fieldA,fieldB,fieldC,fieldD,uploadID)
SELECT DISTINCT s.fieldA,s.fieldB,s.fieldC,s.fieldD,s.UploadID
FROM #Source s
;
DROP TABLE IF EXISTS #Source;
Also it would be nice to know "some conditions No" details as query can differ depends on that.
For a homework assignment, I'm trying to build a trigger that allows for multiple inserts/updates/deletes by utilizing a cursor. We have to use a cursor in order to practice the syntax. We know that there are very few practical scenarios for cursors in a production environment.
Here's what I'm trying to accomplish:
For each row inserted into the TAL_ORDER_LINE table, update the ON_HAND value in the TAL_ITEM table by subtracting the NUM_ORDERED value from the stored ON_HAND value.
Table Structure:
Current Query:
ALTER TRIGGER update_on_hand
ON TAL_ORDER_LINE
AFTER INSERT AS
DECLARE #vItemNum as char
DECLARE #vNumOrdered as int
DECLARE new_order CURSOR FOR
SELECT ITEM_NUM, NUM_ORDERED
FROM inserted
OPEN new_order;
FETCH NEXT FROM new_order INTO #vItemNum, #vNumOrdered;
WHILE ##FETCH_STATUS=0
BEGIN
UPDATE TAL_ITEM
SET ON_HAND = ON_HAND - #vNumOrdered
WHERE ITEM_NUM = #vItemNum
FETCH NEXT FROM new_order INTO #vItemNum, #vNumOrdered;
END
CLOSE new_order
DEALLOCATE new_order
My Insert Query:
INSERT INTO TAL_ORDER_LINE (ORDER_NUM, ITEM_NUM, NUM_ORDERED, QUOTED_PRICE)
VALUES (51626, 'KL78', 10, 10.95), (51626, 'DR67', 10, 29.95)
It runs successfully, but does not affect the ON_HAND value. I think the biggest problem is that I'm struggling to understand cursor syntax, especially the INTO clause in the FETCH statement and how data from the 'inserted' table is passed into the cursor. What do I need to know to get this to work? Thanks in advance!
Your problem is likely due to this:
DECLARE #vItemNum as char
it is HIGHLY unlikely that the ItemNum column is a single character. For future reference, you should always verify that you variable definitions are consistent with the values you expect to store in them. And as has been hinted - you will get better answers by posting a complete script rather than a picture.
Big question,how you gonna debug ?
Is On_Hand col NULL , then do this isnull(on_Hand,0)
DECLARE #vItemNum as char
DECLARE #vNumOrdered as int
DECLARE new_order CURSOR FOR
SELECT ITEM_NUM, NUM_ORDERED
FROM TAL_ORDER_LINE
OPEN new_order;
FETCH NEXT FROM new_order INTO #vItemNum, #vNumOrdered;
WHILE ##FETCH_STATUS=0
BEGIN
--UPDATE TAL_ITEM
--SET ON_HAND = ON_HAND - #vNumOrdered
--WHERE ITEM_NUM = #vItemNum
print #vItemNum
print vNumOrdered
FETCH NEXT FROM new_order INTO #vItemNum, #vNumOrdered;
END
CLOSE new_order
DEALLOCATE new_order
Try this :
ALTER TRIGGER update_on_hand ON TAL_ORDER_LINE
FOR INSERT AS
BEGIN
UPDATE TI
SET TI.ON_HAND = TI.ON_HAND - I.NUM_ORDERED
TAL_ITEM TI INNER JOIN
INSERTED I ON I.ITEM_NUM = TI.ITEM_NUM
END
Changed Trigger to FOR INSERT Trigger
Removed Cursor
Note: NOT Tested. ( If you post the sql scripts for create table + sample inserts I can give it a try )
I know this may seem like a commonly asked question, but I have a unique situation which I can't find an answer to.
I have a simple SSIS package which I want to use to update a table from an Excel spreadsheet. I'll do this using an OLE DB command task which executes a stored procedure. However, it's not retrieving any column names to map. The error is similar to this:
The metadata could not be determined because statement 'insert into #TempTable ... ' uses a temp table.'
I understand why it's returning this error, and know the typical workarounds. However, the error is not coming from the stored procedure that the package calls. That proc doesn't use temp tables. It's a result of the update firing off a database trigger that calls another proc, which DOES use temp tables.
Unfortunately, the triggered proc causing the error is from the third party application I'm developing for, and cannot be modified.
Does anyone know of a solution that doesn't involve adding code to the offending proc?
Thanks!
Option 1, load Excel spreadsheet to a staging table, then use 'Execute SQL Task' to update your final table.
Option 2, create Script task, read from spreadsheet and update your final table, thus avoid going through data flow for column mapping.
Your best bet would be to stage the updates to a dedicated table (OLE DB Destination instead) and then have an Execute SQL Task as a successor event.
Within the Execute SQL Task, set up a cursor to shred the staging table and then call your stored procedure.
DECLARE CSR CURSOR
READ_ONLY
FOR SELECT Col1, Col2
FROM staging;
DECLARE #Col1 nvarchar(100)
, #Col2 nvarchar(100);
OPEN CSR;
FETCH NEXT FROM CSR INTO #Col1, #Col2;
WHILE (##fetch_status <> -1)
BEGIN
IF (##fetch_status <> -2)
BEGIN
EXECUTE dbo.MyProcedure #Col1, #Col2;
END
FETCH NEXT FROM CSR INTO #Col1, #Col2;
END
CLOSE CSR;
DEALLOCATE CSR;
The staging table approach occurred to me shortly after I posted this, and that's the route I went with. While not ideal for such a simple package, it does work. A cursor won't even be necessary...for this package, I can simply update the appropriate table from the staging table, no need for a stored proc. Thanks to you both!
Being an MS Access bod, our IT group doesn't let me touch the SQL server much so I don't know how to do looping of recordsets in it for a scheduled job I need. I have pseudo-coded what I want to do but would be very grateful if someone could point out how this is done...
for every record in qry_deliveryqueue
loop
if not fail then
send docID field to SP_sendfile
set delivereddate=getdate()
else
insert into tbl_errors (errdate,docid) values (getdate(), docid)
endif
next record
The syntax you're after is a CURSOR. The documentation has good examples that should point you in the right direction. You probably want to write a stored procedure around the logic and call that from the scheduler.
Cursors are generally inefficient, but I am assuming that your sp_sendfile has some more complex logic that precludes treating all the records as a set
You can Sql while syntax or Sql Cursor Syntax
Try this ion SQL server(This code is sample of while loop.So you have to tweak it as per your requirements)
--In sql server
Declare #idmin int,#idmax int
select #idmin=MIN(id) from qry_deliveryqueue --This is first record in qry_deliveryqueue
select #idmax=MAX(id) from qry_deliveryqueue --This is last record in qry_deliveryqueue
--You can
While(#idmin <= #idmax)
begin
IF #flag<>'Fail'
begin
-- send docID field to SP_sendfile
-- set delivereddate=getdate()
end
else
begin
insert into tbl_errors (errdate,docid) values (getdate(), docid)
end
SET #idmin=#idmin+1
end
I currently working with an old application that I don't own any source code but I really need to add some new logic to the app.
The only way I found to do it is to mess up with database data, and change whatever I need with a trigger.
I wrote the following trigger that should update 2 columns of a just inserted row:
CREATE TRIGGER addLoteInfo ON fi
FOR INSERT
AS
DECLARE
#loteN varchar(15),
#refN varchar(18),
#CientifN varchar(40),
#CaptureZ varchar(20)
declare MY_CURSOR cursor for
select
i.lote, i.ref
from inserted i
open MY_CURSOR
fetch next from MY_CURSORinto #loteN, #refN
while ##fetch_status = 0
begin
SELECT #CientifN = u_ncientif FROM SE where lote LIKE #loteN and ref LIKE #refN;
SELECT #CaptureZ = u_zcaptura FROM SE where lote LIKE #loteN and ref LIKE #refN;
UPDATE FI SET LNCIENT=#CientifN, LZCAP=#CaptureZ;
fetch next from CURSOR_TESTE into #loteN, #refN
end
close MY_CURSOR
deallocate MY_CURSOR
The problem is when a new registry is inserted, it seems it gets to a deadlock.
Its impossible to do what I'm trying to do?
Can you help me with another approach?
You need to use INSTEAD OF trigger if you want to modify newly inserted/updated record on the fly.