How to properly GROUP BY datetime columns in specific intervals? - sql-server

I am making a stored procedure in MSSQL and I want to group my results by the interval day, I am unsure if this is correct though:
(this is a portion)
INSERT #Results
(
[Day],
[Month],
[Year],
[Result]
)
SELECT
DATEPART(DD, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(MM, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(YY, DATEADD(MI, #GmtOffset, EventDate)),
Result = CASE WHEN #Metric = 'Take Rate' THEN NULL ELSE COUNT(*) END
FROM BundleEvent
WHERE BundleEventTypeId = CASE WHEN #Metric = 'Take Rate' THEN #TypeTakeId ELSE #BundleEventTypeId END
AND EventDate >= #StartTime AND EventDate <= #EndTime
GROUP BY
DATEPART(YY, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(MM, DATEADD(MI, #GmtOffset, EventDate)),
DATEPART(DD, DATEADD(MI, #GmtOffset, EventDate))
My fear is it is ultimately going to group the data by the date where the year or month do not matter. Does anyone know if I am doing this correctly? EventDate is the DateTime field that I want to do a GROUP BY on by interval of day.
Thanks
EDIT:
This is the correct way, a simple format exchange... sometimes I need more coffee.
INSERT #Results
(
[Date],
[Result]
)
SELECT
CAST(DATEADD(MI, #GmtOffset, BundleEvent.EventDate) AS DATE),
Result = CASE WHEN #Metric = 'Take Rate' THEN 0 ELSE COUNT(*) END
FROM dbo.BundleEvent WITH (NOLOCK)
JOIN dbo.BundleUser WITH (NOLOCK)
ON BundleEvent.BundleId = BundleUser.BundleId
JOIN dbo.Bundle WITH (NOLOCK)
ON BundleEvent.BundleId = Bundle.BundleId
WHERE BundleEvent.EventDate >= #StartTimeGmt AND BundleEvent.EventDate <= #EndTimeGmt
AND BundleEvent.BundleEventTypeId = CASE WHEN #Metric = 'Take Rate' THEN #TypeTakeId ELSE #BundleEventTypeId END
AND BundleUser.UserId = CASE WHEN #UserId IS NULL THEN BundleUser.UserId ELSE #UserId END
AND Bundle.BundleType = 1
GROUP BY
CAST(DATEADD(MI, #GmtOffset, BundleEvent.EventDate) AS DATE)
Then I do a sub compare with the format exchange:
CAST(
(SELECT COUNT(*)
FROM dbo.BundleEvent WITH (NOLOCK)
JOIN dbo.BundleUser WITH (NOLOCK)
ON BundleEvent.BundleId = BundleUser.BundleId
JOIN dbo.Bundle WITH (NOLOCK)
ON BundleEvent.BundleId = Bundle.BundleId
WHERE CAST(DATEADD(MI, #GmtOffset, BundleEvent.EventDate) AS DATE) = [Date]
AND BundleEvent.BundleEventTypeId = #TypeTakeId
AND BundleUser.UserId = CASE WHEN #UserId IS NULL THEN BundleUser.UserId ELSE #UserId END
AND Bundle.BundleType = 1)
AS DECIMAL(5,2)
So in essence the query is matching up correctly and using a new format.

Since you are not using and aggregates (except Count(*), Are you sure you want a GROUP BY? Using DISTINCT will produce a single insert entry for all matching dates; perhaps something like:
DECLARE #DATE_GMT datetime
SET #DATE_GMT=DATEADD(MI, #GmtOffset, EventDate)
INSERT #Results
(
[Day],
[Month],
[Year],
[Result]
)
SELECT DISTINCT
DATEPART(DD, #DATE_GMT),
DATEPART(MM, #DATE_GMT),
DATEPART(YY, #DATE_GMT),
Result = CASE WHEN #Metric = 'Take Rate' THEN NULL ELSE COUNT(*) END
FROM BundleEvent
WHERE BundleEventTypeId = CASE WHEN #Metric = 'Take Rate' THEN #TypeTakeId ELSE #BundleEventTypeId END
AND EventDate Between #StartTime AND #EndTime

Related

TSQL-How to replace null values of a column with the output of a join query?

Below query gives expected null values in some records under column Variable2
select SUBSTRING([Variable2],11,10), (select
Case
When Variable1 IS NULL Then 'ANSWERED NO'
When Variable1 = 'Confirmed' Then 'CONFIRMED'
When Variable1 = 'Cancelled' Then 'CANCEL'
END) from t1
where DialedNumberString in (20062,20063,20065)
and DateTime between CAST(CAST(GETDATE() AS DATE) AS DATETIME) and getdate()
and Variable1 IN ('Confirmed','Cancelled'))
UNION ALL
(select SUBSTRING([AccountNumber],11,10),(select
Case
When CallResult = 2 Then 'END DIAL TIM'
When CallResult = 6 Then 'BUSY'
When CallResult = 8 Then 'NO ANSWER'
When CallResult = 9 Then 'HANG UP'
END As CallStatus ) from t2
where DateTime between CAST(CAST(GETDATE() AS DATE) AS DATETIME) and getdate()
and CampaignID in (5004,5011,5012)
and QueryRuleID in (5016,5020,5021)
Now I am looking for an output where the above null values can be replaced from the output of the below join query in order. Note: Variable2 in table 't1' is same as AccountNumber in table 't2' but Variable2 has nulls for some reason (probably App workflow is designed that way).
Select d.AccountNumber as Variable2
from t1 r
LEFT JOIN t2 d ON r.RouterCallKey=d.RouterCallKey
where r.Variable2 is null and r.DialedNumberString in (20062,20063,20065)
and d.CampaignID in (5004,5011,5012) and d.QueryRuleID in (5016,5020,5021)
and r.DateTime between CAST(CAST(GETDATE() AS DATE) AS DATETIME) and getdate()
and d.DateTime between CAST(CAST(GETDATE() AS DATE) AS DATETIME) and getdate()
Removed 'When Variable1 IS NULL Then 'ANSWERED NO'' and did a UNION ALL with the join query adding the last column as 'ANSWERED NO'.
select SUBSTRING([Variable2],11,10), (select
Case
When Variable1 = 'Confirmed' Then 'CONFIRMED'
When Variable1 = 'Cancelled' Then 'CANCEL'
END) from t1
where DialedNumberString in (20062,20063,20065)
and DateTime between CAST(CAST(GETDATE() AS DATE) AS DATETIME) and getdate()
and Variable1 IN ('Confirmed','Cancelled'))
UNION ALL
(select SUBSTRING([AccountNumber],11,10),(select
Case
When CallResult = 2 Then 'END DIAL TIM'
When CallResult = 6 Then 'BUSY'
When CallResult = 8 Then 'NO ANSWER'
When CallResult = 9 Then 'HANG UP'
END As CallStatus ) from t2
where DateTime between CAST(CAST(GETDATE() AS DATE) AS DATETIME) and getdate()
and CampaignID in (5004,5011,5012)
and QueryRuleID in (5016,5020,5021)
UNION ALL
Select SUBSTRING([AccountNumber],11,10),'ANSWERED NO'
from t1 r
LEFT JOIN t2 d ON r.RouterCallKey=d.RouterCallKey
where r.Variable2 is null and r.DialedNumberString in (20062,20063,20065)
and d.CampaignID in (5004,5011,5012) and d.QueryRuleID in (5016,5020,5021)
and r.DateTime between CAST(CAST(GETDATE() AS DATE) AS DATETIME) and getdate()
and d.DateTime between CAST(CAST(GETDATE() AS DATE) AS DATETIME) and getdate()

Subquery can return 1 column only

The purpose of this query would be to do a calculation based on if today's date is a weekday or a weekend and use it after with Union All, however I am getting
Msg 116 error:
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
It looks like that I can return only 1 column with this:
My query:
SELECT (SELECT CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday')
THEN
(SELECT --WeekEND
'X' AS Table_name
,CAST(MAX(date_1) as date) AS max_date
,DATEDIFF(DD,(CAST(MAX(date_1)as date)),GETDATE()) as NrOfDays
,CASE
WHEN DATEDIFF(DD,(CAST(MAX(date_1)as date)),GETDATE()) <= 3 THEN 'good'
ELSE 'bad'
END AS Status
FROM [Table_1])
ELSE
(SELECT --WeekDAY
'X' AS Table_name
,CAST(MAX(date_1) as date) AS max_date
,DATEDIFF(DD,(CAST(MAX(date_1)as date)),GETDATE()) as NrOfDays
,CASE
WHEN DATEDIFF(DD,(CAST(MAX(date_1)as date)),GETDATE()) <= 1 THEN 'good'
ELSE 'bad'
END AS Status
FROM [Table_1])
END
)
Same issue with the following just a simplified version (if I delete the 'good', 'bad' part it works):
SELECT (SELECT CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
(SELECT 'Weekend', 'good')
ELSE
(SELECT 'Weekday', 'bad')
END
)
You are trying to return two columns from your subquery, into a single column of your main query.
Instead, you need to make two (very similar) subqueries. One for each column in your main query.
SELECT(
SELECT(
CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
(SELECT 'Weekend')
ELSE
(SELECT 'Weekday')
END AS Column1,
CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
(SELECT 'good')
ELSE
(SELECT 'bad')
END AS Column2
)
)
There are other (better) ways to do this, depending upon your expected output. But this is probably the easiest/simplest to understand.
Also, not that it really matters but you have a lot of SELECTs in your query that you don't really need. The above can be simplified to:
SELECT
CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
'Weekend'
ELSE
'Weekday'
END AS Column1,
CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
'good'
ELSE
'bad'
END AS Column1
I don't know what your "Status" column is really doing, so I can't optimise it safely for you. But I think this should do what you want:
SELECT
'X' AS Table_name,
CAST(MAX(date_1) as date) AS max_date,
DATEDIFF(DD,(CAST(MAX(date_1)as date)), GETDATE()) as NrOfDays,
CASE WHEN DATENAME(weekday, GETDATE()) IN (N'Saturday', N'Sunday') THEN
CASE WHEN DATEDIFF(DD,(CAST(MAX(date_1)as date)), GETDATE()) <= 3 THEN
'good'
ELSE
'bad'
END
ELSE
CASE WHEN DATEDIFF(DD,(CAST(MAX(date_1)as date)), GETDATE()) <= 1 THEN
'good'
ELSE
'bad'
END
END AS Status
FROM [Table_1]

Using Max with CTE and CASE T-SQL

I am trying to use MAX() to select the most recent placement date within our database, and use Table_CTE so I can then select and filter between the dates desired.
BEGIN
DECLARE #Rangetill DATE, #Rangefrom DATE
SET #rangefrom = DATEADD(day, -50, GETDATE())
SET #Rangetill = DATEADD(day, -90, GETDATE());
WITH Table_CTE (Name, ID, Rangefrom, Rangetill, StatusID, Statusdate) AS
(
SELECT
PE.Personname + ' ' + PE.Surname [Name],
A.ApplicantId,
#rangefrom [Expiry warning from],
#rangetill [Expiry warning till],
A.Statusid,
selected = CASE
WHEN P.EndDate IS NOT NULL AND P.EndDate > A.StatusDate
THEN CONVERT(DATE, P.EndDate, 103)
ELSE CONVERT(DATE, A.StatusDate, 103)
END
FROM
Applicants AS A
LEFT JOIN
Person AS PE ON A.ApplicantId = PE.PersonID
LEFT JOIN
Placements AS P on A.applicantid = P.Applicantid
)
SELECT *
FROM Table_CTE
WHERE table_cte.Statusdate BETWEEN #Rangetill AND #Rangefrom
AND (Table_CTE.StatusID = 58 OR Table_CTE.statusid = 63)
ORDER BY Name DESC
END
The above selects the right information but also selects duplicate applicants with placement end dates (p.enddate) as they could have been placed more than once. The WHERE clause also limits the most recent enddate to within the range provided by the Variables and as there needs to be a log there will be multiple end dates. so my solution or idea would be to uses a max() within the Case or CTE Select. However I am not sure how to use or work with Max() in this case.
In this case I would like to check and return the Max(p.enddate) if it exists and store that in the statusdate of Table_CTE.
Is this possible and is it the best way to provide this information in a stored procedure?
In the CTE would be more efficient but this is easier
SELECT c.[Name], max(c.Statusdate)
FROM Table_CTE c
WHERE c.Statusdate Between #Rangetill and #Rangefrom
AND c.StatusID in (58, 63)
group by c.[Name]
Add the other columns on your own
Declare
#Rangetill date,
#Rangefrom date
SET #rangefrom = DATEADD(day, -50, GETDATE())
SET #Rangetill = DATEADD(day, -90, GETDATE());
With Table_CTE ( ID, Rangefrom, Rangetill, Statusdate)
AS (
Select
A.ApplicantId
, #rangefrom [Expiry warning from]
, #rangetill [Expiry warning till]
, selected = CASE
WHEN max(P.EndDate) IS NOT NULL AND max(P.EndDate) > max(A.StatusDate)
THEN max(CONVERT(DATE, P.EndDate, 103))
ELSE max(CONVERT(DATE, A.StatusDate, 103))
END
FROM Applicants AS A
LEFT JOIN Person AS PE ON A.ApplicantId = PE.PersonID
LEFT JOIN Placements AS P on A.applicantid = P.Applicantid
GROUP BY A.ApplicantId
)
SELECT
PE.PersonName + ' ' + PE.Surname [NAME]
, A.ApplicantId
, Table_CTE.ID
, Table_CTE.Statusdate
, #Rangefrom [Range from]
, #Rangetill [Range till]
FROM Table_CTE
LEFT JOIN Applicants AS A ON A.ApplicantId = Table_CTE.ID
LEFT JOIN Person as PE on PE.PersonID = A.ApplicantId
WHERE table_cte.Statusdate Between #Rangetill and #Rangefrom
AND (A.StatusID = 58 or A.statusid = 63 )
Order by PE.PersonName + ' '+ PE.Surname desc
END
Really messy way around things but I got my solution by removing all but the variables and ID from CTE so I could select Max(DATE) on both A.statusdate and P.EndDate.
By doing this I could group by A.ApplicantID and rejoin specific tables outside of the CTE in order to get Applicant Name and Status ID back into my results set.
Thank you for your help everyone.

How to change the query to give last 15 weeks of data instead of last 15 days from the SQL Server

The following query gives playing time of the users from the database on daily basis for the last 15 days. It adds 0 if no game is played. Now I want to get the data of playing time on weekly basis and 0 if no game is played in the whole week. So I want the query to give the last 15 weeks of data.
Here is the daily query.
CREATE PROCEDURE [dbo].[spGetPlayingTimeOfthepeoplesPerDay] #email NVARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MinDate DATE
,#MaxDate DATE
,#LastXDays INT
SELECT #LastXDays = - 15
SELECT #MaxDate = peoples.l_update
FROM peoples
WHERE peoples.email = #email
DECLARE #test TABLE (
quantity VARCHAR(100)
,DATE DATE
,TimePerDay DECIMAL(5, 2)
);
WITH CTE
AS (
SELECT peoples.email
,peoples.l_update
,act.quantity
,act.starttime
,act.endtime
,act.duration AS [Totaltime]
FROM peoples
INNER JOIN MPeoples ON peoples.Id = MPeoples.parent_id
INNER JOIN slines ON MPeoples.id = slines.movesuser_id
INNER JOIN seg ON slines.id = seg.sline_id
INNER JOIN act ON seg.id = act.seg_id
WHERE act.quantity = 'playing'
AND (peoples.email = #email)
GROUP BY peoples.email
,act.quantity
,act.duration
,act.starttime
,act.endtime
,peoples.l_update
)
INSERT INTO #test (
quantity
,DATE
,TimePerDay
)
SELECT quantity
,Cast(starttime AS DATE) AS DATE
,SUM(datediff(second, starttime, endtime)) / 60.0 AS TimePerDay
FROM cte WITH (NOLOCK)
WHERE starttime >= dateadd(day, #LastXDays, l_update)
GROUP BY quantity
,cast(starttime AS DATE)
SELECT #MaxDate = #MaxDate
,#MinDate = dateadd(day, (#LastXDays + 1), #MaxDate);
WITH AllDates
AS (
SELECT #MinDate AS xDate
UNION ALL
SELECT Dateadd(Day, 1, xDate)
FROM AllDates AS ad
WHERE ad.xDate < #MaxDate
)
SELECT 'playing' AS quantity
,ad.xDate
,Isnull(t.TimePerDay, 0) AS TimePerDay
FROM AllDates AS ad WITH (NOLOCK)
LEFT JOIN #test AS t ON ad.xDate = t.DATE
END
Change the DATEADD from day to week. Hence, two changes:
dateadd(week, #LastXDays, l_update)
and
dateadd(week, (#LastXDays + 1), #MaxDate)
In this case, I would also rename the #LastXDays variable to #LastXWeeks.
CREATE PROCEDURE [dbo].[spGetPlayingTimeOfthepeoplesPerDay] #email NVARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MinDate DATE
,#MaxDate DATE
,#LastXDays INT
SELECT #LastXWeeks = - 15
SELECT #MaxDate = peoples.l_update
FROM peoples
WHERE peoples.email = #email
DECLARE #test TABLE (
quantity VARCHAR(100)
,DATE DATE
,TimePerDay DECIMAL(5, 2)
);
WITH CTE
AS (
SELECT peoples.email
,peoples.l_update
,act.quantity
,act.starttime
,act.endtime
,act.duration AS [Totaltime]
FROM peoples
INNER JOIN MPeoples ON peoples.Id = MPeoples.parent_id
INNER JOIN slines ON MPeoples.id = slines.movesuser_id
INNER JOIN seg ON slines.id = seg.sline_id
INNER JOIN act ON seg.id = act.seg_id
WHERE act.quantity = 'playing'
AND (peoples.email = #email)
GROUP BY peoples.email
,act.quantity
,act.duration
,act.starttime
,act.endtime
,peoples.l_update
)
INSERT INTO #test (
quantity
,DATE
,TimePerDay
)
SELECT quantity
,Cast(starttime AS DATE) AS DATE
,SUM(datediff(second, starttime, endtime)) / 60.0 AS TimePerDay
FROM cte WITH (NOLOCK)
WHERE starttime >= dateadd(week, #LastXWeeks, l_update)
GROUP BY quantity
,cast(starttime AS DATE)
SELECT #MaxDate = #MaxDate
,#MinDate = dateadd(week, (#LastXWeeks + 1), #MaxDate);
WITH AllDates
AS (
SELECT #MinDate AS xDate
UNION ALL
SELECT Dateadd(Day, 7, xDate)
FROM AllDates AS ad
WHERE ad.xDate < #MaxDate
)
SELECT 'playing' AS quantity
,ad.xDate
,Isnull(t.TimePerDay, 0) AS TimePerDay
FROM AllDates AS ad WITH (NOLOCK)
LEFT JOIN #test AS t ON ad.xDate = t.DATE
END
Also, a piece of advice: don't use query hints (NOLOCK) if you do not understand their use. In this case, using NOLOCK can have disastrous effects on your results.
Here are a few articles which you should read before deciding if you are going to keep using NOLOCK or not.
Understanding the SQL Server NOLOCK hint
Bad habits : Putting NOLOCK everywhere

SQL Server 2008 SSRS Total/Combine values from two tables to output into a dataset

I have a SSRS report that needs to switch between three different datasets i.e. order types = Consumable, Service and Total.
I have two queries one for Consumable and one for Service as shown below. I tried putting a union between them but it doesn't seem to be totalling the results i.e. adding the two together. How can I do this?
SELECT COUNT(orderheader.orderid) AS [Consumable Order Amount],
CONVERT(DATE, orderheader.datecreated) AS [Date],
CASE
WHEN orderheader.webref = '' THEN 'Call Centre'
ELSE 'Web'
END AS [Order Type]
FROM orderheader
WHERE CONVERT(DATE, orderheader.datecreated) >= '21 February 2011'
AND CONVERT(DATE, orderheader.datecreated) <= '20 March 2011'
GROUP BY CONVERT(DATE, orderheader.datecreated),
CASE
WHEN orderheader.webref = '' THEN 'Call Centre'
ELSE 'Web'
END
SELECT COUNT(serviceid) AS [Service Order Amount],
CONVERT(DATE, datecreated) AS [Date],
CASE
WHEN serviceorder.webref = '' THEN 'Call Centre'
ELSE 'Web'
END AS SOURCE
FROM serviceorder
WHERE ( CONVERT(DATE, datecreated) >= '21 February 2011' )
AND ( CONVERT(DATE, datecreated) <= '20 March 2011' )
GROUP BY CONVERT(DATE, datecreated),
CASE
WHEN serviceorder.webref = '' THEN 'Call Centre'
ELSE 'Web'
END
ORDER BY [Date]
Can you try something like this for the combined dataset?
;WITH Combined AS
(
SELECT orderid AS id,
datecreated as [datecreated],
webref as [webref]
FROM orderheader
UNION ALL
SELECT serviceid AS id,
datecreated as [datecreated],
webref as [webref]
FROM serviceorder
)
SELECT COUNT(id) AS [Service Order Amount],
CONVERT(DATE, datecreated) AS [Date],
CASE
WHEN webref = '' THEN 'Call Centre'
ELSE 'Web'
END AS SOURCE
FROM Combined
WHERE ( CONVERT(DATE, datecreated) >= '21 February 2011' )
AND ( CONVERT(DATE, datecreated) <= '20 March 2011' )
GROUP BY CONVERT(DATE, datecreated),
CASE
WHEN webref = '' THEN 'Call Centre'
ELSE 'Web'
END
ORDER BY [Date]

Resources