SQL Implementation of IF/ELSE breaking result output - sql-server

I have this query.
DECLARE #testvar varchar
DECLARE #rescount int
DECLARE MY_CURSOR CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
select distinct value from [table] where tablevalId = n
OPEN MY_CURSOR
FETCH NEXT FROM MY_CURSOR INTO #testvar
WHILE ##FETCH_STATUS = 0
BEGIN
select #rescount = Count(value2) from [table] where value = #testvar and tablevalId = n
if #rescount > 1
BEGIN
select top (#rescount - 1) value2 from [table] where value = #testvar and tablevalId = n order by tablevalue3 desc --This is the value I want
END
else
BEGIN
select #rescount = 1 --This is a 'DO NOTHING'
END
FETCH NEXT FROM MY_CURSOR INTO #testvar
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR
I am trying to get a few groups of records minus the first record in each group. The issue came in where the #rescount was not greater than 1. That is when the IF/ELSE was implemented.
Before the IF/ELSE, it would print the result but break when the #rescount was not greater than 1. Now, it prints nothing and tells me "The Query Was Completed Successfully".
Would anyone be able to advise on this?
Thanks

Why are you using cursors for this? Just use row_number():
select t.*
from (select t.*, row_number() over (partition by tablevalId, value
order by tablevalue3 desc) as seqnum
from [table] t
) t
where seqnum > 1;
You should avoid cursors if you do not need them.

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;

how to call function inside a trigger?

what is the problem with the #temp variable?
create function dbo.getNumOfReviews2 (#email varchar(40))
returns int
as begin
declare #numOfReviews int
select #numOfReviews = count(*)
from dbo.Reviews
where email = #email
group by Email
return #numOfReviews
end
CREATE TRIGGER setDiscount
ON dbo.[Contains]
FOR INSERT
AS
DECLARE #OrderID int
DECLARE #ProductID int
DECLARE #Size VarChar(15)
DECLARE #temp int
IF CURSOR_STATUS('global','C_CURSOR')>=-1
BEGIN
DEALLOCATE C_CURSOR
END
DECLARE C_CURSOR CURSOR
FOR SELECT ProductID,OrderID,Size
FROM INSERTED
BEGIN
OPEN C_CURSOR
FETCH NEXT FROM C_CURSOR INTO #ProductID,#OrderID,#Size
WHILE (##FETCH_STATUS=0)
BEGIN
#temp = dbo.getNumOfReviews2(select BillingEmail from dbo.Orders where OrderID=#OrderID)
IF (SELECT COUNT(*)
FROM dbo.[Contains]
WHERE OrderID = #OrderID) > 5 or (SELECT sum(Quantity) FROM dbo.[Contains] WHERE OrderID=#OrderID) > 10 or
( #temp )> 5
UPDATE [Contains]
SET [Savings%] = [Savings%] + 0.05
WHERE OrderID = #OrderID and ProductID = #ProductID and Size = #Size
FETCH NEXT FROM C_CURSOR INTO #ProductID,#OrderID,#Size
END
END
Use select to call scalar function
correct way to do this would be
select #temp = dbo.getNumOfReviews2(BillingEmail)
from dbo.Orders
where OrderID=#OrderID
Note: It is not advisable to write big logic inside a trigger. Triggers should be simple and fast otherwise your DML operations will be slow. Moreover you have used a CURSOR which should be avoided at any cost. Rewrite the code using SET based approach.
Here is a SET based approach code
;WITH cte
AS (SELECT c1.orderid
FROM dbo.[contains] c1
INNER JOIN inserted i1
ON i1.orderid = c1.orderid
GROUP BY orderid
HAVING Count(*) > 5
OR Sum(quantity) > 5
OR #temp > 5)
UPDATE C
SET [savings%] = [savings%] + 0.05
FROM [contains] C
INNER JOIN inserted I
ON I.orderid = C.orderid
AND I.productid = C.productid
AND I.size = C.size
AND EXISTS (SELECT 1
FROM cte c1
WHERE c1.orderid = c.orderid)
CREATE OR REPLACE COVID19VMS_VACCINESHOT_T1
BEFORE INSERT OR UPDATE ON VACCINENEXTSHOTDATE
FOR EACH ROW
BEGIN
IF :NEW.VACCINESHOTDATE := 1
:NEW.VACCINEXTSHOTDATE := to_date(VACCINESHOTDATE +28)
END IF;
IF :NEW.VACCINESHOTDATE :=2
:NEW.VACCINENEXTSHOTDATE IS NULL
END IF
END

How can I subtract a previous row in sql?

What should I query if I wanted to subtract the current row to the previous row. I will use it on looping in vb6.
Something Like this:
Row
1
2
3
4
5
On first loop value 1 will not be deducted because it has no previous row, which is ok.
Next loop value 2 will then be deducted by the previous row which is value 1. And so on until the last row.
How can I achieve this routine?
By SQL query or VB6 code.Any will do.
Assuming you have an ordering column -- say id -- then you can do the following in SQL Server 2012:
select col,
col - coalesce(lag(col) over (order by id), 0) as diff
from t;
In earlier versions of SQL Server, you can do almost the same thing using a correlated subquery:
select col,
col - isnull((select top 1 col
from t t2
where t2.id < t.id
order by id desc
), 0)
from t
This uses isnull() instead of coalesce() because of a "bug" in SQL Server that evaluates the first argument twice when using coalesce().
You can also do this with row_number():
with cte as (
select col, row_number() over (order by id) as seqnum
from t
)
select t.col, t.col - coalesce(tprev.col, 0) as diff
from cte t left outer join
cte tprev
on t.seqnum = tprev.seqnum + 1;
All of these assume that you have some column for specifying the ordering. It might be an id, or a creation date or something else. SQL tables are inherently unordered, so there is no such thing as a "previous row" without a column specifying the ordering.
Using Cursor:
CREATE TABLE t (id int)
INSERT INTO t
VALUES(1)
INSERT INTO t
VALUES(2)
INSERT INTO t
VALUES(3)
INSERT INTO t
VALUES(4)
DECLARE #actual int;
DECLARE #last int;
DECLARE #sub int;
SET #last = 0;
DECLARE sub_cursor CURSOR FOR
SELECT *
FROM t OPEN sub_cursor
FETCH NEXT
FROM sub_cursor INTO #actual;
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT #sub = #actual - #last print cast(#actual AS nvarchar) + '-' + cast(#last AS nvarchar) + '=' + cast(#sub AS nvarchar)
SET #last = #actual
FETCH NEXT FROM sub_cursor INTO #actual;
END
DROP TABLE t
CLOSE sub_cursor; DEALLOCATE sub_cursor;

using cursors and displaying row numbers

I have declared the following cursor and have used a local variable #RowNo to print the number of each each row.
declare TheCursor cursor
for select productName from products
declare #RowNo int
declare #productName nvarchar(50)
set #RowNo = 1
open TheCursor
fetch next from TheCursor into #productName
print #RowNo
print #productName
set #RowNo = #RowNo+1
set #productName=''
while ##FETCH_STATUS=0
begin
fetch next from TheCursor into #productName
print #RowNo
print #productName
set #RowNo = #RowNo+1
set #productName=''
end
close TheCursor
deallocate TheCursor
I am trying to find any other way to assign / define a number to each row and display it in the console. I have found the function Row_number() and used it like select ROW_NUMBER() over (order by (select 0)) As Rownumber to do it.
I want to know if it is possible to use the KEYSET in my cursor to do it? How can I do it with KEYSET?
I don't completely understand what you're trying to achieve - but you could wrap your select statement including the ROW_NUMBER() function into a CTE (common table expression) and then go from there - no messy cursor needed, that really doesn't scale well:
WITH YourSelection AS
(
SELECT
ProductName,
ROW_NUMBER() OVER(ORDER BY ProductName) AS 'RowNum'
FROM dbo.Products
)
SELECT ProductName, RowNum
FROM YourSelection
That should give you the product names, sorted by ProductName, and corresponding row numbers, too.

How to commit inside a CURSOR Loop?

I am trying to see if its possible to perform Update within a cursor loop and this updated data gets reflected during the second iteration in the loop.
DECLARE cur CURSOR
FOR SELECT [Product], [Customer], [Date], [Event] FROM MyTable
WHERE [Event] IS NULL
OPEN cur
FETCH NEXT INTO #Product, #Customer, #Date, #Event
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT * FROM MyTable WHERE [Event] = 'No Event' AND [Date] < #DATE
-- Now I update my Event value to 'No Event' for records whose date is less than #Date
UPDATE MyTable SET [Event] = 'No Event' WHERE [Product] = #Product AND [Customer] = #Customer AND [Date] < #DATE
FETCH NEXT INTO #Product, #Customer, #Date, #Event
END
CLOSE cur
DEALLOCATE cur
Assume when the sql executes the Event column is NULL for all records
In the above sql, I am doing a select inside the cursor loop to query MyTable where Event value is 'No Event' but the query returns no value even though I am doing an update in the next line.
So, I am thinking if it is even possible to update a table and the updated data get reflected in the next iteration of the cursor loop.
Thanks for any help,
Javid
Firstly You shouldn't need a cursor here. Something like the following would have the same semantics (from a starting position where all Event are NULL) and be more efficient.
WITH T
AS (SELECT [Event],
RANK() OVER (PARTITION BY [Product], [Customer]
ORDER BY [Date] DESC) AS Rnk
FROM MyTable)
UPDATE T
SET [Event] = 'No Event'
WHERE Rnk > 1
Secondly regarding the question in the title to commit inside a cursor loop is the same as anywhere else. You just need a COMMIT statement. However if you aren't running this inside a larger transaction the UPDATE statement will be auto committed anyway.
Thirdly Your real question doesn't seem to be about commit anyway. It is about the cursor reflecting updates to the data on subsequent iterations. For the case in the question you would need a DYNAMIC cursor
Defines a cursor that reflects all data changes made to the rows in
its result set as you scroll around the cursor. The data values,
order, and membership of the rows can change on each fetch.
Not all queries support dynamic cursors. The code in the question would but without an ORDER BY it is undeterministic what order the rows would be processed in and thus whether you would see visible results. I have added an ORDER BY and an index to support this to allow a dynamic cursor to be used.
If you try the following you will see the cursor only fetches one row as the dates are processed in descending order and when the first row is processed the table is updated such that no more rows qualify for the next fetch. If you comment out the UPDATE inside the cursor loop all three rows are fetched.
CREATE TABLE MyTable
(
[Product] INT,
[Customer] INT,
[Date] DATETIME,
[Event] VARCHAR(10) NULL,
PRIMARY KEY ([Date], [Product], [Customer])
)
INSERT INTO MyTable
VALUES (1,1,'20081201',NULL),
(1,1,'20081202',NULL),
(1,1,'20081203',NULL)
DECLARE #Product INT,
#Customer INT,
#Date DATETIME,
#Event VARCHAR(10)
DECLARE cur CURSOR DYNAMIC TYPE_WARNING FOR
SELECT [Product],
[Customer],
[Date],
[Event]
FROM MyTable
WHERE [Event] IS NULL
ORDER BY [Date] DESC
OPEN cur
FETCH NEXT FROM cur INTO #Product, #Customer, #Date, #Event
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Product,
#Customer,
#Date,
#Event
-- Now I update my Event value to 'No Event' for records whose date is less than #Date
UPDATE MyTable
SET [Event] = 'No Event'
WHERE [Product] = #Product
AND [Customer] = #Customer
AND [Date] < #Date
FETCH NEXT FROM cur INTO #Product, #Customer, #Date, #Event
END
CLOSE cur
DEALLOCATE cur
DROP TABLE MyTable
Even if this worked, this would not guarantee the correct result since you miss an ORDER BY clause in your query.
Depending on this, all records, no records or any random subset of records could be updated.
Could you please explain in plain English what your stored procedure should do?
Use Below template
DECLARE #CCount int = 100
DECLARE #Count int = 0
DECLARE #id AS BigInt
DECLARE cur Cursor fast_forward for
SELECT t1.Id
FROM Table1 t1 WITH (NOLOCK) WHERE <Some where clause>
OPEN cur
Begin Tran
While (1=1)
Begin
Fetch next from cur into #id
If ##Fetch_Status <> 0
break
-- do some DML actions
Delete From Table1 WITH (ROWLOCK) where Id = #id
Set #count = #count + ##Rowcount
if (#count % #CCount = 0)
Begin
if (#count % 100 = 0)
Print 'Table1: DML action ' + Cast(#count as Varchar(15)) + ' rows'
-- for every 100 rows commit tran will trigger , and starts a new one.
While ##Trancount > 0 Commit Tran
Begin Tran
End
End
While ##Trancount > 0 Commit Tran
Close cur
Deallocate cur

Resources