SQL Server stored procedure running in infinite loop - sql

I am running a stored procedure which is running infinitely.
I have used a while loop that seems to be running without ever ending.
CREATE PROCEDURE ABC
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Id INT;
DECLARE #iter INT = 1;
DECLARE #iterMax INT;
DECLARE #psubject VARCHAR(100);
DECLARE #pbody NVARCHAR(MAX);
DECLARE #pSendTo NVARCHAR(MAX);
DECLARE #pProfile VARCHAR(MAX);
IF OBJECT_ID('tempdB..#temp') IS NOT NULL
DROP TABLE #temp;
SET #pProfile = 'Test';
IF ((SELECT COUNT(*)
FROM [Table_A] R
JOIN [Table_B] T ON R.Id = T.r_Id
WHERE R.[Date] <= (DATEADD(DAY, -1, GETDATE()))
AND T.[Sent_Flag] IS NULL) >= 1)
BEGIN
SELECT IDENTITY(int, 1, 1) AS RecId,
[r_id] * 1 AS Id
INTO #temp
FROM [Table_A] R
JOIN [Table_B] T ON R.Id = T.r_Id
WHERE R.[Date] <= (DATEADD(DAY, -1, GETDATE()))
AND T.[Sent_Flag] IS NULL;
BEGIN
SET #iterMax = (SELECT COUNT(*)FROM #temp);
WHILE (#iter <= #iterMax)
BEGIN
SET #psubject = 'HIIII'; /*this is in HTML example */
SET #pbody = 'You got one email';
IF ((SELECT COUNT(*)
FROM [Table_B]
WHERE R_Id = (SELECT Id FROM #temp WHERE RecId = #iter)
AND [Mail1_Flag] = 'Y'
AND [Mail2_Flag] = 'Y') = 1)
BEGIN
IF ((SELECT COUNT(*)
FROM [Table_A] R
JOIN [Table_B] T ON R.Id = T.r_Id
WHERE R_Id = (SELECT Id FROM #temp WHERE RecId = #iter)
AND R.[Date] <= (DATEADD(DAY, -1, GETDATE()))
AND T.[Sent_Flag] IS NULL) = 1)
BEGIN
SET #pSendTo = N'ABC#gmail.com';
EXEC msdb.dbo.sp_send_dbmail #profile_name = #pProfile,
#body = #pbody,
#subject = #psubject,
#recipients = #pSendTo,
#body_format = 'HTML';
END;
UPDATE [Table_B]
SET [Sent_Flag] = 'Y'
WHERE [Id] IN (SELECT Id FROM #temp WHERE RecId = #iter);
END;
END;
SET #iter = #iter + 1;
END;
END;
IF OBJECT_ID('tempd..#temp') IS NOT NULL
DROP TABLE #temp;
END;
This program is checking that if date is more than 24 hours then it will send a mail correspondingly. I am able to trigger a mail. But I am getting multiple mails. Like the loop is running infinitely and getting same mail multiple times and the sent_Flag column is initial NULL and after a mail is sent it sholud update to 'Y' but it is also not updating to 'Y' after mail is triggered.
Please help to resolve this issue. Thank you

You are not incrementing the counter inside the loop:
UPDATE [Table_B]
SET [Sent_Flag] = 'Y'
WHERE [Id] IN (SELECT Id FROM #temp WHERE RecId = #iter);
END; --this is the END of the first IF BEGIN
END; --this is the END of the WHILE BEGIN
SET #iter = #iter + 1; --and here you update the counter, which will never be reached
If you don't increment the counter inside the loop, the loop will run infinitely, since the loop condition will always be true.

Related

SQL Iterating Select Statement

In my Database I have various Schemas & every Schema have a Table as [Company] & Other Tables.
I have written below Query which iterates all Schemas & in case i want to INSERT something in a Table for all Schemas I run this Query.
I am stuck in a Scenario where Insert Query requires Values from a [Company] Table.
Example - In 1 Schema I have [Company] Table & I have 4 Records in it.
So I want to INSERT 4 Records in [Menu] Table & Company Id will be picked from [Company] Table.
Right Now, In the below Query I am just Selecting Id from [Company] table.
I want to know How to iterate through the Records of Select Statement?
-- in-memory schema table to hold distinct schema_names
DECLARE #i int
DECLARE #numrows int
DECLARE #schema_names nvarchar(max)
DECLARE #schema_table TABLE (
idx smallint Primary Key IDENTITY(1,1)
, schema_names nvarchar(max)
)
DECLARE #company_table nvarchar(max)
DECLARE #sql nvarchar(max)
-- populate schema table
INSERT #schema_table
SELECT name FROM sys.schemas Where name <> 'dbo' AND name <> 'guest' AND name <> 'INFORMATION_SCHEMA' AND name <> 'db_accessadmin' AND name <> 'db_backupoperator' AND name <> 'db_datareader' AND name <> 'db_datawriter' AND name <> 'db_ddladmin' AND name <> 'db_denydatareader' AND name <> 'db_denydatawriter' AND name <> 'db_owner' AND name <> 'db_securityadmin' AND name <> 'sys'
select * from #schema_table
-- enumerate the table
SET #i = 1
SET #numrows = (SELECT COUNT(*) FROM #schema_table)
IF #numrows > 0
WHILE (#i <= (SELECT MAX(idx) FROM #schema_table))
BEGIN
-- get the next record primary key
SET #schema_names = (SELECT schema_names FROM #schema_table WHERE idx = #i)
SET #company_table = '['+#schema_names+'].[Company]'
SET #sql = 'select Id from ' + #company_table
EXEC(#sql)
BEGIN TRY
DECLARE #sSQL nvarchar(500);
SELECT #sSQL = N'INSERT ['+#schema_names+'].[Menu] VALUES (9, N''Dashboard'', N''Charts'', N''/Dash/Chart'', 1)'
EXEC sp_executesql #sSQL
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()+' '+#schema_names AS ErrorMessage;
END CATCH
-- increment counter for next record
SET #i = #i + 1
END
In this Query - 9 will be replaced by Value from [Company] Table.
Just it simple for iterating in each row you can use the below Example
CREATE PROCEDURE cursor1()
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE fname1 CHAR(20) DEFAULT "";
DECLARE lname1 CHAR(20) DEFAULT "";
DECLARE nameList CHAR(100) DEFAULT "";
-- 1. Declare cursor for employee
DECLARE emp_cursor CURSOR FOR SELECT fname, lname FROM employee WHERE salary > 40000;
-- 2. Declare NOT FOUND handler
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
-- 3. Open the cursor
OPEN emp_cursor;
L: LOOP
-- 4. Fetch next tuple
FETCH emp_cursor INTO fname1, lname1;
-- Handler will set finished = 1 if cursor is empty
IF finished = 1 THEN
LEAVE L;
END IF;
-- build emp list
SET nameList = CONCAT( nameList, fname1, ' ', lname1, ';' );
END LOOP ;
-- 5. Close cursor when done
CLOSE emp_cursor;
SELECT nameList ;
END //
DELIMITER
Eg2.
DROP PROCEDURE IF EXISTS depreciation_calculator;
# depreciation calculator..........................................
CREATE PROCEDURE depreciation_calculator(IN deprcesionDate INT)
BEGIN
DECLARE acc DOUBLE;
DECLARE diff INT;
DECLARE currentDate DATE;
DECLARE depDate VARCHAR(12);
DECLARE dep DOUBLE;
DECLARE bookValue DOUBLE;
DECLARE assetId INT;
DECLARE depStatus VARCHAR(12);
DECLARE finished INTEGER DEFAULT 0;
DECLARE emp_cursor CURSOR FOR SELECT
dep_date,
dep_amount,
dep_status,
asset_ass_id,
book_value,
accumulative_value
FROM depreciation;
-- 2. Declare NOT FOUND handler
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
-- 3. Open the cursor
OPEN emp_cursor;
L: LOOP
-- 4. Fetch next element
FETCH emp_cursor
INTO depDate, dep, depStatus, assetId, bookValue, acc;
-- Handler will set finished = 1 if cursor is empty
IF finished = 1
THEN
LEAVE L;
END IF;
SET currentDate = DATE(now());
SET diff := TIMESTAMPDIFF(MONTH, depDate, currentDate);
IF diff > 12 && diff <= 13 && bookValue > 0
THEN
SET depDate = currentDate;
SET dep = dep;
SET acc = acc + dep;
SET bookValue = bookValue - dep;
IF bookValue = 0
THEN
SET depStatus = 'depreciated';
END IF;
INSERT INTO depreciation (dep_date, dep_amount, dep_status, dep_description, dep_commnet, asset_ass_id, book_value, accumulative_value)
VALUES (depDate, dep, depStatus, 1, 1, assetId, bookValue, acc);
END IF;
END LOOP;
-- 5. Close cursor when done
CLOSE emp_cursor;
SELECT diff;
END;

SQL Server while loop and dynamic SQL

I am trying to execute the dynamic SQL from SQL Server while loop. When the only print statement is executed query prints in the correct format but does not execute dynamic SQL with execute() or SP_EXECUTESQL. Please suggest.
Code:
WHILE( #count > 0 )
BEGIN
SELECT
#minID = MinID,
#maxID = MaxID
FROM
IDRange
WHERE
ID = #count
SET #QueryString = ' UPDATE
SD WITH(TABLOCk)
SET a = S4H.ID
FROM
A (nolock) S4H
INNER JOIN B SD on S4H.col = SD.col AND S4H.col1 = SD.col1
WHERE
SD.ID between ' + convert (varchar,#minID )+' AND '+convert (varchar,#maxID )+' AND
S4H.ID <= SD.ID AND
SD.ID <= S4h.ROWID'
SET #count= #count - 1'
print #QueryString
EXECUTE (#QueryString)
EXECUTE sp_executesql #QueryString, N'#minID INT,#maxID INT', #minID = #minID,#maxID= #maxID
--EXEC SP_EXECUTESQL #QueryString
--SELECT #Rcount= ##Rowcount
SET #count= #count - 1
END
END
There is an open ' at the end of SET #count= #count, remove that.
Use EXEC (#QueryString) to execute it, so it will look this this:
WHILE(#count > 0)
BEGIN
SELECT
#minID = MinID,
#maxID = MaxID
FROM IDRange
WHERE ID = #count;
SET #QueryString = ' UPDATE
SD WITH(TABLOCk)
SET a = S4H.ID
FROM
A (nolock) S4H
INNER JOIN B SD on S4H.col = SD.col AND S4H.col1 = SD.col1
WHERE
SD.ID between '+CONVERT(VARCHAR, #minID)+' AND '+CONVERT(VARCHAR, #maxID)+' AND
S4H.ID <= SD.ID AND
SD.ID <= S4h.ROWID';
PRINT #QueryString;
EXEC (#QueryString);
SET #count = #count - 1;
END;
-- Dynamic Query closing was not proper SET #count= #count - 1' Repeated twice which is not proper
WHILE( #count > 0 )
BEGIN
SELECT #minID = MinID, #maxID = MaxID
FROM IDRange
WHERE ID = #count
SET #QueryString = ' UPDATE
SD WITH(TABLOCk)
SET a = S4H.ID
FROM
A (nolock) S4H
INNER JOIN B SD on S4H.col = SD.col AND S4H.col1 = SD.col1
WHERE
SD.ID BETWEEN ' + convert (varchar,#minID )+' AND '+convert (varchar,#maxID )+' AND
S4H.ID <= SD.ID AND
SD.ID <= S4h.ROWID'
PRINT #QueryString
EXECUTE (#QueryString)
SET #count= #count - 1
END

How to optimize a slow running query with multiple 'AND' statements?

I'm trying to cleanup old records from a DB (SQL Server) which has only 1 table with millions of records in it & the query I'm using takes forever to run. Is it possible to optimize this one?
DECLARE #Count INT = -15;
DECLARE #CountThreshold INT = -4;
DECLARE #DeletedRows INT = 0 WHILE #Count <= #CountThreshold BEGIN BEGIN TRANSACTION T1
DELETE [Log]
WHERE [Date] <= DATEADD(d, #Count, getdate())
AND (windowsIdentity LIKE 'BTA-SL%'
AND Environment ='')
SET #Count = #Count + 1;
PRINT 'DELETED '+ CONVERT(VARCHAR(10),#DeletedRows) + ' ROWS ON COUNT - '+ CONVERT(VARCHAR(10),#Count)
COMMIT TRANSACTION T1 END
Your while is not util because #CountThreshold < #Count. your can do simply this:
DECLARE #CountThreshold INT = -4;
DELETE [Log]
WHERE [Date] <=DATEADD(d, #CountThreshold , getdate())
AND (windowsIdentity LIKE 'BTA-SL%'
AND Environment ='');
PRINT 'DELETED '+ CAST(##ROWCOUNT AS VARCHAR(10)) + ' rows deleted'
COMMIT TRANSACTION T1 END

Creating an Instead of insert Trigger SQL

I am a DBA with my company. I am trying to create trigger that will check any insert statement for duplicates first then if none allow the original insert. Not even sure this can be done. The insert statements may be written by various users so the statements will never be the same. All I have found so far is the check for duplicates but the insert statement is then hard coded in the trigger. My plan is also to check update as well, but it is not important right now.
Here is my current code.
ALTER TRIGGER [dbo].[BlockDuplicatesOnTable]
ON [dbo].[blockduplicates]
Instead of INSERT, Update
AS
BEGIN
SET NOCOUNT ON;
Declare #ProviderAcctNumber nvarchar(50)
Declare #Referredby int
Declare #Action as char(1)
Declare #Count as int
Set #Action = 'I'
Select #Count = Count(*) from DELETED
IF #Count > 0
Begin
Set #Action = 'D'
Select #Count = count(*) from INSERTED
IF #Count > 0
Set #Action = 'U'
IF #Action = 'I'
Begin
IF not exists (Select 1 from inserted as i
inner join dbo.blockduplicates as b
on i.ProviderAcctNumber = b.ProviderAcctNumber
and i.Referredby = b.Referredby)
Begin
--execute original insert
End
Else
Begin
Print 'Duplicate insert'
Raiserror ('Duplicate Entry for Insert',16,1);
Return
End
End
Else IF #Action = 'U'
Begin
Select #ProviderAcctNumber = ProviderAcctNumber, #Referredby = Referredby from inserted
IF Not exists (Select 1 from deleted where ProviderAcctNumber = #ProviderAcctNumber and Referredby = #Referredby)
Begin
Print 'Update Statement is True';
End
Else
Begin
Print 'duplicate'
Raiserror ('Duplicate Entry for Update',16,1);
Return
End
End
End
End;

Azure SQL Database trigger is not running and I cannot seem to figure out why

I'm trying to update the status column of a table when the table has been edited. I use a similar trigger on another table to update this status but when I try to put the trigger on the table itself it will not run.
ALTER TRIGGER dbo.tr_Invoice_SetInvoiceStatus_Update
ON dbo.Invoice
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #InvoiceID int
DECLARE #TotalInvoice DECIMAL(18,2)
DECLARE #TotalPayments DECIMAL(18,2)
DECLARE #InvoiceStatus NVARCHAR(50)
DECLARE #InvoiceStatusID INT
SELECT #InvoiceID = ID
FROM Inserted i
SELECT #TotalPayments = IsNull(Sum(ph.Amount), 0)
FROM PaymentHistory ph
WHERE InvoiceID = #InvoiceID
SELECT #TotalInvoice = ISNULL(Total, 0)
FROM Invoice
WHERE ID = #InvoiceID
IF (#TotalPayments > 0)
BEGIN
IF (#TotalPayments >= #TotalInvoice)
BEGIN
SELECT #InvoiceStatus = 'Paid'
END
ELSE
BEGIN
SELECT #InvoiceStatus = 'Partially Paid'
END
END
ELSE
BEGIN
SELECT #InvoiceStatus = 'Open'
END
SELECT #InvoiceStatusID = ID
FROM dbo.InvoiceStatus
WHERE [Name] = #InvoiceStatus
UPDATE dbo.Invoice
SET InvoiceStatusID = #InvoiceStatusID
WHERE ID = #InvoiceID
END
GO
Any help would be great?