I've been working on a query that should :
business logic - show all members who attended the gym(selected from dropdown) within a specified date range.
Problem encountered -I realised that results shows duplicates of which mustn't , may you please assist.
Below is the query I used.
Declare #StartDate datetime = '29 May 2014'
Declare #EndDate datetime = '29 May 2014'
DEclare #SiteID INT =14
Declare #StartTime datetime = '05:00 AM'
Declare #EndTime datetime = '10:00 PM'
Declare #Start datetime = cast (#StartDate + ' '+ #StartTime as datetime)
Declare #End datetime = cast(#EndDate + ' ' + #EndTime as datetime )
SELECT isnull(atc.totalaccepted,0) TotalAcceptedVisits,ISNULL(att.TotalOverrideVisits, 0) AS TotalOverrides, isnull(att1.TotalOverrideDenieds,0) as TotalDenies, ast.Name as VisitiedSite, c.FirstName + ' ' + c.LastName as Name, md.MemRefNo
FROM Contacts c
inner JOIN Attendance a on a.ContactGUID = c.GUID
inner JOIN MemberDetail md on md.ContactGUID = c.GUID
inner JOIN Sites ast on a.SiteID = ast.ID
OUTER APPLY
(
SELECT a1.contactguid,COUNT(*) totalaccepted
FROM Attendance a1
WHERE a1.contactguid = c.guid
AND (a1.IsSwipeSuccessful = 1)
AND a1.accessoverridereasonid IS NULL
AND a1.AttendDate BETWEEN #Start AND #End
group by a1.contactguid
)atc
outer APPLY
(
SELECT a2.contactguid, COUNT(*) TotalOverrideVisits
FROM Attendance a2
WHERE a2.contactguid = c.guid
And (a.IsSwipeSuccessful = 1)
AND a2.accessoverridereasonid IS NOT NULL
AND a2.AttendDate BETWEEN #Start AND #End
and (a2.SiteID = #SiteID OR #SiteID = 0)
group by a2.contactguid
) att
OUTER APPLY
(
SELECT a3.contactguid,COUNT(*) TotalOverrideDenieds
FROM Attendance a3
WHERE a3.contactguid = c.guid
AND a3.IsSwipeSuccessful = 0
AND a3.accessoverridereasonid IS NULL
AND a3.AttendDate BETWEEN #Start AND #end
group by a3.contactguid
) att1
where (a.SiteID = #SiteID OR #SiteID = 0)
and a.AttendDate BETWEEN #Start AND #End
order by md.MemRefNo
Results: as you can see below there are members repeating themselves, I only need to see one row of each member with the number of total visits if any, overrides if any and denied visits if any.
TotalAcceptedVisits TotalOverrides TotalDenies VisitiedSite Name memRefNo
1 0 0 Groblersdal Jean T G0030
1 1 1 Groblersdal Koky Bakkes G0032
1 0 1 Groblersdal Koky Bakkes G0032
1 1 1 Groblersdal Koky Bakkes G0032
1 0 0 Groblersdal Naomi Fisher G0035
1 0 0 Groblersdal Arthur Bart G0089
1 0 0 Groblersdal Tulinda Swi G0034
1 1 0 Groblersdal Devon Mooi G0008
1 1 0 Groblersdal Devon Mooi G0008
I think you will need something like this:
Declare #StartDate datetime = '29 May 2014'
Declare #EndDate datetime = '29 May 2014'
DEclare #SiteID INT =14
Declare #StartTime datetime = '05:00 AM'
Declare #EndTime datetime = '10:00 PM'
Declare #Start datetime = cast (#StartDate + ' '+ #StartTime as datetime)
Declare #End datetime = cast(#EndDate + ' ' + #EndTime as datetime )
SELECT CASE WHEN TotalAcceptedVisits > 0 THEN 1 ELSE 0 END AS TotalAcceptedVisits,
CASE WHEN TotalOverrides > 0 THEN 1 ELSE 0 END AS TotalOverrides,
CASE WHEN TotalDenies > 0 THEN 1 ELSE 0 END AS TotalDenies,
VisitiedSite, Name, MemRefNo
FROM
(SELECT SUM(isnull(atc.totalaccepted,0)) TotalAcceptedVisits, SUM(ISNULL(att.TotalOverrideVisits, 0)) AS TotalOverrides, SUM(isnull(att1.TotalOverrideDenieds,0)) as TotalDenies, ast.Name as VisitiedSite, c.FirstName + ' ' + c.LastName as Name, md.MemRefNo
FROM Contacts c
inner JOIN Attendance a on a.ContactGUID = c.GUID
inner JOIN MemberDetail md on md.ContactGUID = c.GUID
inner JOIN Sites ast on a.SiteID = ast.ID
OUTER APPLY
(
SELECT a1.contactguid,COUNT(*) totalaccepted
FROM Attendance a1
WHERE a1.contactguid = c.guid
AND (a1.IsSwipeSuccessful = 1)
AND a1.accessoverridereasonid IS NULL
AND a1.AttendDate BETWEEN #Start AND #End
group by a1.contactguid
)atc
outer APPLY
(
SELECT a2.contactguid, COUNT(*) TotalOverrideVisits
FROM Attendance a2
WHERE a2.contactguid = c.guid
And (a.IsSwipeSuccessful = 1)
AND a2.accessoverridereasonid IS NOT NULL
AND a2.AttendDate BETWEEN #Start AND #End
and (a2.SiteID = #SiteID OR #SiteID = 0)
group by a2.contactguid
) att
OUTER APPLY
(
SELECT a3.contactguid,COUNT(*) TotalOverrideDenieds
FROM Attendance a3
WHERE a3.contactguid = c.guid
AND a3.IsSwipeSuccessful = 0
AND a3.accessoverridereasonid IS NULL
AND a3.AttendDate BETWEEN #Start AND #end
group by a3.contactguid
) att1
where (a.SiteID = #SiteID OR #SiteID = 0)
and a.AttendDate BETWEEN #Start AND #End
GROUP BY atc.totalaccepted, att.TotalOverrideVisits, att1.TotalOverrideDenieds, ast.Name, c.FirstName + ' ' + c.LastName, md.MemRefNo) TB
ORDER BY MemRefNo
Since, you don't want the SUM of the values, then you can do something as above which basically embeds your SELECT as a subquery and then has a CASE statement to see whether the Total Values is greater than 0, if so return 1 else 0.
Related
I'm working on a T-SQL function to return the count of two categories (enrolls and verifies) for each month within a specific period. The function below works only when there is at least a count greater than 1 for each month. What I'd like to do is given a start date of the lowest date found in the source table, an end date of today, and a given number of months, return the counts of enrolls, and the count of verifies for a given user id between the start and end period of months.
Ideally I'd like the range-period data be formatted as MMM-YYYY. For any months during this period where there are no counts, I'd like those values to show a count of 0.
RELEASED_DATETIME is a DATETIME column in the INTERACTION_SESSION_T table.
Valid Data for a period of 7 Months
RANGEPERIOD | ENROLLS | VERIFIES
------------+---------+---------
Nov-2017 | 15 | 15
Dec-2017 | 150 | 2582
Jan-2018 | 0 | 0
Feb-2018 | 0 | 98
Mar-2018 | 10 | 0
Apr-2018 | 8 | 0
May-2018 | 12 | 85
My code:
CREATE FUNCTION [dbo].[SVE_GET_ENROL_VERIFY_COUNT_MONTH_R]
(#DEV_ID INT, #NUM_MONTHS INT)
RETURNS #D TABLE (RANGEPERIOD VARCHAR(10),
TOTAL_ENROLLMENTS INT,
TOTAL_VERIFICATIONS INT)
AS
BEGIN
DECLARE #cur VARCHAR(12); -- stores the current record date the cursor points to
DECLARE #sdate DATETIME; -- start date of range
DECLARE #edate DATETIME; -- end date of range
DECLARE #tot_enrls INT; -- stores the #cur date's enrollment count
DECLARE #tot_verfs INT; -- stores the #cur date's verification count
SELECT #sdate = MIN(RELEASED_DATETIME)
FROM SVE_INTERACTION_SESSION_T
WHERE (DEVELOPER_OWNER_ID = #DEV_ID OR DEVELOPER_USER_ID = #DEV_ID)
AND RELEASED_DATETIME >= DATEADD(MONTH, -#NUM_MONTHS + 1, GETDATE())
SELECT #edate = GETDATE();
INSERT INTO #D (RANGEPERIOD)
(SELECT
RANGEPERIOD = CONCAT(SUBSTRING(datename(month, I.RELEASED_DATETIME), 1, 3 ), '-', YEAR(I.RELEASED_DATETIME))
FROM
SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK)
INNER JOIN
SVE_APPLICATIONS_T AS A WITH (NOLOCK) ON I.APPLICATION_ID = A._ID
WHERE
(I.DEVELOPER_OWNER_ID = #DEV_ID OR I.DEVELOPER_USER_ID = #DEV_ID)
AND I.RELEASED_CODE = 'C'
AND A.IS_DELETED = 0
AND I.RELEASED_DATETIME >= #sdate
AND I.RELEASED_DATETIME < DATEADD(day, 1, #edate)
GROUP BY
YEAR(I.RELEASED_DATETIME), MONTH(I.RELEASED_DATETIME),
DATENAME(MONTH, I.RELEASED_DATETIME)
);
-- Start of cursor
DECLARE DTY CURSOR FOR
SELECT RANGEPERIOD FROM #d
OPEN DTY
-- Fetch the first row
FETCH NEXT FROM DTY INTO #cur
WHILE ##FETCH_STATUS = 0
BEGIN
SET #tot_enrls = (SELECT COUNT(DISTINCT I._ID) AS TOT_ENROLLS
FROM SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK)
INNER JOIN SVE_ENROLL_SESSION_T AS E WITH (NOLOCK) ON E.INTERACTION_SESSION_ID = I._ID
INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK) ON I.APPLICATION_ID = A._ID
WHERE
(I.DEVELOPER_OWNER_ID = #DEV_ID OR I.DEVELOPER_USER_ID = #DEV_ID)
AND E.INSTANCE_ID = 0
AND E.COMPLETION_STATUS = 'T'
AND I.RELEASED_CODE = 'C'
AND A.IS_DELETED = 0
AND CONCAT( SUBSTRING( datename( month, I.RELEASED_DATETIME ), 1, 3 ), '-', YEAR( I.RELEASED_DATETIME ) ) = #cur
);
SET #tot_verfs = (
SELECT
COUNT( DISTINCT I._ID ) AS TOT_VERIFYS
FROM
SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) INNER JOIN SVE_VERIFY_SESSION_T AS V WITH (NOLOCK)
ON
V.INTERACTION_SESSION_ID = I._ID INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK)
ON
I.APPLICATION_ID = A._ID
WHERE
( I.DEVELOPER_OWNER_ID = #DEV_ID OR I.DEVELOPER_USER_ID = #DEV_ID ) AND
V.INSTANCE_ID = 0 AND
V.COMPLETION_STATUS = 'S' AND
I.RELEASED_CODE = 'C' AND
A.IS_DELETED = 0 AND
CONCAT( SUBSTRING( datename( month, I.RELEASED_DATETIME ), 1, 3 ), '-', YEAR( I.RELEASED_DATETIME ) ) = #cur
);
-- Updates the fields in the return table
UPDATE
#d
SET
TOTAL_ENROLLMENTS = #tot_enrls,
TOTAL_VERIFICATIONS = #tot_verfs
WHERE
RANGEPERIOD = #cur
-- Fetch the next row and repeat the above process
FETCH NEXT FROM DTY INTO #cur
END
-- Updates the return table by setting all null values to 0 for better visuals
UPDATE #d SET TOTAL_ENROLLMENTS = 0, TOTAL_VERIFICATIONS = 0 WHERE TOTAL_ENROLLMENTS IS NULL
RETURN
END
It might be worth noting that I already do something similar for a range of days, and this works flawlessly. The only difference is I use a calendar table for the day range function. I know the secret sauce is in the temporary table that gets built from the calendar table, which i attempted to modify the month function to suit, but I'm at this point flustered
Here's the day function which works great
ALTER FUNCTION [dbo].[SVE_GET_ENROL_VERIFY_COUNT_R]( #DEV_ID INT, #NUM_DAYS INT )
RETURNS #D TABLE
(
RANGEDATE DATE,
TOTAL_ENROLLMENTS INT,
TOTAL_VERIFICATIONS INT
)
AS
BEGIN
DECLARE #s DATE; -- start date of range
DECLARE #e DATE; -- end date of range
DECLARE #cur DATE; -- stores the current record date the cursor points to
DECLARE #tot_enrls INT; -- stores the #cur date's enrollment count
DECLARE #tot_verfs INT; -- stores the #cur date's verification count
SET #e = getdate(); -- stores today's date as the end date
SET #s = DATEADD( dd, -( #NUM_DAYS-1 ), #e ); -- Subtract 1 from the incoming day and setup the accurate range of days
INSERT INTO #d ( RANGEDATE )
(
SELECT
RANGEDATE = c.d
FROM
Calendar AS c LEFT OUTER JOIN SVE_INTERACTION_SESSION_T as I WITH (NOLOCK)
ON
I.RELEASED_DATETIME >= #s AND I.RELEASED_DATETIME < = #e AND
c.d = CONVERT( DATE, I.RELEASED_DATETIME )
WHERE
c.d >= #s AND c.d <= #e
GROUP BY c.d
);
-- Start of cursor
DECLARE DTY CURSOR FOR
SELECT RANGEDATE FROM #d
OPEN DTY
-- Fetch the first row
FETCH NEXT FROM DTY INTO #cur
WHILE ##FETCH_STATUS = 0
BEGIN
-- Gets the developer's enrollment count for the current record's day
SET #tot_enrls = (
SELECT
COUNT( DISTINCT I._ID ) AS TOT_ENROLLS
FROM
SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) INNER JOIN SVE_ENROLL_SESSION_T AS E WITH (NOLOCK)
ON
E.INTERACTION_SESSION_ID = I._ID INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK)
ON
I.APPLICATION_ID = A._ID
WHERE
( I.DEVELOPER_OWNER_ID = #DEV_ID OR I.DEVELOPER_USER_ID = #DEV_ID ) AND
E.INSTANCE_ID = 0 AND
E.COMPLETION_STATUS = 'T' AND
I.RELEASED_CODE = 'C' AND
A.IS_DELETED = 0 AND
I.RELEASED_DATETIME >= #cur AND
I.RELEASED_DATETIME < dateadd(day,1,#cur)
);
-- Gets the developer's verification count for the current record's day
SET #tot_verfs = (
SELECT
COUNT( DISTINCT I._ID ) AS TOT_VERIFYS
FROM
SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) INNER JOIN SVE_VERIFY_SESSION_T AS V WITH (NOLOCK)
ON
V.INTERACTION_SESSION_ID = I._ID INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK)
ON
I.APPLICATION_ID = A._ID
WHERE
( I.DEVELOPER_OWNER_ID = #DEV_ID OR I.DEVELOPER_USER_ID = #DEV_ID ) AND
V.INSTANCE_ID = 0 AND
V.COMPLETION_STATUS = 'S' AND
I.RELEASED_CODE = 'C' AND
A.IS_DELETED = 0 AND
I.RELEASED_DATETIME >= #cur AND
I.RELEASED_DATETIME < dateadd(day,1,#cur)
);
-- Updates the fields in the return table
UPDATE
#d
SET
TOTAL_ENROLLMENTS = #tot_enrls,
TOTAL_VERIFICATIONS = #tot_verfs
WHERE
RANGEDATE = #cur
-- Fetch the next row and repeat the above process
FETCH NEXT FROM DTY INTO #cur
END
-- Updates the return table by setting all null values to 0 for better visuals
UPDATE #d SET TOTAL_ENROLLMENTS = 0, TOTAL_VERIFICATIONS = 0 WHERE TOTAL_ENROLLMENTS IS NULL
RETURN
END
This is what my modification looks like for the month period to use the calendar table but its an epic fail, as it only returns one row.
INSERT INTO #d ( RANGEPERIOD )
(
SELECT
RANGEPERIOD = CONCAT( SUBSTRING( datename( month, c.d ), 1, 3 ), '-', YEAR( c.d ) )
FROM
Calendar AS c LEFT OUTER JOIN SVE_INTERACTION_SESSION_T as I WITH (NOLOCK)
ON
(YEAR(I.RELEASED_DATETIME) >= YEAR(#s) AND MONTH(I.RELEASED_DATETIME) >= MONTH(#s)) AND (YEAR(I.RELEASED_DATETIME) <= YEAR(#e) AND MONTH(I.RELEASED_DATETIME) <= MONTH(#e)) AND
c.d = CONVERT( DATE, I.RELEASED_DATETIME )
WHERE
(YEAR(c.d) >= YEAR(#s) AND MONTH(c.d) >= MONTH(#s)) AND (YEAR(c.d) <= YEAR(#e) AND MONTH(c.d) <= MONTH(#e))
GROUP BY c.d
);
Can someone point me in the right direction, or provide an assist?
I cannot give you a specific point but I can give you some guidelines to resolve your problem.
I assume that you need the results in this format. (MMM-YYYY)
In your modification query, you can remove SVE_INTERACTION_SESSION_T table.
Because
You didn't select column from this table.
You were using LEFT JOIN then your result don't get the effect from your condition. The result still comes from whole table of calendar
SELECT RANGEPERIOD = CONCAT(SUBSTRING(datename(MONTH, c.d), 1, 3), '-', YEAR(c.d))
FROM Calendar AS c
WHERE (YEAR(c.d) >= YEAR(#s) AND MONTH(c.d) >= MONTH(#s))
AND (YEAR(c.d) <= YEAR(#e) AND MONTH(c.d) <= MONTH(#e))
GROUP BY c.d
If anyone is interested in a solution to this see my code below. It might not be the most efficient, but at the moment I cannot think of another solution.
CREATE FUNCTION [dbo].[SVE_GET_ENROL_VERIFY_COUNT_MONTH_R]( #DEV_ID INT, #NUM_MONTHS INT )
RETURNS #M TABLE
(
RANGEPERIOD VARCHAR(10),
TOTAL_ENROLLMENTS INT,
TOTAL_VERIFICATIONS INT
)
AS
BEGIN
-- We need to declare a temporary table to hold the days from the period
DECLARE #D TABLE(
RANGEDATE DATETIME,
TOTAL_ENROLLMENTS INT,
TOTAL_VERIFICATIONS INT
)
DECLARE #cur VARCHAR(12); -- stores the current record date the cursor points to
DECLARE #sdate DATETIME; -- start date of range
DECLARE #edate DATETIME; -- end date of range
DECLARE #tot_enrls INT; -- stores the #cur date's enrollment count
DECLARE #tot_verfs INT; -- stores the #cur date's verification count
SELECT #sdate = DATEADD( MONTH, -( #NUM_MONTHS-1 ), GETDATE() )
SELECT #edate = GETDATE();
-- First fill the temp day table with dates from the calendar
INSERT INTO #D ( RANGEDATE ) (
SELECT
RANGEDATE = CONVERT( DATE, c.d )
FROM
Calendar AS c
WHERE
c.d >= #sdate AND c.d <= #edate
GROUP BY c.d
)
-- Next we fill our return table with our grouped by formatted dates
INSERT INTO #M ( RANGEPERIOD ) (
SELECT
RANGEPERIOD = CONCAT( SUBSTRING( datename( month, D.RANGEDATE ), 1, 3 ), '-', YEAR( D.RANGEDATE ) )
FROM
#D AS D
GROUP BY year( D.RANGEDATE ), month( D.RANGEDATE ), datename( month, D.RANGEDATE )
)
-- Start of cursor
DECLARE DTY CURSOR FOR
SELECT RANGEPERIOD FROM #M
OPEN DTY
-- Fetch the first row
FETCH NEXT FROM DTY INTO #cur
WHILE ##FETCH_STATUS = 0
BEGIN
SET #tot_enrls = (
SELECT
COUNT( DISTINCT I._ID ) AS TOT_ENROLLS
FROM
SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) INNER JOIN SVE_ENROLL_SESSION_T AS E WITH (NOLOCK)
ON
E.INTERACTION_SESSION_ID = I._ID INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK)
ON
I.APPLICATION_ID = A._ID
WHERE
( I.DEVELOPER_OWNER_ID = #DEV_ID OR I.DEVELOPER_USER_ID = #DEV_ID ) AND
E.INSTANCE_ID = 0 AND
E.COMPLETION_STATUS = 'T' AND
I.RELEASED_CODE = 'C' AND
A.IS_DELETED = 0 AND
CONCAT( SUBSTRING( datename( month, I.RELEASED_DATETIME ), 1, 3 ), '-', YEAR( I.RELEASED_DATETIME ) ) = #cur
);
SET #tot_verfs = (
SELECT
COUNT( DISTINCT I._ID ) AS TOT_VERIFYS
FROM
SVE_INTERACTION_SESSION_T AS I WITH (NOLOCK) INNER JOIN SVE_VERIFY_SESSION_T AS V WITH (NOLOCK)
ON
V.INTERACTION_SESSION_ID = I._ID INNER JOIN SVE_APPLICATIONS_T AS A WITH (NOLOCK)
ON
I.APPLICATION_ID = A._ID
WHERE
( I.DEVELOPER_OWNER_ID = #DEV_ID OR I.DEVELOPER_USER_ID = #DEV_ID ) AND
V.INSTANCE_ID = 0 AND
V.COMPLETION_STATUS = 'S' AND
I.RELEASED_CODE = 'C' AND
A.IS_DELETED = 0 AND
CONCAT( SUBSTRING( datename( month, I.RELEASED_DATETIME ), 1, 3 ), '-', YEAR( I.RELEASED_DATETIME ) ) = #cur
);
-- Updates the fields in the return table
UPDATE
#M
SET
TOTAL_ENROLLMENTS = #tot_enrls,
TOTAL_VERIFICATIONS = #tot_verfs
WHERE
RANGEPERIOD = #cur
-- Fetch the next row and repeat the above process
FETCH NEXT FROM DTY INTO #cur
END
-- Updates the return table by setting all null values to 0 for better visuals
UPDATE #M SET TOTAL_ENROLLMENTS = 0, TOTAL_VERIFICATIONS = 0 WHERE TOTAL_ENROLLMENTS IS NULL
RETURN
END
I have written two queries and I need help to know which one will be better in term of performance. Both do the same thing. Please also advise if you have better a way of writing the query
Query 1
DECLARE #TotalAvailedLateNightGeneral INT
;WITH CTE (TotalDaysAppliedFor) AS (
SELECT SUM(TotalDaysAppliedFor) AS TotalDaysAppliedFor
FROM [CURFEW_RLX_REQUEST] WITH (NOLOCK)
GROUP BY StaffSeqId, RequestTypeId, MONTH(PermissionRequiredFrom), StatusId, IsActive
HAVING StaffSeqId = 41130
AND RequestTypeId = 3
AND MONTH(PermissionRequiredFrom) = MONTH('2016-03-30 00:00:00.000')
AND StatusId <> 111
AND IsActive = 1
)
SELECT #TotalAvailedLateNightGeneral = SUM(TotalDaysAppliedFor)
FROM CTE
SELECT #TotalAvailedLateNightGeneral
QUERY 2
SELECT SUM(TotalDaysAppliedFor) AS TotalDaysAppliedFor
FROM [CURFEW_RLX_REQUEST] WITH (NOLOCK)
--GROUP BY StaffSeqId,RequestTypeId,MONTH(PermissionRequiredFrom),StatusId,IsActive
WHERE StaffSeqId = 41130
AND RequestTypeId = 3
AND MONTH(PermissionRequiredFrom) = MONTH('2016-03-30 00:00:00.000')
AND StatusId <> 111
AND IsActive = 1
DECLARE #StartDate DATETIME = '2016-03-01'
DECLARE #EndDate DATETIME = '2016-04-01'
SELECT TotalDaysAppliedFor = SUM(TotalDaysAppliedFor)
FROM dbo.QAG_GEMS_CURFEW_RLX_REQUEST
WHERE StaffSeqId = 41130
AND RequestTypeId = 3
AND PermissionRequiredFrom >= #StartDate
AND PermissionRequiredFrom < #EndDate
AND StatusId != 111
AND IsActive = 1
I am using SQL Server 2008 R2. I have a problem in merging tables. We have 500+ employees
I have the following tables:
Calendar table - holds the dates from 1/1/2005 to 12/31/2016
Attendance table - for the attendance of employees
LeaveHistory table - for the leave history
LeaveBreakDown table -for leave break down
Holiday table - for holidays
Our goal, with date range from calendar (11/1/2015 - 11/30/2015)
we want to show the complete days even if the attendance is not equal to the total numbers of days.
Here's my first solution but too slow and without calendar table
FETCH NEXT FROM Employees INTO #EmployeeID,#BranchCode,#IsOfficer, #FirstName, #MiddleName, #LastName, #RankCode;
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #tblData(ActualDate,EmployeeID,BranchCode,IsOfficer, FirstName, MiddleName, LastName, RankCode, LeaveCode, Gender, ShiftCode,ShiftIn,ShiftOut,IsRestDay)
SELECT
#StartDate
, #EmployeeID
, #BranchCode
, #IsOfficer
, #FirstName
, #MiddleName
, #LastName
, #RankCode
, LB.LeaveBreakDownCode
, E.Gender
,ShiftCode = SS.ShiftCode
,ShiftIn = CONVERT(DATETIME, CONVERT(VARCHAR(20), DATEADD(day,0,#StartDate), 101) + ' ' + CONVERT(VARCHAR(20), SS.ShiftIn,108))
,ShiftOut = CASE
WHEN SS.ShiftOut < SS.ShiftIn THEN CONVERT(DATETIME, CONVERT(VARCHAR(20), DATEADD(day,1,#StartDate), 101) + ' ' + CONVERT(VARCHAR(20), SS.ShiftOut ,108))
ELSE CONVERT(DATETIME, CONVERT(VARCHAR(20), DATEADD(day,0,#StartDate), 101) + ' ' + CONVERT(VARCHAR(20), SS.ShiftOut ,108))
END
,IsRestDay = CASE
WHEN SS.Sunday = 1 AND DATEPART(weekday, #StartDate) = 1 THEN 1
WHEN SS.Monday = 1 AND DATEPART(weekday, #StartDate) = 2 THEN 1
WHEN SS.Tuesday = 1 AND DATEPART(weekday, #StartDate) = 3 THEN 1
WHEN SS.Wednesday = 1 AND DATEPART(weekday, #StartDate) = 4 THEN 1
WHEN SS.Thursday = 1 AND DATEPART(weekday, #StartDate) = 5 THEN 1
WHEN SS.Friday = 1 AND DATEPART(weekday, #StartDate) = 6 THEN 1
WHEN SS.Saturday = 1 AND DATEPART(weekday, #StartDate) = 7 THEN 1
ELSE 0
END
FROM Employees E
LEFT JOIN (
SELECT
LB.LeaveBreakDownCode
, LB.EmployeeID
FROM LeaveBreakDown LB
INNER JOIN LeaveHistory LH ON LH.LeaveHistoryCode = LB.LeaveHistoryCode AND LB.DateLeave = #StartDate AND LH.Status IN ('0','1')
WHERE LB.EmployeeID = #EmployeeID
) LB ON LB.EmployeeID = E.EmployeeID
LEFT JOIN ShiftSchedule SS ON SS.EmployeeID = E.EmployeeID AND #StartDate BETWEEN SS.EffectivityDate AND SS.EndDate
WHERE E.Status='1' AND E.ResignedDate IS NULL AND E.EmployeeID = #EmployeeID
SET #StartDate = DATEADD(day,1,#StartDate)
END
SET #StartDate = #InitialDate -- Reinitialize Start Date
FETCH NEXT FROM Employees INTO #EmployeeID, #BranchCode,#IsOfficer,#FirstName, #MiddleName, #LastName, #RankCode;
END;
CLOSE Employees;
DEALLOCATE Employees;
With this solution, if we are going to run the script. it took 3 minutes and sometimes 6minutes.
Might be the structure
Dates ('11/1/2015' - '11/30/2015')
-> Attendance
-> LeaveHistory
All dates from Dates table in date range will be filled with values from different table.
Don't use cursor.In fact it can be done without using RBAR/cursor.
You need to explain little more your table structure.Or start slowly with fewer column and table and ask where you are struck.
Calender Table is not require.What is the purpose of "ShiftSchedule" ?
You can do so by using recursive CTE.
Tell us more then we help you accordingly.
Recursive CTE sample.you can implemented your example here with fewer table and column then gradually increase.
DECLARE #StartDate DATETIME = '11/1/2015'
DECLARE #EndDate DATETIME = '11/30/2015';
WITH CTE
AS (
SELECT #StartDate DT
UNION ALL
SELECT DATEADD(day, 1, DT)
FROM CTE
WHERE DT < #EndDate
)
SELECT *
FROM CTE
I have a stored procedure, in which return a table containing date,presence,total working hour if present...
Total Working Hour is calculated in a separate function that return a decimal value, But sometimes employees have a mistakes in data, like a duplicate punches or punch (out or in) for twice,
if any of the above scenario exist the function returned an error, and therefore The SP return error...
My request is when any error is occurred, i want to return 0 or -1 or whatever instead of the error, to forces the SP to return data even if they are errors..
The Error generating every time is :
SQL Server Subquery returned more than 1 value.
This is not permitted when the subquery follows =, !=, <, <= , >, >=
So I want in every time when this error occurred, to return a value instead of it,
the SP Code is:
ALTER PROCEDURE [dbo].[SM_GetAttendance]
(
#StartDate date ,
#EndDate date ,
#EmployeeID NVARCHAR(6)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE dte_Cursor CURSOR FOR
WITH T(date)
AS
(
SELECT #StartDate
UNION ALL
SELECT DateAdd(day,1,T.date) FROM T WHERE T.date < #EndDate
)
SELECT date FROM T OPTION (MAXRECURSION 32767);
DECLARE #date NVARCHAR(20);
CREATE TABLE #datetable(date DATETIME,Status NVARCHAR(50),nbOfWorkingHour DECIMAL(36,2))
--SELECT date FROM T
set #date = ''
OPEN dte_Cursor
FETCH NEXT FROM dte_Cursor INTO #date
WHILE ##FETCH_STATUS = 0
BEGIN
insert #datetable
SELECT
cast((select distinct Convert(Nvarchar(12),date,102) from Attendance where date = #date
and employeeid =#EmployeeID ) as nvarchar(30))
date
,CASE WHEN EXISTS (select 1 from Attendance
where employeeid=#EmployeeID and date = #date)
then 'Present'
else 'absent'
end Status
,dbo.GetWorkingHourPerDay(#date,#EmployeeID) as numberOFWorkingHour
FETCH NEXT FROM dte_Cursor INTO #date
END
CLOSE dte_Cursor;
DEALLOCATE dte_Cursor;
end
And The Part of the Function Code that get the error is:
SET #From = (SELECT Time from #Tbl where date = #Date AND (EmployeeID=#employeeID OR ISNULL( #employeeID, '') = '') and funckey = 'EMPIN')
set #to = (CASE WHEN EXISTS(SELECT Times from #Tbl where dates = #Date AND (EmployeeID=#employeeID OR ISNULL( #employeeID, '') = '') and funckey = 'EMPOUT' )
then (SELECT Time from #Tbl where date = #Date AND (EmployeeID=#employeeID OR ISNULL( #employeeID, '') = '') and funckey = 'EMPOUT' )
else (SELECT Top 1 Time from #Tbl where date = dateadd(day,1,#Date) AND (EmployeeID=#employeeID OR ISNULL( #employeeID, '') = '') and funckey = 'EMPOUT')
end)
return ROUND(CAST(DATEDIFF(#From,#to) AS decimal)/ 60,2)
the following piece of code that get the error :
(CASE WHEN EXISTS(SELECT Times from #Tbl where dates = #Date AND (EmployeeID=#employeeID OR ISNULL( #employeeID, '') = '') and funckey = 'EMPOUT' )
IN this example the employee have two out punches like the below :
EMPID Date Time Status
123 2015-10-22 06:54:42 AM OUT
123 2015-10-22 04:35:02 PM OUT
So how to how to handle this state?
You can just change
(CASE WHEN EXISTS(SELECT Times from #Tbl where dates = #Date AND (EmployeeID=#employeeID OR ISNULL( #employeeID, '') = '') and funckey = 'EMPOUT' )
To this
(CASE WHEN EXISTS(SELECT top 1 Times from #Tbl where dates = #Date AND (EmployeeID=#employeeID OR ISNULL( #employeeID, '') = '') and funckey = 'EMPOUT' )
I have the following stored procedure:
CREATE PROCEDURE [dbo].[vRpt_VolunteerPaymentsAll]
#startdate DATETIME = NULL ,
#enddate DATETIME = NULL ,
#user NVARCHAR(50)
AS /* This procedure generates Team Events that have been audtied */
DECLARE #sd DATETIME
DECLARE #ed DATETIME
/* Ensure that the start and end dates covert whole days */
SET #sd = CONVERT(VARCHAR(10), #startdate, 120) + ' 00:00:00'
SET #ed = CONVERT(VARCHAR(10), #enddate, 120) + ' 23:59:59'
DECLARE #id INT
INSERT INTO vVolunteerPaymentEvents
( StartDate ,
EndDate ,
Verifier ,
DatePaid ,
RecordsPaid ,
UnDone ,
Comments ,
Flags
)
VALUES ( #startdate ,
#enddate ,
/*Get the users initials*/
( SELECT dbo.vUsers.Initials
FROM dbo.vUsers
WHERE dbo.vUsers.UserID = #user
) ,
GETDATE() ,
/* Count how many records are going ot be affected*/
( SELECT COUNT(*)
FROM dbo.vVolunteerPayments
WHERE dbo.vVolunteerPayments.PaymentEventID = 0
AND ( dbo.vVolunteerPayments.DateCreated BETWEEN #sd
AND
#ed )
) ,
0 ,
'' ,
0
)
/*Set the id of the payment event id for the volunteer payments*/
SET #id = SCOPE_IDENTITY() ;
--get the totals for the payment based on the
--#id value just inserted above
UPDATE dbo.vVolunteerPayments
SET dbo.vVolunteerPayments.PaymentEventID = #id ,
dbo.vVolunteerPayments.DatePaid = GETDATE()
WHERE dbo.vVolunteerPayments.PaymentEventID = 0
AND dbo.vVolunteerPayments.DateCreated BETWEEN #sd
AND #ed
SELECT * FROM
(SELECT VOLSACCT.volunteerid ,
ISNULL(VOLS.Forename, '') + ' ' + ISNULL(VOLS.Surname, '') AS Name ,
VOLSACCT.SortCode ,
VOLSACCT.AccountName ,
VOLSACCT.AccountNumber ,
SUM(CASE [Type]
WHEN 1001
THEN CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--AND PMTS.PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Amount
END
ELSE 0
END) AS Reimbursements ,
SUM(CASE [Type]
WHEN 1002
THEN CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--AND PMTS.PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Amount
END
ELSE 0
END) AS MobilePhoneCharges ,
SUM(CASE [Type]
WHEN 1003
THEN CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--AND PMTS.PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Amount
END
ELSE 0
END) AS GPContributions ,
SUM(CASE [Type]
WHEN 12
THEN CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--AND PMTS.PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Amount
END
ELSE 0
END) AS GPExpenses ,
SUM(CASE [Type]
WHEN 137
THEN CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Amount
END
ELSE 0
END) AS Expenses ,
SUM(CASE WHEN PMTS.DateCreated BETWEEN #sd AND #ed
--AND PMTS.PaymentEventID = 0
--AND PMTS.VolunteerId = #volunteerid
THEN Mileage
ELSE 0
END) AS TotalMileageWeek ,
VOLSACCT.BACS,
PMTS.ChargeRate
FROM dbo.vVolunteerPayments PMTS
INNER JOIN dbo.vVolunteerAccounts VOLSACCT ON PMTS.VolunteerId = VOLSACCT.VolunteerID
INNER JOIN dbo.vVolunteers VOLS ON PMTS.VolunteerId = VOLS.VolunteerID
WHERE PMTS.PaymentEventID = #id
GROUP BY VOLSACCT.volunteerid ,
VOLSACCT.AccountName ,
VOLSACCT.AccountNumber ,
VOLSACCT.BACS ,
VOLSACCT.SortCode ,
VOLS.Forename ,
VOLS.Surname,
PMTS.ChargeRate) AS A
WHERE
GPContributions >0
OR GPExpenses > 0
OR MobilePhoneCharges > 0
OR Expenses > 0
OR Reimbursements > 0
OR Expenses > 0
OR TotalMileageWeek >0
ORDER BY Name
It basically inserts a summary record of affected transactions into one table (volunteerpaymentevent) , updates existing records of another table (volunteerpayments(datepaid and payment event id)) and then returns record sets of sumary values within a join
However I have found that if the join fails or there are no joins at all (and consequently nothing outputs) then the update part of the procedure still updates the records
Is there a way of checking the result of the inner join before updating the records in the volunteerpayments table
Cheers
You need to put your code inside a transaction:
ALTER PROCEDURE [dbo].[vRpt_VolunteerPaymentsAll]
#startdate DATETIME = NULL ,
#enddate DATETIME = NULL ,
#user NVARCHAR(50)
AS
begin transaction
your procedure code
commit transaction
See MSDN for more on transactions.