This question already has answers here:
Rolling sum previous 3 months SQL Server
(3 answers)
Closed 4 years ago.
I need to aggregate sales of previous five months to current month for each record. In the table below for example,
StoreID |Month |Sales
-----------|----------|----------
119119 |201802|50000
119119 |201803|62500
119119 |201804|93750
119119 |201708|45000
119271 |201803|25000
119271 |201804|75000
119271 |201802|50000
StoreID 119119's aggregated sales for Month 201804 should be 50000+62500+93750.
Aggregated sales for 119119 for Month 201803 should be 62500+50000 and so on.
I need to do this so that I will be able to plot this aggregated column on a time-series graph.
I cannot use ASC ROWS 5 PRECEEDING here because there may not be sales data for every month and I should not aggregate previous 5 records but it should be strictly based on previous 5 months sales data.
Is it possible to do this without using cursors? Can someone suggest a solution?
You can use a self join to get the older values. ie:
DECLARE #stores TABLE(StoreID INT, [Month] VARCHAR(6), Sales INT);
INSERT INTO #stores(StoreID, Month, Sales)
VALUES(119119, '201802', 50000),
(119119, '201803', 62500),
(119119, '201804', 93750),
(119119, '201708', 45000),
(119271, '201803', 25000),
(119271, '201804', 75000),
(119271, '201802', 50000);
WITH
Sales AS (SELECT StoreID, CAST([Month]+'01' AS DATE) AS [Month], Sales FROM #stores)
SELECT s1.StoreID, s1.[Month], SUM(s2.Sales) AS total
FROM Sales s1
inner JOIN Sales s2 ON s1.StoreID=s2.StoreID
AND s1.[Month]>=s2.[Month]
AND DATEDIFF(MONTH, s2.[Month], s1.[Month])<5
GROUP BY s1.StoreID, s1.[Month]
ORDER BY s1.StoreID, s1.[Month];
Related
I am trying to create a Attendance result set in SQL Server for using it in a SSRS report. The Employee Attendance table is as below:
EmpId
ADate
In
Out
1
2023-01-01
8:00
15:00
I need to calculate the Total working days for all months in a year and display the number of working days per employee. Report format should be as follows:
Saturday and Sunday being weekend, I can able to get the no of working days monthly.
Another table tbl_Holiday has entries for holidays Fromdate and ToDate. I need to consider that also when calculating working days. Several number of results i got from the internet for calculating this. But when creating a view using this data , it has to calculate workdays for each employee row
SELECT
EmpName, EmpId,
(SELECT COUNT(*) FROM tbl_EmpAttendance
WHERE EmpRecId = A.RecId
GROUP BY MONTH(Adate), YEAR(Adate)) AS WorkedDays,
dbo.fn_GetWorkDays(DATEFROMPARTS(YEAR(ADate), MONTH(ADate), 1), EOMONTH(ADate)) AS workingDays
FROM
tbl_Employee A
LEFT JOIN
tbl_EmpAttendance B ON A.RecId = B.EmpRecId
fn_GetWorkDays - calculates the working days for month.
I need to get number of holidays from tbl_holiday too, I understand that this query is becoming more complex than I thought. There must be simpler way to achieve this, can anyone please help?
I tried to get the result in one single view, for using that as SSRS report dataset
I am using Microsoft SQL Server Management Studio. I am trying to measure the customer retention rate of an eCommerce site.
For this, I need four values:
customer_id
order_purchase_timestamp
age_by_month
first_purchase
The values of age_by_month and first_purchase are not in my database. I want to calculate them.
In my database, I have customer_id and order_purchase_timestamp.
The first_purchase should be the earliest instance of order_purchase_timestamp. I only want the month and year.
The age_by_month should be the difference of months from first_purchase to order_purchase_timestamp.
I only want to measure the retention of the customer for each month so if two purchases are made in the same month it shouldn't be shown.
the dates are between 2016-10-01 to 2018-09-30. it should be ordered by order_purchase_timestamp
An example
customer_id
order_purchase_timestamp
1
2016-09-04
2
2016-09-05
3
2016-09-05
3
2016-09-15
1
2016-10-04
to
customer_id
first_purchase
age_by_month
order_purchase_timestamp
1
2016-09
0
2016-09-04
2
2016-09
0
2016-09-05
3
2016-09
0
2016-09-05
1
2016-09
1
2016-10-04
What I have done
SELECT
customer_id, order_purchase_timestamp
FROM
orders
WHERE
(order_purchase_timestamp BETWEEN '2016-10-01' AND '2016-12-31')
OR (order_purchase_timestamp BETWEEN '2017-01-01' AND '2017-03-31')
OR (order_purchase_timestamp BETWEEN '2017-04-01' AND '2017-06-30')
OR (order_purchase_timestamp BETWEEN '2017-07-01' AND '2017-09-30')
OR (order_purchase_timestamp BETWEEN '2017-10-01' AND '2017-12-31')
OR (order_purchase_timestamp BETWEEN '2018-01-01' AND '2018-03-31')
OR (order_purchase_timestamp BETWEEN '2018-04-01' AND '2018-06-30')
OR (order_purchase_timestamp BETWEEN '2018-07-01' AND '2018-09-30')
ORDER BY
order_purchase_timestamp
Originally I was going to do it by quarters but I want to do it in months now.
The following approach is designed to be relatively easy to understand. There are other ways (e.g., windowed functions) that may be marginally more efficient; but this makes it easy to maintain at your current SQL skill level.
Note that the SQL commands below build on one another (so the answer is at the end). To follow along, here is a db<>fiddle with the working.
It's based around a simple query (which we'll use as a sub-query) that finds the first order_purchase_timestamp for each customer.
SELECT customer_id, MIN(order_purchase_timestamp) AS first_purchase_date
FROM orders
GROUP BY customer_id
The next thing is DATEDIFF to find the difference between 2 dates.
Then, you can use the above as a subquery to get the first date onto each row - then find the date difference e.g.,
SELECT orders.customer_id,
orders.order_purchase_timestamp,
first_purchases.first_purchase_date,
DATEDIFF(month, first_purchases.first_purchase_date, orders.order_purchase_timestamp) AS age_by_month
FROM orders
INNER JOIN
(SELECT customer_id, MIN(order_purchase_timestamp) AS first_purchase_date
FROM orders
GROUP BY customer_id
) AS first_purchases ON orders.customer_id = first_purchases.customer_id
Note - DATEDIFF has a 'gotcha' that gets most people but is good for you - when comparing months, it ignores the day component e.g., if finding the difference in months, there is 0 difference in months between 1 Jan and 31 Jan. On the other hand, there will be a difference on 1 month between 31 Jan and 1 Feb. However, I think this is actually what you want!
The above, however, repeats when a customer has multiple purchases within the month (it has one row per purchase). Instead, we can GROUP BY to group by the month it's in, then only take the first purchase for that month.
A 'direct' approach to this would be to group on YEAR(orders.order_purchase_timestamp) AND MONTH(orders.order_purchase_timestamp). However, I use a little trick below - using EOMONTH which finds the last day of the month. EOMONTH returns the same date for any date in that month; therefore, we can group by that.
Finally, you can add the WHERE expression and ORDER BY to get the results you asked for (between the two dates)
SELECT orders.customer_id,
MIN(orders.order_purchase_timestamp) AS order_purchase_timestamp,
first_purchases.first_purchase_date,
DATEDIFF(month, first_purchases.first_purchase_date, EOMONTH(orders.order_purchase_timestamp)) AS age_by_month
FROM orders
INNER JOIN
(SELECT customer_id, MIN(order_purchase_timestamp) AS first_purchase_date
FROM orders AS orders_ref
GROUP BY customer_id
) AS first_purchases ON orders.customer_id = first_purchases.customer_id
WHERE orders.order_purchase_timestamp BETWEEN '20161001' AND '20180930'
GROUP BY orders.customer_id, first_purchases.first_purchase_date, EOMONTH(orders.order_purchase_timestamp)
ORDER BY order_purchase_timestamp;
Results - note they are different from yours because you wanted the earliest date to be 1/10/2016.
customer_id order_purchase_timestamp first_purchase_date age_by_month
1 2016-10-04 00:00:00.000 2016-09-04 00:00:00.000 1
Edit: Because someone else will do it like this otherwise!
You can do this with a single read-through that will potentially run a little faster. It is also a bit shorter - but harder to understand imo.
The below uses windows functions to calculate both the customer's earliest purchase, and the earliest purchase for each month (and uses DISTINCT rather than a GROUP BY). With that, it just does the DATEDIFF to calculate the difference.
WITH monthly_orders AS
(SELECT DISTINCT orders.customer_id,
MIN(orders.order_purchase_timestamp) OVER (PARTITION BY orders.customer_id, EOMONTH(orders.order_purchase_timestamp)) AS order_purchase_timestamp,
MIN(orders.order_purchase_timestamp) OVER (PARTITION BY orders.customer_id) AS first_purchase_date
FROM orders)
SELECT *, DATEDIFF(month, first_purchase_date, order_purchase_timestamp) AS age_by_month
FROM monthly_orders
WHERE order_purchase_timestamp BETWEEN '20161001' AND '20180930';
Note however this has one difference in the results. If you have 2 orders in a month, and your lowest date filter is between the to (e.g., orders on 15/10 and 20/10, and your minimum date is 16/10) then the row won't be included as the earliest purchase in the month is outside the filter range.
Also beware with both of these and what type of date or datetime field you are using - if you have datetimes rather than just dates, BETWEEN '20161001' AND '20180930' is not the same as >= '20161001' AND < '20181001'
Here is short query that achieves all you want (descriptions of methods used are inline):
declare #test table (
customer_id int,
order_purchase_timestamp date
)
-- some test data
insert into #test values
(1, '2016-09-04'),
(2, '2016-09-05'),
(3, '2016-09-05'),
(3, '2016-09-15'),
(1, '2016-10-04');
select
customer_id,
-- takes care of correct display of first_purchase
format(first_purchase, 'yyyy-MM') first_purchase,
-- used to get the difference in months
datediff(m, first_purchase, order_purchase_timestamp) age_by_month,
order_purchase_timestamp
from (
select
*,
-- window function used to find min value for given column within group
-- for each row
min(order_purchase_timestamp) over (partition by customer_id) first_purchase
from #test
) a
This question already has answers here:
Is there a way to access the "previous row" value in a SELECT statement?
(9 answers)
Closed 10 months ago.
I have a table with multiple records per subsriberid, i need a query to find all subscriberid with 90 days gaps between any two records (grouped by subscriberid).
There are many entries per subscriberid on different dates.
The objective to find subscriberid with gaps of 90 days, those who did not have any activity for 90 days in a row.
Desired outcome is a list of subscriberid that were idle for 90 days straight at any given point in time, not necessarily the last 90 days.
The columns in the table are:
subscriberid
datecreated
eventtype (this has the different event types, subscription, unsubscription, charging, basically everything)
select * from SubsEvents
where DIFFERENCE between DateCreated >= 90 DAY
GROUP BY SubscriberId
We can use the function LAG() to compare the date with the date of the previous record.
WITH cte AS (
SELECT
subscriberid,
DATEDIFF(
d,
DateCreated,
LAG(DateCreated) OVER (PARTITION BY subscriberid ORDER BY DateCreated )
) AS date_lag
from SubsEvents)
SELECT
subscriberid
FROM cte
WHERE date_lag >= 90;
From the little information you give us, I assume you want this
select t.subscriberid
from ( select t.subscriberid,
t.datecreated,
lag(t.datecreated) over (partition by t.subscriberid order by t.subscriberid, t.datecreated) prevdate
from atable t
) t
where datediff(day, prevdate, datecreated) >= 90
See this DBFiddle to check if this is what you want
For a helpdesk application we have opened date, closed date, technician name.
Using sql server how can I create a query to show by month and over multiple years:
how many jobs were closed for each month?
for the last day of the month how many jobs were in the queue as unclosed, by technician name?
average number of days to close a job by month by technician name?
Thanks
The query you require is a very simple SQL statement using the GROUP BY clause. Because you are asking information that needs grouping by date only and also information that needs grouping by date and by technician you should use GROUPING SETS.
select year(closed) as year
, month(closed) as month
, Technician
, count(*) as count
, avg(datediff(DAY,Opened, Closed)) as avgDays
from jobs
where not Closed is null
group by grouping sets
(
(year(closed), month(Closed)),
(year(closed), month(Closed), technician)
)
order by year(closed)
, month(closed)
, Technician
If you want the jobs that are not closed remove the NOT in the WHERE clause.
This is a partial answer using a helper table with the first day of every month in a date field. This doesn't give by technician grouping but does calculate 'pending jobs'
select date,
(select COUNT(1) FROM dbo.TASKS WHERE
OPENDATE<=date AND
(CLSDDATE>=date Or CLSDDATE is null))as 'Pending jobs',
(select COUNT(1) FROM dbo.TASKS WHERE
month(CLSDDATE)=MONTH(date) AND YEAR(clsddate)=YEAR(date)) as 'Completed jobs',
(select AVG(datediff(DAY,OPENDATE, CLSDDATE)) as avgDays FROM dbo.TASKS WHERE
month(CLSDDATE)=MONTH(date) AND YEAR(clsddate)=YEAR(date)) as 'AvgDays'
from dbo.CalendarMonths
where date<=GETDATE()
I have a table Clients.
Every client has da_reg (date registered in our system). I need to make a report:
By months - total number of clients (count(distinct customernumber)); and new customers by da_reg date (I can do this per month like insert all clients from past month into temp table and then compare WHERE da_reg < 'date' and customerid not in (select customerid from #temp) - however it takes a lot of time to every time compare).
How to make it easiest way? In 1-2 steps?
Please help!
Thanks in advance!
please try this
select count(*) as new_count,
month(da_reg) as month,year(da_reg) as year
(select count(*) from tbl a where tbl.da_reg>=a.da_reg) as total_cus
from tbl
group by month(da_reg),year(da_reg)
You can Select DAtE_TIME column by month and then calc an number of row :Example for August
SELECT *,count(a.id)
FROM TABLE A as a
WHERE DATEPART(month, MY_DATETIME) = 8
where a.id PK of A