TSQL Accumulate rows every monday with distinct rows - sql-server

I'm trying to get a query that returns customers I've attended per day, and i have this dataset:
fecha RecargadorPDV
2016/12/19 1
2016/12/19 2
2016/12/19 3
2016/12/20 1
2016/12/20 4
2016/12/20 5
2016/12/21 2
2016/12/21 6
2016/12/21 7
2016/12/21 8
..
...
2016/12/26 1
2016/12/26 2
2016/12/26 1
2016/12/27 2
2016/12/27 6
2016/12/27 7
2016/12/27 8
but the output I'd want to have is this:
date attended acum_customers
2016/12/19 3 3 -- Every monday it restart
2016/12/20 3 5
2016/12/21 4 8
.
..
2016/12/26 3 3 -- Every monday it restart
2016/12/27 4 3
.
..
As you can see, every monday it restart the values and if some customers are in a date and in the next day are present it needs to be ignored.

Here is a version that returns what you need for any arbitrary date. I have included sample data for a full week + two days to confirm its functionality.
DECLARE #t table (fecha date,
RecargadorPDV int
)
INSERT INTO #t VALUES
('2016/12/19', 1),
('2016/12/19', 2),
('2016/12/19', 3),
('2016/12/20', 1),
('2016/12/20', 4),
('2016/12/20', 5),
('2016/12/21', 2),
('2016/12/21', 6),
('2016/12/21', 7),
('2016/12/21', 8),
('20161222', 12),
('20161222', 1),
('20161222', 8),
('20161223', 11),
('20161223', 13),
('20161223', 15),
('20161223', 9),
('20161224', 1),
('20161225', 22),
('2016/12/26', 1),
('2016/12/26', 2),
('2016/12/26', 1),
('2016/12/27', 2),
('2016/12/27', 6),
('2016/12/27', 7),
('2016/12/27', 8)
;
With a as (
SELECT DISTINCT
fecha,
Dateadd(day, -(
case
when datepart(weekday, fecha) >=2
THEN datepart(weekday, fecha) - 2
ELSE 6
END), fecha) as LastMonday
FROM #t
)
SELECT
a.fecha as [date],
-- count(Distinct(CASE when t.fecha = a.fecha Then t.recargadorPDV else -1 END)) - 1 as attended,
SUM(CASE when t.fecha = a.fecha Then 1 else 0 END) as attended,
Count(distinct recargadorPDV) as acum_customers
FROM #t t
INNER JOIN a
ON t.fecha BETWEEN a.LastMonday and a.fecha
Group by a.fecha
ORDER BY a.fecha
Output of the above (as corrected) is:
date attended acum_customers
2016-12-19 3 3
2016-12-20 3 5
2016-12-21 4 8
2016-12-22 3 9
2016-12-23 4 13
2016-12-24 1 13
2016-12-25 1 14
2016-12-26 3 2
2016-12-27 4 5

I think your second week acum_customers is off based off the test data so check on that. I also assumed that the recargadorPDVcould only attend once per day since it's unique so I removed the one record noted below. With that... this should get you what you want. Let me know if it needs more explanation.
--change the ##DATEFIRST from 7 (english default) to 1 for the start of the week calculations
SET DATEFIRST 1;
--load some test data
declare #table table (fetcha datetime, recargadorPDV int)
insert into #table(fetcha, recargadorPDV)
values
('2016/12/19',1),
('2016/12/19',2),
('2016/12/19',3),
('2016/12/20',1),
('2016/12/20',4),
('2016/12/20',5),
('2016/12/21',2),
('2016/12/21',6),
('2016/12/21',7),
('2016/12/21',8),
--this is the break in the weeks
('2016/12/26',1),
('2016/12/26',2),
--('2016/12/26',1), -- removed this value since a unique ID shouldn't be allowed to attend twice for a single day
('2016/12/27',2),
('2016/12/27',6),
('2016/12/27',7),
('2016/12/27',8)
--temp table to hold some aggregated data
if object_id('tempdb..#tempT') is not null drop table #tempT
select
y.YR
,y.WK
,y.fetcha
,count(y.recargadorPDV) as attend
,sum(y.CTforWK) as accum
into #tempT
from(
select x.*
from
(select
fetcha
,recargadorPDV
,datepart(yy,fetcha) as YR
,datepart(wk,fetcha) as WK
--the case statment is my way of assigning 1 to each recargadorPDV ONLY once for each week so the running total is correct, ignoring duplicates as you stated
,case when count(recargadorPDV) over (partition by datepart(yy,fetcha), datepart(wk,fetcha), recargadorPDV order by fetcha) = 1 then 1 else 0 end as CTforWK
from #table) x) y
group by
y.YR
,y.WK
,y.fetcha
--see the inital results without the running total
select * from #tempT
--see the final results with the running total
select
a.fetcha
,a.attend
,sum(x.accum) as acum_customers
from #tempT a
inner join #tempT x on x.fetcha <= a.fetcha and x.YR = a.YR and x.WK = a.WK
group by a.fetcha, a.attend
order by a.fetcha
--change back the ##DATEFIRST setting
SET DATEFIRST 7;

Related

Extract short date from a set of conditions in SQL Server

s_term
w_due
2
2
4
2
Given a table as shown above, I want to extract the corresponding date for the month of August/September (dd-mm-yyyy) as follows:
<s_term, w_due>
(2,2) means a service started on 2nd Monday of August and I need to return the date as the 4th Monday of August as indicated by w_due which indicates the stepsize from the starting point.
Similarly for (4, 2): I want to return the 2nd Monday in September as the service start date is on 4th Monday of August with a w_due of 2.
A Calendar Table would be well worth the effort.
Here is an option where we use an ad-hoc tally table to create an ad-hoc calendar table.
Example
Declare #BaseDate date = '2021-08-01'
;with cte as (
Select D
,WN = row_number() over (partition by month(d) order by D)
From ( Select Top 90 D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#BaseDate) From master..spt_values n1 ) D
Where datename(WEEKDAY,D)='Monday'
)
Select A.*
,Date1 = B.D
,Date2 = C.D
From YourTable A
Join cte B on s_term=B.WN and month(B.D)=month(#BaseDate)
Join cte C on w_due =C.WN and month(C.D)=month(dateadd(month,1,#BaseDate))
Results
s_term w_due Date1 Date2
2 2 2021-08-09 2021-09-13
4 2 2021-08-23 2021-09-13
DECLARE #referenceMonth DATE = '2021-08-01';
DECLARE #tbl TABLE (s_term int, w_due int);
INSERT INTO #tbl VALUES (2, 2), (4, 2);
SELECT a.first_monday, s_term, b.s_term_monday, w_due, c.w_due_monday
FROM #tbl
CROSS APPLY (
SELECT DATEADD(d, (9 - DATEPART(dw, #referenceMonth)) % 7, #referenceMonth) as first_monday
) a
CROSS APPLY
(
SELECT DATEADD(WEEK, s_term - 1, a.first_monday) as s_term_monday
) b
CROSS APPLY (
SELECT DATEADD(WEEK, w_due, b.s_term_monday) as w_due_monday
) c;
The first part (a) just gets the first monday of the reference month.
The second part (b) calculates the Nth monday of the reference month (s_term).
The last part (c) calculates de Nth monday from that s_term monday (w_due)
The result is as follows:
As you specified august/2021 as the reference date, I just hard-coded it as a parameter.
I also split the DATE opperations in multiple CROSS APPLY for ease of read, but you could nest the DATEADD inside one another right in the SELECT. Something like this:
SELECT s_term, w_due, DATEADD(WEEK, w_due,
DATEADD(WEEK, s_term - 1,
DATEADD(d, (9 - DATEPART(dw, #referenceMonth)) % 7, #referenceMonth)))
FROM #tbl
Edit: Explaning that misterious logic of DATEADD(d, (9 - DATEPART(dw, #referenceMonth)) % 7, #referenceMonth)
We take the weekday of the first day of that month: DATEPART(dw, #referenceMonth)
Sunday is 1, Monday is 2 and so on...
Now we find out how many days it would need to get from our reference week day to the desired week day (Monday). We do that with that formulae: (9 - X) % 7
That 9 is the number of days minus our WeekDay that would equal to 7 (the number of days in a week). 7 (days of the week) + 2 (monday) = 9.
If you where to find, say, the first Thursday of the week, that 9 would become 13. 7 (days of the week) + 5 (thursday) = 13.
When we take the modulus of 7 we are looking at the number of week days we are away from our desired week day. Let's run a few simulations:
refenceDate is a Monday (2): 9 - 2 % 7 = 0
nothing to do!
refenceDate is a Sunday (1): 9 - 1 % 7 = 1
we are 1 day away from the next monday
refenceDate is a Thursday (5): 9 - 5 % 7 = 4
we are 4 days away from the next monday
Now we finish by adding that number of days to our reference date: DATEADD(d, y, #referenceMonth)
With that, the first day of the month becomes the first Monday of said month.
maybe ?
CREATE TABLE #temptable(
s_term INTEGER
,w_due INTEGER
);
INSERT INTO #temptable(s_term,w_due) VALUES (2,2) ,(4,2);
WITH Mondays AS (
SELECT ROW_NUMBER () OVER (ORDER BY (SELECT NULL)) RN ,
DATEADD(DAY, n, M_fom) AS Mday
FROM (
SELECT fom, DATEADD(DAY, DATEDIFF(DAY, 0, fom) / 7 * 7, 0)
FROM ( VALUES ( DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)) ) d ( fom )
) e ( fom, M_fom )
CROSS JOIN ( VALUES ( 7), ( 14), ( 21), ( 28), ( 35),(42),(49),(56),(63) ) f ( n )
)
SELECT m1.Mday AS M1, m2.Mday AS M2
FROM #temptable AS t INNER JOIN
Mondays AS m1 ON t.s_term = m1.RN INNER JOIN
Mondays AS m2 ON t.s_term + t.w_due = m2.RN

T-SQL - 3 month moving sum - preceding null values

Using SQL Server 2016. I have the following data table (sample)
Target Date Total
-----------------
2018-01-24 1
2018-02-28 1
2018-03-02 1
2018-03-08 1
2018-03-15 1
2018-03-30 1
2018-04-16 1
2018-04-18 1
2018-04-30 1
I would like to get to get a 3 month moving sum (grouping is by month):
Target Date Total_Sum
-----------------------
2018-01-01 1
2018-02-01 2
2018-03-01 6
2018-04-01 8
Ok, this should get the answer you want. Firstly you need to total the value your months, then you can do a running total for the last 3 months:
CREATE TABLE SampleTable (TargetDate date, Total int);
GO
INSERT INTO SampleTable
VALUES ('20180124', 1),
('20180228', 1),
('20180302', 1),
('20180308', 1),
('20180315', 1),
('20180330', 1),
('20180416', 1),
('20180418', 1),
('20180430', 1);
GO
SELECT *
FROM SampleTable;
GO
WITH Months AS (
SELECT DATEADD(MONTH,DATEDIFF(MONTH, 0, TargetDate),0) AS TargetMonth, SUM(Total) AS MonthTotal
FROM SampleTable
GROUP BY DATEADD(MONTH,DATEDIFF(MONTH, 0, TargetDate),0))
SELECT TargetMonth,
SUM(MonthTotal) OVER (ORDER BY TargetMonth ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS Last3Months
FROM Months;
GO
DROP TABLE SampleTable;
GO
Pls try the below code
;WITH CTE(TargetDate,Total)
AS
(
SELECT '2018-01-24', 1 UNION ALL
SELECT '2018-02-28', 1 UNION ALL
SELECT '2018-03-02', 1 UNION ALL
SELECT '2018-03-08', 1 UNION ALL
SELECT '2018-03-15', 1 UNION ALL
SELECT '2018-03-30', 1 UNION ALL
SELECT '2018-04-16', 1 UNION ALL
SELECT '2018-04-18', 1 UNION ALL
SELECT '2018-04-30', 1
)
SELECT STUFF(TargetDate,9,2,'01') AS TargetDate
,Total_Sum
FROM
(
SELECT TargetDate,Total_Sum
,ROW_NUMBER()OVER(PARTITION BY Total_Sum ORDER BY TargetDate) AS Seq
FROM
(
SELECT TargetDate
,SUM(Total )OVER(ORDER BY MONTH(TargetDate) ) AS Total_Sum
FROM CTE
)dt
)fnl
WHERE Seq=1
Result
TargetDate Total_Sum
---------------------
2018-01-01 1
2018-02-01 2
2018-03-01 6
2018-04-01 9

Conditional scoring

I have a table that shows only the 'captured' data. For example in the below exhibit, the emp_no 17 has 2 records - for November and February (for a specified 6 month period, from July 2017). It does not have data for the other 4 months (within the 6-month date range, from previous 6 months to current date).
How can I populate these missing months (Sept, Oct, Dec) with default values for num_differences of 0 for the missing months? (for example, in this case, I want emp_no 17 to have the below (I can ignore 2018 data - only require data up to Dec 2017):
I have the script below:
declare #YMN date;
set #YMN = '20171201';
DECLARE #Emp TABLE (
[date] date,
[emp_no] int,
[num_differences] int
);
INSERT INTO #Emp VALUES
('2017-09-14', 17, 1), ('2017-12-01', 17, 1),('2017-12-18', 17, 1),('2017-12-21', 17, 1),
('2017-09-27', 17, 1), ('2017-12-04', 17, 1);
-------------------------------------------------------------------------------------------get missing dates---------------------------------------------------------------------------
;WITH cte_Emp_No AS (
SELECT DISTINCT [emp_no]
FROM #Emp
),
cte_dates AS (
SELECT [emp_no], DATEADD(month, -6, DATEADD(dd, -(DAY(dateadd(month, 1, #YMN)) - 1), dateadd(month, 1, #YMN))) AS [date]
FROM cte_Emp_No
UNION ALL
SELECT [emp_no], DATEADD(month, 1, [date]) AS [date]
FROM cte_dates
WHERE [date] < dateadd(month, 0, #YMN)
)
SELECT DISTINCT ISNULL(e.emp_no, c.emp_no) emp_no, ISNULL(e.date, c.date) date, ISNULL(e.num_differences, 0) num_differences
into ##new_table
FROM #Emp AS e
RIGHT JOIN cte_dates AS c ON YEAR(c.date) = YEAR(e.date) AND MONTH(c.date) = MONTH(e.date)
-----------------------------------------------------------------------------------------------MAIN CTE------------------------------------------------------------------------------
;with cte_RawScore as
(
select emp_no
, date YMN
,sum(case when datediff(month, convert(datetime, #YMN, 112), date) = 0 then num_differences else 0 end) as thismonth
,sum(case when datediff(month, convert(datetime, #YMN, 112), date) between -2 and 0 then num_differences else 0 end) as last3month
,sum(case when datediff(month, convert(datetime, #YMN, 112), date) between -5 and 0 then num_differences else 0 end) as last6month
from ##new_table d
group by emp_no, date
)
select
emp_no
,YMN
,case when last6month = 0 then 5
when last3month = 0 then 4
when thismonth = 0 then 3
when thismonth <= 3 then 2
else 1 end RawScore
from cte_RawScore
ORDER BY day(YMN) desc
drop table ##new_table
I want this the scoring only to be applicable for 6 months from and after July 2017. i.e. the #YMN is a variable that stores the year month number; and the score, according to the above rule applies to the 6 months from 201707.
So 201707 is 1 month,
201708 is 2 months, etc, up to 201712
I wish to have a list of employees with their associated scores, based on the rules mentioned below .
That’s, :
A score of 5 if 0 differences in 6 consecutive months ( from July to December) ;
A score of 4 if 0 differences in 3 consecutive months (from July to December);
A score of 3 if 0 differences for 1 month ( from July to December);
A score of 2 if 1 to 3 differences for 1 month (from July to December);
A score of 1 if 4 or more differences in 1 month (from July to December).
I get the number of differences from a table, but some employees do not appear for certain months; hence I want to give them a difference of 0 if they do not appear for that particular month.
Please assist.
I think I understand what you're getting at. Let me give you a simplified example. You need a table full of dates to join to. In data warehousing we use a Date dimension which has attributes about every date.
For your example your date dimension table could just have Month names or numbers:
1
2
...
12
Let's call this table Months.
Then you would do something like this, to count a zero for months with no data. Here I'm using what's called a Common Table Expression or CTE (the part with the WITH) in place of a table, since I'm not concerned with creating a permanent table right now.
WITH Months AS (
SELECT 1 AS MonthNumber UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 7 UNION
SELECT 8 UNION
SELECT 9 UNION
SELECT 10 UNION
SELECT 11 UNION
SELECT 12
)
SELECT M.MonthNumber, COUNT(*)
FROM Months as M
LEFT JOIN MyData as D
ON MONTH(D.SomeDateValue) = M.MonthNumber
GROUP BY M.MonthNumber
This will guarantee every month appears with a count, perhaps of zero.

Finding the Datediff between Records in same Table

IP QID ScanDate Rank
101.110.32.80 6 2016-09-28 18:33:21.000 3
101.110.32.80 6 2016-08-28 18:33:21.000 2
101.110.32.80 6 2016-05-30 00:30:33.000 1
I have a Table with certain records, grouped by Ipaddress and QID.. My requirement is to find out which record missed the sequence in the date column or other words the date difference is more than 30 days. In the above table date diff between rank 1 and rank 2 is more than 30 days.So, i should flag the rank 2 record.
You can use LAG in Sql 2012+
declare #Tbl Table (Ip VARCHAR(50), QID INT, ScanDate DATETIME,[Rank] INT)
INSERT INTO #Tbl
VALUES
('101.110.32.80', 6, '2016-09-28 18:33:21.000', 3),
('101.110.32.80', 6, '2016-08-28 18:33:21.000', 2),
('101.110.32.80', 6, '2016-05-30 00:30:33.000', 1)
;WITH Result
AS
(
SELECT
T.Ip ,
T.QID ,
T.ScanDate ,
T.[Rank],
LAG(T.[Rank]) OVER (ORDER BY T.[Rank]) PrivSRank,
LAG(T.ScanDate) OVER (ORDER BY T.[Rank]) PrivScanDate
FROM
#Tbl T
)
SELECT
R.Ip ,
R.QID ,
R.ScanDate ,
R.Rank ,
R.PrivScanDate,
IIF(DATEDIFF(DAY, R.PrivScanDate, R.ScanDate) > 30, 'This is greater than 30 day. Rank ' + CAST(R.PrivSRank AS VARCHAR(10)), '') CFlag
FROM
Result R
Result:
Ip QID ScanDate Rank CFlag
------------------------ ----------- ----------------------- ----------- --------------------------------------------
101.110.32.80 6 2016-05-30 00:30:33.000 1
101.110.32.80 6 2016-08-28 18:33:21.000 2 This is greater than 30 day. Rank 1
101.110.32.80 6 2016-09-28 18:33:21.000 3 This is greater than 30 day. Rank 2
While Window Functions could be used here, I think a self join might be more straight forward and easier to understand:
SELECT
t1.IP,
t1.QID,
t1.Rank,
t1.ScanDate as endScanDate,
t2.ScanDate as beginScanDate,
datediff(day, t2.scandate, t1.scandate) as scanDateDays
FROM
table as t1
INNER JOIN table as t2 ON
t1.ip = t2.ip
t1.rank - 1 = t2.rank --get the record from t2 and is one less in rank
WHERE datediff(day, t2.scandate, t1.scandate) > 30 --only records greater than 30 days
It's pretty self-explanatory. We are joining the table to itself and joining the ranks together where rank 2 gets joined to rank 1, rank 3 gets joined to rank 2, and so on. Then we just test for records that are greater than 30 days using the datediff function.
I would use windowed function to avoid self join which in many case will perform better.
WITH cte
AS (
SELECT
t.IP
, t.QID
, LAG(t.ScanDate) OVER (PARTITION BY t.IP ORDER BY T.ScanDate) AS beginScanDate
, t.ScanDate AS endScanDate
, DATEDIFF(DAY,
LAG(t.ScanDate) OVER (PARTITION BY t.IP ORDER BY t.ScanDate),
t.ScanDate) AS Diff
FROM
MyTable AS t
)
SELECT
*
FROM
cte c
WHERE
Diff > 30;

SQL How to show '0' value for a month, if no data exists in the table for that month

First of all my result looks like this:
KONTONR
Month
SELSKAPSKODE
BELOP
459611
1
BAGA
156000
459611
2
BAGA
73000
459611
4
BAGA
217000
459611
5
BAGA
136000
459611
1
CIVO
45896
459611
3
CIVO
32498
459611
4
CIVO
9841
330096
1
BAGA
42347
330096
3
BAGA
3695
I'm trying to show month 2 month bookings on several accounts, per account (KONTONR) there are several codes (SELSKAPSKODE) on which bookings are recorded (the sum of the bookings as BELOP). I would like to give an overview of the sum of the bookings (BELOP) per account (KONTONR) per month per code (SELSKAPSKODE). My problem is the codes don't show in a month if no bookings are made on that code. Is there a way to fix this? I understand why the codes don't show, since they're simply not in the table I'm querying. And I suspect that the solution is in making a 'fake' table which I then join (left outer join?) with 'another' table.
I just can't get it to work, I'm pretty new to SQL. Can someone please help?
My query looks like this (I only inserted the 'nested' query to make a set-up for a join, if this makes sense?!):
SELECT TOP (100) PERCENT KONTONR, Month, SELSKAPSKODE, BELOP
FROM (
SELECT SELSKAPSKODE, KONTONR, SKIPS_KODE, MONTH(POSTDATO) AS Month, SUM(BELOP) AS BELOP
FROM dbo.T99_DETALJ
WHERE (POSTDATO >= '2012-01-01') AND (BILAGSART = 0 OR BILAGSART = 2)
GROUP BY SELSKAPSKODE, KONTONR, SKIPS_KODE, MONTH(POSTDATO)
) AS T99_summary
GROUP BY KONTONR, SELSKAPSKODE, Month, BELOP
ORDER BY KONTONR, SELSKAPSKODE, Month
So concluding I would like to 'fill up' the missing months (see table at the start), for instance for account (KONTONR) 459611 month 3 is 'missing'. I would like to show month 3, with the sum of the bookings (BELOP) as '0'. Any help is greatly appreciated, thanks in advance!
You can query a table with the values 1-12 and left outer join your result.
Here is a sample using a table variable instead of your query and a CTE to build a table with numbers.
declare #T table
(
Month int
)
insert into #T values(1)
insert into #T values(1)
insert into #T values(1)
insert into #T values(3)
insert into #T values(3)
;with Months(Month) as
(
select 1
union all
select Month + 1
from Months
where Month < 12
)
select M.Month,
count(T.Month) Count,
isnull(sum(T.Month), 0) Sum
from Months as M
left outer join #T as T
on M.Month = T.Month
group by M.Month
Result:
Month Count Sum
----------- ----------- -----------
1 3 3
2 0 0
3 2 6
4 0 0
5 0 0
6 0 0
7 0 0
8 0 0
9 0 0
10 0 0
11 0 0
12 0 0
if you don't want to do all that you could also modify this: SUM(BELOP) with this:
Sum (case when BELOP is not null then 1 else 0 end)
You can also add in the year if you have a creation date for the interactions you are counting which may be helpful if your interactions span the course of many years.
with Months(Month) as
(
select 1
union all
select Month + 1
from Months
where Month < 12
)
select M.Month, year(CreatedOn) as Year,
count(amount) Count,
isnull(sum(amount), 0) Sum
from Months as M
left outer join Charge as C
on M.Month = (month(CreatedOn))
group by M.Month, year(CreatedOn) order by year(CreatedOn)

Resources