How to get previous 7 days' data from today in SQL Server - sql

I have a DataEntry Table called GuestAddressData(UserId INT, EDate DateTime) with users data. I need to fetch the count of users for today to previous 7 Days. My Query:
SELECT
row_number() over (order by (SELECT 1)) ID,
count(*) Total,
LEFT(Datename(weekday, Cast(EDate as date)), 3) Day
FROM
CRM0001GuestAddressData
WHERE
EDate >= dateadd(week, datediff(d, -1, getdate()-2)/7, -1)
GROUP BY
Cast(EDate as date)
ORDER BY
Cast(EDate as date)
For example if today is Friday then my expected output is:
ID | TOTAL | DAY
------------------------
1 | 78 | Sat
2 | 23 | Sun
3 | 54 | Mon
4 | 17 | Tues
5 | 56 | Wed
6 | 45 | Thus
7 | 78 | Fri - Today
but this is not correct. How to solve it?

You can "generate" a list of seven numbers and use it to build the desired dates. Then left join with your data to get the counts, including zeros:
WITH datelist(num, a, b) AS (
SELECT num, DATEADD(DAY, -num, CAST(CURRENT_TIMESTAMP AS DATE)), DATEADD(DAY, -num + 1, CAST(CURRENT_TIMESTAMP AS DATE))
FROM (VALUES (0), (1), (2), (3), (4), (5), (6)) AS v(num)
)
SELECT 7 - num AS ID, datelist.a AS Day, COUNT(IDBooking)
FROM datelist
LEFT JOIN T_Bookings ON Opened >= datelist.a AND Opened < datelist.b
GROUP BY datelist.a, datelist.num
ORDER BY datelist.a

SELECT
row_number() over (order by dDate) ID,
cnt,
LEFT(Datename(weekday, dDate), 3) Day
from
(Select cast(EDate as Date) as dDate,
count(*) as cnt
FROM (values (0),(1),(2),(3),(4),(5),(6)) t(v)
inner join
CRM0001GuestAddressData gd on datediff(d, gd.Edate, getdate()) = t.v
WHERE
EDate >= dateadd(d, -6, cast(getdate() as date)) and EDate < dateadd(d,1,cast(getdate() as date))
GROUP BY
Cast(EDate as date)) tmp;
Note: You meant to get 7 days from yesterday, right? Nevermind, corrected based on your sample.
DBFiddle demo
EDIT: Having all days:
SELECT
row_number() over (order by dDate) ID,
cnt,
LEFT(Datename(weekday, dDate), 3) Day
from
(Select dateadd(d,-v,cast(getdate() as date)) as dDate,
count(Edate) as cnt
FROM (values (0),(1),(2),(3),(4),(5),(6)) t(v)
left join
CRM0001GuestAddressData gd on Datediff(d,gd.EDate, getdate()) = t.v
GROUP BY
dateadd(d,-v,cast(getdate() as date))) tmp;
DBFiddle Demo

Related

Keep last n business days records from today date in SQL Server

How can we keep last n business days records from today date in this table:
Suppose n = 7
Sample Data:
Table1:
Date
----------
2021-11-29
2021-11-30
2021-12-01
2021-12-02
2021-12-03
2021-12-04
2021-12-05
2021-12-06
2021-12-07
2021-12-08
2021-12-09
2021-12-10
2021-12-11
2021-12-12
2021-12-13
Based on this table data we want output like below. It should delete all the rows before the 03-Dec or data for last 7 business days.
Date
-------
2021-12-03
2021-12-06
2021-12-07
2021-12-08
2021-12-09
2021-12-10
2021-12-13
Note: It's fine if we keep data for Saturday, Sunday in between business days.
I tried this query
DECLARE #n INT = 7
SELECT * FROM Table1
WHERE [date] < Dateadd(day, -((#n + (#n / 5) * 2)), Getdate())
but Saturday, Sunday logic doesn't fit here with my logic. Please suggest better approach.
You can get the 7th working day from today as
select top(1) cast(dateadd(d, -n + 1, getdate()) as date) d
from (
select n
, sum (case when datename(dw, dateadd(d, -n + 1, getdate())) not in ('Sunday', 'Saturday') then 1 end) over(order by n) wdn
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)
)t0(n)
) t
where wdn = 7
order by n;
Generally using on-the-fly tally for a #n -th day
declare #n int = 24;
with t0(n) as (
select n
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
) t(n)
), tally as (
select top(#n + (#n/5 +1)*2) row_number() over(order by t1.n) n
from t0 t1, t0 t2, t0 t3
)
select top(1) cast(dateadd(d, -n + 1, getdate()) as date) d
from (
select n
, sum (case when datename(dw, dateadd(d, -n + 1, getdate())) not in ('Sunday', 'Saturday') then 1 end) over(order by n) wdn
from tally
) t
where wdn = #n
order by n;
You can use CTE to mark target dates and then delete all the others from the table as follows:
; With CTE As (
Select [Date], Row_number() Over (Order by [Date] Desc) As Num
From tbl
Where DATEPART(weekday, [Date]) Not In (6,7)
)
Delete From tbl
Where [Date] Not In (Select [Date] From CTE Where Num<=7)
If the number of business days in the table may be less than 7 and you need to bring the total number of days to 7 by adding days off, try this:
Declare #n Int = 7
; With CTE As (
Select [Date], IIF(DATEPART(weekday, [Date]) In (6,7), 0, 1) As IsBusinessDay
From tbl
)
Delete From tbl
Where [Date] Not In (Select Top(#n) [Date] From CTE Order By IsBusinessDay Desc, [Date] Desc)
If there is only one date for each day, you can simply do this:
SELECT TOP 7 [Date] FROM Table1
WHERE
[Date] < GETDATE() AND DATENAME(weekday, [DATE]) NOT IN ('Saturday', 'Sunday')
ORDER BY
[DATE] DESC

Get Last and Next Appointments

I have the following table in SQL Server and would like to get the last and next appointments for each customer.
Note: If the first appointment is in the future, last appointment should be N/A. Similarly if the last appointment is in the past, next appointment will be N/A. If the last appointment is older than 30 days it should not be shown (if there is no future appointment - considered an inactive customer).
CustomerId (int) | Date (date) | Time (time)
1 | 20210801 | 11:00
1 | 20210802 | 13:00
1 | 20210805 | 10:00
1 | 20210811 | 16:00
1 | 20210821 | 17:00
2 | 20210801 | 11:00
2 | 20210802 | 11:00
2 | 20210803 | 11:00
2 | 20210804 | 11:00
3 | 20210831 | 11:00
4 | 20210526 | 10:00
In this case the result should be (Assuming the date is today 7 August 2021):
CustomerId (int) | LastAppointment (varchar) | NextAppointment (varchar)
1 | 05 Aug 2021 - 10:00 | 11 Aug 2021 - 16:00
2 | 04 Aug 2021 - 11:00 | N/A
3 | N/A | 31 Aug 2021 - 11:00
Can anyone help me please? An example would be appreciated.
You simply need to work with datetime values and then use conditional aggregation to select the required date for each customer. Using a CTE first to simplify converting the dates as much as possible, this looks like:
with ap as (
select CustomerId, Convert(datetime,Left(Concat([date], ' ', [time]),15)) app
from t
), groups as (
select CustomerId,
Max(case when app <= GetDate() then app end) LastAppointment,
Min(case when app > GetDate() then app end) NextAppointment
from ap
group by customerId
)
select CustomerID,
IsNull(Format(LastAppointment, 'dd MMM yyyy - hh:mm'), 'N/A') LastAppointment,
IsNull(Format(NextAppointment, 'dd MMM yyyy - hh:mm'), 'N/A') NextAppointment
from groups
where DateAdd(day,-30,GetDate()) < isnull(lastappointment,GetDate())
see DB<>Fiddle
Also note this query only touches the table once and performs a single logical read.
You need conditional aggregation:
SELECT CustomerId,
COALESCE(
MAX(CASE
WHEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME) < GETDATE()
THEN FORMAT(CAST(Date AS DATETIME) + CAST(Time AS DATETIME), 'dd MMM yyyy - HH:mm')
END
), 'N/A'
) LastAppointment,
COALESCE(
MIN(CASE
WHEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME) > GETDATE()
THEN FORMAT(CAST(Date AS DATETIME) + CAST(Time AS DATETIME), 'dd MMM yyyy - HH:mm')
END
), 'N/A'
) NextAppointment
FROM tablename
GROUP BY CustomerId
HAVING COALESCE(DATEDIFF(
d,
MAX(CASE
WHEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME) < GETDATE()
THEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME)
END
),
GETDATE()
), 0) < 30
See the demo.
Results:
CustomerId
LastAppointment
NextAppointment
1
05 Aug 2021 - 10:00
11 Aug 2021 - 16:00
2
04 Aug 2021 - 11:00
N/A
3
N/A
31 Aug 2021 - 11:00
NOTE : This solution works but it is very bad in terms of performance, check this answer for a better approach
Something like this
SELECT DISTINCT customerid,
Isnull(CONVERT(VARCHAR,
(SELECT TOP 1 Concat(date, ' ', TIME)
FROM appointments B
WHERE b.customerid = a.customerid
AND ([date] < CONVERT(DATE, Getdate())
OR ([date] = CONVERT(DATE, Getdate())
AND [time] <= CONVERT(TIME, Getdate())))
ORDER BY [date] DESC)), 'N/A') AS lastappointment,
Isnull(CONVERT(VARCHAR,
(SELECT TOP 1 Concat(date, ' ', TIME)
FROM appointments B
WHERE b.customerid = a.customerid
AND ([date] > CONVERT(DATE, Getdate())
OR ([date] = CONVERT(DATE, Getdate())
AND [time] > CONVERT (TIME, Getdate())))
ORDER BY [date])), 'N/A') AS nextappointment
FROM appointments A
WHERE Datediff(DAY,
(SELECT TOP 1 date
FROM appointments B
WHERE b.customerid = a.customerid
AND [date] <= CONVERT(DATE, Getdate())
ORDER BY [date] DESC), CONVERT(DATE, Getdate())) <= 30
OR (((
(SELECT TOP 1 date
FROM appointments B
WHERE b.customerid = a.customerid
AND [date] > CONVERT(DATE, Getdate())
ORDER BY [date]) > CONVERT(DATE, Getdate())))
OR ((
(SELECT TOP 1 date
FROM appointments B
WHERE b.customerid = a.customerid
AND [date] > CONVERT(DATE, Getdate())
ORDER BY [date]) = CONVERT(DATE, Getdate()))
AND (
(SELECT TOP 1 [time]
FROM appointments B
WHERE b.customerid = a.customerid
AND [date] > CONVERT(DATE, Getdate())
ORDER BY [date]) > CONVERT(TIME, Getdate()))))
I called your table appointments and the condition is to select customer with last appointment in the past 30 days OR with a future appointment.
I tested with column types Date for Date and Time(7) for time.
Base table is used only single time because of optimization purpose. Use LAG() function and others necessary condition for picking actual set of data.
-- SQL SERVER
SELECT p.CustomerId
, CASE WHEN p.chk_condition = 1
THEN CONVERT(varchar(13), p.prev_Date, 113) + ' - ' + LEFT(p.prev_time, 5)
WHEN p.chk_condition = 2
THEN CONVERT(varchar(13), p.Date, 113) + ' - ' + LEFT(p.time, 5)
ELSE 'N/A'
END "LastAppointment"
, CASE WHEN p.chk_condition != 2
THEN CONVERT(varchar(13), p.Date, 113) + ' - ' + LEFT(p.time, 5)
ELSE 'N/A'
END "NextAppointment"
FROM ( SELECT t.*
, CASE WHEN t.prev_Date < GETDATE() AND t.Date >= GETDATE()
THEN 1
WHEN t.prev_Date < GETDATE() AND t.Date <= GETDATE()
THEN 2
ELSE 0
END chk_condition
, ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY t.Date DESC, t.prev_Date DESC) row_num
FROM (SELECT CustomerId, Date, Time
, LAG(Date) OVER (PARTITION BY CustomerId ORDER BY "Date", "Time") "prev_Date"
, LAG(Time) OVER (PARTITION BY CustomerId ORDER BY "Date", "Time") "prev_Time"
FROM appointment) t
WHERE CASE WHEN t.prev_Date < GETDATE() AND t.Date >= GETDATE()
THEN 1
WHEN t.prev_Date IS NULL
THEN CASE WHEN DATEDIFF(day, t.Date, GETDATE()) >= 30
THEN 0
ELSE 1
END
WHEN t.prev_Date < GETDATE() AND t.Date <= GETDATE()
THEN 1
END = 1 ) p
WHERE p.row_num = 1
ORDER BY p.CustomerId;
Please check this url https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=3813d09cf25ed14d249970654995b085

Pivoting for DATEPART(wk, date)

I have a table like this,
mytable
date | value
2018.09.12 | 1
2018.09.11 | 2
2018.09.10 | 3
I need a query to return sum(value) for the last six weeks. Exactly like this.
week# | value
37 | 6
36 | 0
35 | 0
34 | 8
33 | 9
32 | 10
31 | 11
I have a query to return sumvalue for each week.
SELECT Sum(Value) AS Sumvalue, DATEPART(wk, date) AS [weekNo]
FROM mytable
WHERE Date BETWEEN DATEADD(DAY, -42, GETDATE()) AND GETDATE()
GROUP BY DATEPART(wk, date)
But this can't handle zero values for a week.
How can write a pivoted query to obtain the format?
My try;
SELECT *
FROM (
SELECT Value, DATEPART(wk, date) AS [weekNo]
FROM mytable
WHERE Date BETWEEN DATEADD(DAY, -42, GETDATE()) AND GETDATE()
) As sourcetable
PIVOT
(
Sum(Value) for DATEPART(wk, date) IN (SELECT date FROM mytable where date between
DATEADD(DAY, -42, GETDATE()) and GETDATE())
) AS pivotable
I am getting the syntax error near for keyword. How can I put the six weeks in pivot statemet
Found a partial solution!
SELECT *
FROM (
SELECT Value, DATEPART(wk, date) AS [weekNo]
FROM mytable
WHERE Date BETWEEN DATEADD(DAY, -42, GETDATE()) AND GETDATE()
) As sourcetable
PIVOT
(
Sum(Value) for [week] IN ([1], [2], [3], ... ,[54])
) AS pivotable
It is partial since it is kind of hardcoding the for statement and cannot control the unnecessary weeks in the query.

MSSQL Subtract two rows from each other in union

So I have a successful query that shows the first row of last month, and the first row of this month. This query shows fields that have ongoing counters for electricity usage.
I need the difference of a particular field, and then multiply that by .54802 and round to nearest hundredth.
SELECT *
FROM
(
SELECT TOP (1) *
FROM Datebase
WHERE DATEPART(M, Timestamp) = DATEPART(M, DATEADD(M, -1, GETDATE()))
AND DATEPART(Yyyy, Timestamp) = DATEPART(Yyyy, DATEADD(M, 0, GETDATE()))
ORDER BY Timestamp ASC
) AS A
UNION
SELECT *
FROM
(
SELECT TOP (1) *
FROM Datebase
WHERE DATEPART (M, Timestamp) = DATEPART(M, DATEADD(M, 0, GETDATE()))
AND DATEPART(Yyyy, Timestamp) = DATEPART(YYYY, DATEADD(M, 0, GETDATE()))
ORDER BY Timestamp ASC
) AS B;
This outputs:
| ID | Timestamp | 7000AV119 |
---------------------------------------------------
| 1 | 2018-08-01 00:00:03.000 | 3675.59 |
| 2 | 2018-09-01 00:00:03.000 | 3750.93 |
I need it to show:
| ID | Timestamp | 7000AV119 | Difference
-----------------------------------------------------------
| 1 | 2018-08-01 00:00:03.000 | 3675.59 | 0
| 2 | 2018-09-01 00:00:03.000 | 3750.93 | 41.29
I would get the two records as:
select t.*
from (select t.*,
row_number() over (partition by year(timestamp), month(timestamp) order by timestamp asc) as seqnum
from Datebase t
where timestamp >= dateadd(day, 1, eomonth(getdate(), -2))
) t
where seqnum = 1;
Then I would use lag():
select t.*,
round(([7000AV119] - lag([7000AV119]) over (order by timestamp)
) * 0.54802,
2
)
from (select t.*,
row_number() over (partition by year(timestamp), month(timestamp) order by timestamp asc) as seqnum
from Datebase t
where timestamp >= dateadd(day, 1, eomonth(getdate(), -2))
) t
where seqnum = 1;
This returns NULL rather than 0 for the first row. That makes more sense to me.
Try with lag function
with testcte as
(
select *
from (select top(1) * from DATABASE
WHERE DATEPART(m, Timestamp) = DATEPART(m, DATEADD(m, -1, getdate()))
AND DATEPART(yyyy, Timestamp) = DATEPART(yyyy, DATEADD(m, 0, getdate()))
order by Timestamp ASC) a
union
select *
from (select top(1) * from DATABASE
WHERE DATEPART(m, Timestamp) = DATEPART(m, DATEADD(m, 0, getdate()))
AND DATEPART(yyyy, Timestamp) = DATEPART(yyyy, DATEADD(m, 0, getdate()))
order by Timestamp ASC) b
)
select id,Timestamp,7000AV119,7000AV119- LAG(7000AV119,1,0) OVER (ORDER BY id) AS Diff
from testcte

SQL Server Group by date and by time of day over a date range

I'm not even sure if this can/should be done is SQL but here goes.
I have a table that stores a start date and an end date like so
userPingId createdAt lastUpdatedAt
1 2017-10-17 11:31:52.160 2017-10-18 14:31:52.160
I want to return a result set that groups the results by date and if they were active between different points between the two date.
The different points are
Morning - Before 12pm
Afternoon - Between 12pm and 5pm
Evening - After 5pm
So for example I would get the following results
sessionDate morning afternoon evening
2017-10-17 1 1 1
2017-10-18 1 1 0
Here is what I have so far and I believe that it's quite close but the fact I can't get the results I need make me think that this might not be possible in SQL (btw i'm using a numbers lookup table in my query which I saw on another tutorial)
DECLARE #s DATE = '2017-01-01', #e DATE = '2018-01-01';
;WITH d(sessionDate) AS
(
SELECT TOP (DATEDIFF(DAY, #s, #e) + 1) DATEADD(DAY, n-1, #s)
FROM dbo.Numbers ORDER BY n
)
SELECT
d.sessionDate,
sum(case when
(CONVERT(DATE, createdAt) = d.sessionDate AND datepart(hour, createdAt) < 12)
OR (CONVERT(DATE, lastUpdatedAt) = d.sessionDate AND datepart(hour, lastUpdatedAt) < 12)
then 1 else 0 end) as Morning,
sum(case when
(datepart(hour, createdAt) >= 12 and datepart(hour, createdAt) < 17)
OR (datepart(hour, lastUpdatedAt) >= 12 and datepart(hour, lastUpdatedAt) < 17)
OR (datepart(hour, createdAt) < 12 and datepart(hour, lastUpdatedAt) >= 17)
then 1 else 0 end) as Afternoon,
sum(case when datepart(hour, createdAt) >= 17 OR datepart(hour, lastUpdatedAt) >= 17 then 1 else 0 end) as Evening
FROM d
LEFT OUTER JOIN MYTABLE AS s
ON s.createdAt >= #s AND s.lastUpdatedAt <= #e
AND (CONVERT(DATE, s.createdAt) = d.sessionDate OR CONVERT(DATE, s.lastUpdatedAt) = d.sessionDate)
WHERE d.sessionDate >= #s AND d.sessionDate <= #e
AND userPingId = 49
GROUP BY d.sessionDate
ORDER BY d.sessionDate;
Building on what you started with the numbers table, you can add the time ranges to your adhoc calendar table using another common table expression using cross apply()
and the table value constructor (values (...),(...)).
From there, you can use an inner join based on overlapping date ranges along with conditional aggregation to pivot the results:
declare #s datetime = '2017-01-01', #e datetime = '2018-01-01';
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, d as ( /* adhoc date/numbers table */
select top (datediff(day, #s, #e)+1)
SessionDate=convert(datetime,dateadd(day,row_number() over(order by (select 1))-1,#s))
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by SessionDate
)
, h as ( /* add time ranges to date table */
select
SessionDate
, StartDateTime = dateadd(hour,v.s,SessionDate)
, EndDateTime = dateadd(hour,v.e,SessionDate)
, v.point
from d
cross apply (values
(0,12,'morning')
,(12,17,'afternoon')
,(17,24,'evening')
) v (s,e,point)
)
select
t.userPingId
, h.SessionDate
, morning = count(case when point = 'morning' then 1 end)
, afternoon = count(case when point = 'afternoon' then 1 end)
, evening = count(case when point = 'evening' then 1 end)
from t
inner join h
on t.lastupdatedat >= h.startdatetime
and h.enddatetime > t.createdat
group by t.userPingId, h.SessionDate
rextester demo: http://rextester.com/MVB77123
returns:
+------------+-------------+---------+-----------+---------+
| userPingId | SessionDate | morning | afternoon | evening |
+------------+-------------+---------+-----------+---------+
| 1 | 2017-10-17 | 1 | 1 | 1 |
| 1 | 2017-10-18 | 1 | 1 | 0 |
+------------+-------------+---------+-----------+---------+
Alternately, you could use pivot() instead of conditional aggregation in the final select:
select UserPingId, SessionDate, Morning, Afternoon, Evening
from (
select
t.userPingId
, h.SessionDate
, h.point
from t
inner join h
on t.lastupdatedat >= h.startdatetime
and h.enddatetime > t.createdat
) t
pivot (count(point) for point in ([Morning], [Afternoon], [Evening])) p
rextester demo: http://rextester.com/SKLRG63092
You can using PIVOT on CTE's to derive solution to this problem.
Below is the test table
select * from ping
Below is the sql query
;with details as
(
select userPingId, createdAt as presenceDate , convert(date, createdAt) as
onlyDate,
datepart(hour, createdAt) as onlyHour
from ping
union all
select userPingId, lastUpdatedAt as presenceDate , convert(date,
lastUpdatedAt) as onlyDate,
datepart(hour, lastUpdatedAt) as onlyHour
from ping
)
, cte as
(
select onlyDate,count(*) as count,
case
when onlyHour between 0 and 12 then 'morning'
when onlyHour between 12 and 17 then 'afternoon'
when onlyHour>17 then 'evening'
end as 'period'
from details
group by onlyDate,onlyHour
)
select onlyDate, coalesce(morning,0) as morning,
coalesce(afternoon,0) as afternoon , coalesce(evening,0) as evening from
(
select onlyDate, count,period
from cte ) src
pivot
(
sum(count)
for period in ([morning],[afternoon],[evening])
) p
Below is the final result
This is a fairly similar answer to the one already posted, I just wanted the practice with PIVOT :)
I use a separate table with the time sections in it. this is then cross joined with the number table to create a date and time range for bucketing. i join this to the data and then pivot it (example: https://data.stackexchange.com/stackoverflow/query/750496/bucketing-data-into-date-am-pm-evening-and-pivoting-results)
SELECT
*
FROM (
SELECT
[userPingId],
dt,
[desc]
FROM (
SELECT
DATEADD(D, number, #s) AS dt,
CAST(DATEADD(D, number, #s) AS datetime) + CAST(s AS datetime) AS s,
CAST(DATEADD(D, number, #s) AS datetime) + CAST(e AS datetime) AS e,
[desc]
FROM #numbers
CROSS JOIN #times
WHERE number < DATEDIFF(D, #s, #e)
) ts
INNER JOIN #mytable AS m
ON m.createdat < ts.e
AND m.[lastUpdatedAt] >= ts.s
) src
PIVOT
(
COUNT([userPingId])
FOR [desc] IN ([am], [pm], [ev])
) piv;
the #times table is just:
s e desc
00:00:00.0000000 12:00:00.0000000 am
12:00:00.0000000 17:00:00.0000000 pm
17:00:00.0000000 23:59:59.0000000 ev