I need some tip on tuning some TSQL to execute faster, it's taking way too long although it works. This may be because I'm fetching a key from another table before I can do the insert, any ideas anyone?
DECLARE db_cursorReads CURSOR FOR SELECT
[MeterId]
,[MeterRead]
FROM MdsReadsImports;
declare #PremiseMeterId int;
declare #MeterId nvarchar(24);
declare #MeterRead int;
OPEN db_cursorReads;
FETCH NEXT FROM db_cursorReads INTO
#MeterId
,#MeterRead;
WHILE ##FETCH_STATUS = 0
BEGIN
set #PremiseMeterId = (select top 1 PremiseMeterId from PremiseMeters where MeterId = #MeterId)
insert into PremiseMeterReads (MeterRead,PremiseMeterId)
values (#MeterRead, #MPremiseMeterId)
FETCH NEXT FROM db_cursorReads INTO
#MeterId
,#MeterRead;
END;
CLOSE db_cursorReads;
DEALLOCATE db_cursorReads;
I see you are retrieving PremiseMeterId but not using it in the script you posted. Perhaps you can ditch the cursor and perform a single set-based query:
INSERT INTO PremiseMeterReads (MeterRead,MeterId)
SELECT
[MeterRead]
,[MeterId]
FROM MdsReadsImports;
First, I note that you are setting but not using #PremiseMeterID.
Second, you seem to be doing this:
insert into PremiseMeterReads (MeterRead, MeterId)
select MeterRead, MeterId
from MdsReadsImports;
A set based operation should be much faster than using a cursor.
Related
I want to create a function in SQL Server which takes a comma separated string a parameter, splits it and returns one value at a time. The basic idea here is to call that function from a query. Something like this.
CREATE FUNCTION SPLIT_VALUE(#IN_CSV)
RETURN VARCHAR AS
-- LOGIC TO RETURN A SINGLE VALUE FROM CSV
END
I want to call this function from a stored procedure.
CREATE PROCEDURE DEMO_PROC #IN_CSV VARCHAR(5000), #OUT VARCHAR(5000) OUTPUT AS
BEGIN
SELECT #OUT= CONCAT(A.VALUE1,B.VALUE2) FROM TABLE1 A INNER JOIN TABLE2 B ON A.ID=B.ID WHERE A.ID
IN(--CALL THE FUNCTION AND GET ONE VALUE);
END;
I have to create a loop or cursor to point to a particular value every time. Is this practically possible to? If yes then how can I do that?
Like I mention, you'll have to use a CURSOR to do this, however, the fact you want to do it this way infers a (large) design flaw:
DECLARE #value varchar(8000)
DECLARE Delimited_Values CURSOR FAST_FORWARD
FOR
SELECT [value]
FROM STRING_SPLIT('a,b,c,d,e',',')
OPEN Delimited_Values;
FETCH NEXT FROM Delimited_Values
INTO #value;
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT #value; --Do your stuff here
FETCH NEXT FROM Delimited_Values
INTO #value;
END;
CLOSE Delimited_Values;
DEALLOCATE Delimited_Values;
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 have some TSQL used in classic asp like this:
Declare #tbl TABLE(some columns)
Declare #somevarables
Declare myCur CURSOR
For
Select something From my_table
Open myCur
Fetch Next From myCur Inti somevarables
While (##Fetch_Status<>-1)
Begin
some processimg
......
Insert Into #tbl(...)
Values(...)
Fetch Next From myCur Inti somevarables
End
Deallocate myCur
Select * From #tbl
The scripts worked well in SQL Query Analyzer. However, when I run it in an ASP page, there's no rowset returned, nor error message.
Who can tell me why?
Thanks!
The problem is that you're inserting multiple times and each time your rows affected count will generate a closed recordset.
Simple fix is to make sure in your T-SQL you first SET NOCOUNT ON;, this will stop the row counts and the only recordset returned will be your end SELECT.
I created a simple trigger:
ALTER TRIGGER [dbo].[idlist_update] ON [dbo].[Store]
FOR INSERT, UPDATE
AS
BEGIN
DECLARE #brand varchar(50);
DECLARE #model varchar(50);
DECLARE #category varchar(100);
DECLARE #part varchar(100);
DECLARE #count int;
SELECT #count = COUNT(*) FROM inserted;
SELECT #brand=Brand, #model=Model, #category=AClass, #part=Descript FROM inserted;
EXECUTE GenerateId_Part #brand, #model, #category, #part;
END
With rows, modified by our users (they using special application), it works ok, but I need to apply it to all rows in the table (more than 200.000+). I tried:
UPDATE Store SET lastupd={fn NOW()};
But it does not work.
I believe the syntax you want is:
UPDATE dbo.Store SET lastupd = CURRENT_TIMESTAMP;
However if you are updating the entire table every time you insert or update a single row, this seems quite wasteful to me. Why not store that fact once, somewhere else, instead of storing it redundantly 200,000 times? It seems to be a property of the store itself, not the products in it.
Also note that your trigger won't properly handle multi-row operations. You can't do this "assign a variable from inserted" trick because if two rows are inserted by a single statement, you'll only affect one arbitrary row. Unlike some platforms, in SQL Server triggers fire per statement, not per row. We can help fix this if you show what the stored procedure GenerateId_Part does.
USE [ddb]
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[requeststrigger]
ON [dbo].[requests]
AFTER INSERT,UPDATE
AS
BEGIN
DECLARE #email VARCHAR(400);
DECLARE #firstname VARCHAR(400);
DECLARE #requestno VARCHAR(400);
DECLARE #lastname VARCHAR(400);
DECLARE #statusid INT;
DECLARE thecursor CURSOR FOR SELECT inserted.requestno,contacts.firstname,contacts.lastname,contacts.email FROM request_contacts,contacts,inserted WHERE request_contacts.requestid=inserted.requestid AND contacts.contactid=request_contacts.contactid AND request_contacts.notification=1 AND contacts.notification=1;
SET #statusid = (SELECT statusid FROM inserted);
IF #statusid = 4 AND #statusid <> (SELECT statusid FROM deleted)
BEGIN
SET NOCOUNT ON
SET ARITHABORT ON
OPEN thecursor;
FETCH NEXT FROM thecursor
INTO #requestno,#firstname,#lastname,#email
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC MAIL_SEND #email,#firstname,#requestno,#lastname;
FETCH NEXT FROM thecursor
INTO #requestno,#firstname,#lastname,#email
END
CLOSE thecursor;
DEALLOCATE thecursor
SET NOCOUNT OFF
END
END
This simply makes the whole UPDATE/INSERT not work. When I remove the cursor declaration, it works. The cursor is just selecting a field from a table that is existing in the same database called "contacts". What is wrong?
Are you prepared to consider amending your design? There appear to be a couple of issues with what you're attempting here.
A trigger isn't necessarily the best place to be doing this kind of row-by-row operation since it executes in-line with the changes to the source table, and will affect the performance of the system negatively.
Also, your existing code evaluates statusid only for a single row in the batch, although logically it could be set to more than one value in a single batch of updates.
A more robust approach might be to insert the rows which should generate a MAIL_SEND operation to a queuing table, from which a scheduled job can pick rows up and execute MAIL_SEND, setting a flag so that each operation is carried out only once.
This would simplify your trigger to an insert - no cursor would be required there (although you will still need a loop of some kind in the sechduled job).