While Loop SQL not populating complete results - sql

Question: the iteration happens only till record 131 and gives accurate value, after that the parameter #ADE_END_DATE returns a NULL value, why would that be? Below is my code.
Additionally I noticed the column Leave_Date has NULL values and the iteration stops and returns NULL value for the parameter #ADE_END_DATE where the NULL value starts.
Thanks for your help.
BEGIN
DECLARE #HIREDATEPlus1Yr DATETIME
DECLARE #ADE_Leave_Date DATETIME
DECLARE #ADE_End_Date DATETIME
DECLARE #ADE_Start_Date DATETIME
DECLARE #DATECAL DATETIME
DECLARE #i INT
DECLARE #j INT
DECLARE #Loop_length INT
DECLARE #ID VARCHAR(18)
-- start of loop
SET #j = 1
-- Loop length will equal to the list of all ADRs
SET #Loop_Length = (SELECT COUNT([AD_ID])
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE Status NOT IN ('MANAGER', 'TBH', 'FROZEN'))
-- Loop through each ADRs
WHILE (#j <= #Loop_length)
BEGIN
-- Loop through each ADRs
SET #i = 0
-- Find AD ID
SET #ID = (SELECT TOP 1 [AD_ID] FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE [AD_ID] NOT IN (SELECT TOP (#j-1) [AD_ID]
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE ([AD_ID] IS NOT NULL
AND Status NOT IN ('MANAGER', 'TBH', 'FROZEN'))))
-- Find the start date of the ADR
SET #ADE_Start_Date = (SELECT TOP 1 [Hire_Date]
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE [AD_ID] NOT IN (SELECT TOP (#j-1) [AD_ID]
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE ([AD_ID] IS NOT NULL
AND Status NOT IN ('MANAGER', 'TBH', 'FROZEN'))))
-- Hire date plus 1 year
SET #HIREDATEPlus1Yr = DATEADD(YEAR, 1, #ADE_Start_Date)
--Adding Leave Date
SET #ADE_Leave_Date = (SELECT TOP 1 [LEAVE_DATE]
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE [AD_ID] NOT IN (SELECT TOP (#j-1) [AD_ID]
FROM [DS_ADHOC_MOPs].[ADE].[List]
WHERE ([AD_ID] IS NOT NULL
AND Status NOT IN ('MANAGER', 'TBH', 'FROZEN'))))
-- Set a temporary variable which will be 1 year from now. Use the Date ADD formulae to start date, if they are leaver before one year then add leave date (Use IF): DONE
-- Put everything inside the while loop and add opportunity selecting to it.
IF #ADE_Leave_Date IS NULL
SET #ADE_End_Date = DATEADD(YEAR, 1, #ADE_Start_Date)
ELSE IF #HIREDATEPlus1Yr < #ADE_Leave_Date
SET #ADE_End_Date = DATEADD(YEAR, 1, #ADE_Start_Date)
ELSE
SET #ADE_End_Date = #ADE_Leave_Date
SET #DATECAL = datediff(DAY, #ADE_Start_Date, #ADE_End_Date)
SET #j = #j + 1
UPDATE #TEMPTABLEEEE
SET [#ADE_End_Date] = #ADE_End_Date
WHERE #ID = AD_ID
END
SELECT * FROM #TEMPTABLEEEE
END

I'm not sure why you are using a WHILE loop. It looks like this code could be much simplified. SQL is a set based language. Whenever possible, you should try to handle your data as a whole set instead of breaking it down into row by row evaluations.
Does this give you what you need? If the table has more than one row for each AD_ID, you will need to get the MAX() or MIN() Hire_Date/LEAVE_DATE. To improve the answer, consider providing sample data.
UPDATE t
SET [#ADE_End_Date] = ed.ADE_EndDate
FROM #TEMPTABLEEEE AS t
INNER JOIN (
SELECT AD_ID
,CASE
WHEN LEAVE_DATE IS NULL THEN DATEADD(YEAR,1,Hire_Date)
WHEN DATEADD(YEAR,1,Hire_Date) < LEAVE_DATE THEN DATEADD(YEAR,1,Hire_Date)
ELSE LEAVE_DATE
END AS ADE_EndDate
FROM DS_ADHOC_MOPs.ADE.List
WHERE Status NOT IN ('MANAGER', 'TBH', 'FROZEN')
) AS ed ON t.AD_ID = ed.AD_ID

Related

How to improve while loop insert performance in sql server?

Here is my SQL Query. It's insert almost 6500+ row from temp table. But its takes 15+ mins! . How can i improve this ? Thanks
ALTER proc [dbo].[Process_bill]
#userid varchar(10),
#remark nvarchar(500),
#tdate date ,
#pdate date
as
BEGIN
IF OBJECT_ID('tempdb.dbo..#temptbl_bill', 'U') IS NOT NULL
DROP TABLE #temptbl_bill;
CREATE TABLE #temptbl_bill (
RowID int IDENTITY(1, 1),
------------
)
// instert into temp table
DECLARE #NumberRecords int, #RowCounter int
DECLARE #batch INT
SET #batch = 300
SET #NumberRecords = (SELECT COUNT(*) FROM #temptbl_bill)
SET #RowCounter = 1
SET NOCOUNT ON
BEGIN TRANSACTION
WHILE #RowCounter <= #NumberRecords
BEGIN
declare #clid int
declare #hlid int
declare #holdinNo nvarchar(150)
declare #clientid nvarchar(100)
declare #clientName nvarchar(50)
declare #floor int
declare #radius nvarchar(50)
declare #bill money
declare #others money
declare #frate int
declare #due money
DECLARE #fine money
DECLARE #rebate money
IF #RowCounter > 0 AND ((#RowCounter % #batch = 0) OR (#RowCounter = #NumberRecords))
BEGIN
COMMIT TRANSACTION
PRINT CONCAT('Transaction #', CEILING(#RowCounter/ CAST(#batch AS FLOAT)), ' committed (', #RowCounter,' rows)');
BEGIN TRANSACTION
END;
// multiple select
// insert to destination table
Print 'RowCount -' +cast(#RowCounter as varchar(20)) + 'batch -' + cast(#batch as varchar(20))
SET #RowCounter = #RowCounter + 1;
END
COMMIT TRANSACTION
PRINT CONCAT('Transaction #', CEILING(#RowCounter/ CAST(#batch AS FLOAT)), ' committed (',
#RowCounter,' rows)');
SET NOCOUNT OFF
DROP TABLE #temptbl_bill
END
GO
As has been said in comments, the loop is completely unnecessary. The way to improve the performance of any loop is to remove it completely. Loops are a last resort in SQL.
As far as I can tell your insert can be written with a single statement:
INSERT tbl_bill(clid, hlid, holdingNo,ClientID, ClientName, billno, date_month, unit, others, fine, due, bill, rebate, remark, payment_date, inserted_by, inserted_date)
SELECT clid = c.id,
hlid = h.id,
h.holdinNo ,
c.cliendID,
clientName = CAST(c.clientName AS NVARCHAR(50)),
BillNo = CONCAT(h.holdinNo, MONTH(#tdate), YEAR(#tdate)),
date_month = #tDate,
unit = 0,
others = CASE WHEN h.hfloor = 0 THEN rs.frate * (h.hfloor - 1) ELSE 0 END,
fine = bs.FineRate * b.Due / 100,
due = b.Due,
bill = #bill, -- This is declared but never assigned
rebate = bs.rebate,
remark = #remark,
payment_date = #pdate,
inserted_by = #userid,
inserted_date = GETDATE()
FROM ( SELECT id, clientdID, ClientName
FROM tbl_client
WHERE status = 1
) AS c
INNER JOIN
( SELECT id, holdinNo, [floor], connect_radius
FROM tx_holding
WHERE status = 1
AND connect_radius <> '0'
AND type = 'Residential'
) AS h
ON c.id = h.clid
LEFT JOIN tbl_radius_setting AS rs
ON rs.radius= CONVERT(real,h.connect_radius)
AND rs.status = 1
AND rs.type = 'Non-Govt.'
LEFT JOIN tbl_bill_setting AS bs
ON bs.Status = 1
LEFT JOIN
( SELECT hlid,
SUM(netbill) AS Due
FROM tbl_bill AS b
WHERE date_month < #tdate
AND (b.ispay = 0 OR b.ispay IS NULL)
GROUP BY hlid
) AS b
ON b.hlid = h.id
WHERE NOT EXISTS
( SELECT 1
FROM tbl_bill AS tb
WHERE EOMONTH(#tdate) = EOMONTH(date_month)
AND tb.holdingNo = h.holdinNo
AND (tb.update_by IS NOT NULL OR tb.ispay=1)
);
Please take this with a pinch of salt, it was quite hard work trying to piece together the logic, so it may need some minor tweaks and corrections
As well as adapting this to work as a single statement, I have made a number of modifications to your existing code:
Swapped NOT IN for NOT EXISTS to avoid any issues with null records. If holdingNo is nullable, they are equivalent, if holdingNo is nullable, NOT EXISTS is safer - Not Exists Vs Not IN
The join syntax you are using was replaced 27 years ago, so I switched from ANSI-89 join syntax to ANSI-92. - Bad habits to kick : using old-style JOINs
Changed predicates of YEAR(date_month) = YEAR(#tDate) AND MONTH(date_month) = MONTH(#tDate) to become EOMONTH(#tdate) = EOMONTH(date_month). These are syntactically the same, but EOMONTH is Sargable, whereas MONTH and YEAR are not.
Then a few further links/suggestions that are directly related to changes I have made
Although I removed the while lopp, don't fall into the trap of thinking this is better than a cursor. A properly declared cursor will out perform a while loop like yours - Bad Habits to Kick : Thinking a WHILE loop isn't a CURSOR
The general consensus is that prefixing object names is not a good idea. It should either be obvious from the context if an object is a table/view or function/procedure, or it should be irrelevant - i.e. There is no need to distinguish between a table or a view, and in fact, we may wish to change from one to the other, so having the prefix makes things worse, not better.
The average ratio of time spent reading code to time spent writing code is around 10:1 - It is therefore worth the effort to format your code when you are writing it so that it is easy to read. This is hugely subjective with SQL, and I would not recommend any particular conventions, but I cannot believe for a second you find your original code free flowing and easy to read. It took me about 10 minutes just unravel the first insert statement.
EDIT
The above is not correct, EOMONTH() is not sargable, so does not perform any better than YEAR(x) = YEAR(y) AND MONTH(x) = MONTH(y), although it is still a bit simpler. If you want a truly sargable predicate you will need to create a start and end date using #tdate, so you can use:
DATEADD(MONTH, DATEDIFF(MONTH, '19000101', #tdate), '19000101')
to get the first day of the month for #tdate, then almost the same forumla, but add months to 1st February 1900 rather than 1st January to get the start of the next month:
DATEADD(MONTH, DATEDIFF(MONTH, '19000201', #tdate), '19000201')
So the following:
DECLARE #Tdate DATE = '2019-10-11';
SELECT DATEADD(MONTH, DATEDIFF(MONTH, '19000101', #tdate), '19000101'),
DATEADD(MONTH, DATEDIFF(MONTH, '19000201', #tdate), '19000201');
Will return 1st October and 1st November respectively. Putting this back in your original query would give:
WHERE NOT EXISTS
( SELECT 1
FROM tbl_bill AS tb
WHERE date_month >= DATEADD(MONTH, DATEDIFF(MONTH, '19000101', #tdate), '19000101'),
AND date_month < DATEADD(MONTH, DATEDIFF(MONTH, '19000201', #tdate), '19000201')
AND tb.holdingNo = h.holdinNo
AND (tb.update_by IS NOT NULL OR tb.ispay=1)
);

Do action for each register without ID

I need to determine which registers are about to expire. I have half of the script which shows ones that are close to expiring but I need to save the final date:
declare #FechaIngreso datetime --Variable to determinate begin
declare #FechaEgreso datetime --Variable End
declare #DiasExp Int --Days before Expire
set #DiasExp = 10
select #FechaIngreso = GETDATE() --Get the actual date
select #FechaEgreso = (select top 1 FEC_FINA from FA_DDERI where (DATEDIFF (dd, #FechaIngreso,FEC_FINA) ) = #DiasExp ) --heres the thing I select just 1 with top 1 cause I need save the final date to comparate the dates
select * from FA_DDERI where FEC_FINA = #FechaEgreso
select COUNT(*) from FA_DDERI where FEC_FINA = #FechaEgreso --this tell me how many lines get the condition
I need a condition like the following:
if (select FEC_FINA from FA_DDERI where (DATEDIFF (dd, #FechaIngreso,FEC_FINA) ) = #DiasExp)
print 'The Reso Will Expire'
break
else
continue
print'Any Reso will Expire'
The tables only have 1 unique column and its is not an identity column
Is there a control statement to check every line in the database to prove the condition?
I guess you want this
SELECT FEC_FINA,
CASE
WHEN DATEDIFF (dd, #FechaIngreso,FEC_FINA) ) = #DiasExp THEN 'The Reso Will Expire'
ELSE 'Any Reso will Expire'
END AS resultcol
FROM FA_DDERI

Aggregate Value in UDF Always Zero, Affecting Local Variable

The following UDF returns an integer based on different conditions executed after a query. The variables, #recCount, is always a zero despite the fact that it can also be greater than zero. It should contain the value of inline query using COUNT(*).
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[fnGetConfID] (#eventID INT,
#conferenceID INT,
#companyID VARCHAR(32))
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #confID INT; -- Conference ID we're passing back
DECLARE #curAtt INT; -- Current attendance
DECLARE #maxAtt INT; -- Max attendance
DECLARE #waitConfID INT; --Waitlist value of current conference
DECLARE #recCount INT; -- Total number of employees who selected same conference
SELECT #confID = ec.conferenceID,
#recCount = (SELECT count(*)
FROM tblRegistration r
INNER JOIN tblRegConferences rc
ON r.ID = rc.regID
WHERE r.optfield2 = #companyID
AND r.eventID = #eventID
AND rc.conferenceID = #conferenceID),
#curAtt = ec.currentAttendance,
#maxAtt = ec.maxAttendance,
#waitConfID = ec.waitListProdID
FROM tblEventConferences ec
WHERE ec.conferenceID = #conferenceID
AND ec.isWaitList = 0
-- If no records were found (waitlist item)
IF ##ROWCOUNT = 0
BEGIN
SET #confID = #conferenceID -- use same value we passed in
END
-- records found
ELSE
BEGIN
--Max attendance not reached, return main conference ID
IF #curAtt < #maxAtt
BEGIN
SET #confID = #conferenceID
END
--Max attendance reached, return waitlist ID
IF #curAtt >= #maxAtt
BEGIN
SET #confID = #waitConfID
END
--Company cap reached, return waitlist ID
IF #recCount > 1
BEGIN
SET #confID = #waitConfID
END
END
RETURN #confID
END
Running it as a query, I get values greater than zero for the field companyCnt, which is #recCount's equivalent.
SELECT ec.conferenceID, (
SELECT count(*)
FROM tblRegistration r
INNER JOIN tblRegConferences rc ON r.ID = rc.regID
WHERE
r.optfield2 = '83b90acc-42af-4de2-9279-76e80eb8b73a'
AND
r.eventID = 624
AND
rc.conferenceID = 8848
) AS companyCnt, ec.currentAttendance, ec.maxAttendance, ec.waitListProdID
FROM
tblEventConferences ec
WHERE
ec.conferenceID = 8848
AND
ec.isWaitList = 0
Change #companyid from varchar(32) to varchar(50).
You are experiencing this problem because the length of company in your query is 36 characters where as the variable is declared only for 32 characters SQL server don't give a warning it just truncates the last characters.

Check if date is exists among the dates then add extra one day to that date

i want to add the days(for example 3 days) to the given date. Before adding days we can check the holidays which are already configured in one table.
Here is my sample code. But i am unable to achieve it.
declare #HolidaysList NVARCHAR(250) = '2014-06-29,2014-07-02,2014-07-18,2014-07-30,2014-10-26'
DECLARE #RDATE DATE = '2014-06-28'
declare #addDays int = 3
declare #count int = 0
while(#count < #addDays)
BEGIN
set #RDATE = DATEADD(DAY,1,#RDATE)
--print '1 ' +cast( #RDATE as nvarchar(100))
if exists(SELECT ITEM FROM fnSplit(#HolidaysList,',') WHERE item = #RDATE)
begin
SELECT #RDATE= CONVERT(VARCHAR(10),DATEADD(DAY,1,#RDATE),101)
PRINT 'if '+ CAST( #HRDATE AS NVARCHAR(100))
end
set #count = #count+1
END
PRINT #RDATE
Here fnSplit is a function which returns a table.
In the above script i have to add 3 days to #RDate. Before adding i have to check holidays list i.e in #HolidaysList. If holiday is come then we can add extra date.
in the above script the output is: 2014-08-03 because 29th is holiday and 2nd is also holiday. so output will be 2014-08-03
You can do this without loops:
DECLARE #Holidays TABLE (Item DATE);
INSERT #Holidays
VALUES ('2014-06-29'),('2014-07-02'),('2014-07-18'),('2014-07-30'),('2014-10-26');
DECLARE #RDATE DATE = '2014-06-28',
#addDays INT = 3;
WITH CTE AS
( SELECT *,
RowNumber = ROW_NUMBER() OVER(ORDER BY d.Date)
FROM ( SELECT DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY object_id), #RDATE)
FROM sys.all_objects
) AS d (Date)
WHERE NOT EXISTS
( SELECT 1
FROM #Holidays AS h
WHERE h.Item = d.Date
)
)
SELECT Date
FROM CTE
WHERE RowNumber = #addDays;
The principal is this part:
SELECT Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY object_id), #RDATE)
FROM sys.all_objects
Will just generate a list of dates onwards from your starting date. You then exclude all holidays from this list using NOT EXISTS, and add a rank to all these days using ROW_NUMBER(). It is then just a case of selecting the date with the required rank.
declare #HolidaysList NVARCHAR(250) = '2014-06-29,2014-06-30,2014-07-01,2014-07-30,2014-07-18,2014-10-26'
DECLARE #RDATE DATE = '2014-06-28'
declare #addDays int = 3
declare #count int = 0
DECLARE #EXISTS int = 0
while(#count < #addDays)
BEGIN
set #RDATE = DATEADD(DAY,1,#RDATE)
if exists(SELECT ITEM FROM fnSplit(#HolidaysList,',') WHERE item = #RDATE)
set #EXISTS = #EXISTS+1
set #count = #count+1
END
if(#EXISTS is not null)
set #RDATE = DATEADD(DAY,#EXISTS,#RDATE)
print #RDATE

SQL Stored Procedure set variables using SELECT

I have a stored procedure in SQL Server 2005 with multiple variables and I want to set the values of these variables using a select statement. All three variables come from a same table and there should be a way to set them using one select statement instead of the way I currently have as shown below. Please help me to figure it out.
DECLARE #currentTerm nvarchar(max)
DECLARE #termID int
DECLARE #endDate datetime
SET #currentTerm =
(
Select CurrentTerm from table1 where IsCurrent = 1
)
SET #termID =
(
Select TermID from table1 where IsCurrent = 1
)
SET #endDate =
(
Select EndDate from table1 where IsCurrent = 1
)
select #currentTerm = CurrentTerm, #termID = TermID, #endDate = EndDate
from table1
where IsCurrent = 1
One advantage your current approach does have is that it will raise an error if multiple rows are returned by the predicate. To reproduce that you can use.
SELECT #currentTerm = currentterm,
#termID = termid,
#endDate = enddate
FROM table1
WHERE iscurrent = 1
IF( ##ROWCOUNT <> 1 )
BEGIN
RAISERROR ('Unexpected number of matching rows',
16,
1)
RETURN
END