SQL Server 2012 Find bucket wise products and dates - sql

I have a table like below:
date prodname
2018-01-01 Product1
2018-01-05 Product1
2018-01-09 Product1
2018-01-17 Product1
2018-01-24 Product1
2018-01-26 Product1
2018-02-20 Product1
2018-01-02 Product2
2018-01-07 Product2
2018-01-09 Product2
2018-01-17 Product2
2018-01-28 Product2
Code:
CREATE TABLE myTemp(
Date Date,
ProductName varchar(10))
INSERT INTO myTemp values
('2018-01-01', 'Product1')
,('2018-01-05','Product1')
,('2018-01-09','Product1')
,('2018-01-17','Product1')
,('2018-01-24','Product1')
,('2018-01-26','Product1')
,('2018-02-20','Product1')
,('2018-01-02','Product2')
,('2018-01-07','Product2')
,('2018-01-09','Product2')
,('2018-01-17','Product2')
,('2018-01-28','Product2')
The table can have any number of date's entries for each product.
I want to display the product name, minimum date and min date + 24 days. And I want to do this for each 25 days' bucket.
For example,
In case of product1, 2018-01-01 is the min date and 2018-01-25 is the PLUS 25 date.
BUT, product1 also has records for date greater than 2018-01-25. So I should see one more record for product1 in the output like below:
2018-01-26 2018-02-19 product1
2018-01-26 being the Min Date after 2018-01-25. 2109-02-19 being the PLUS 25 date. and product1 is the product name.
That way, my final output should be:
2018-01-01 2018-01-25 Product1
2018-01-26 2018-02-19 Product1
2018-01-02 2018-01-26 Product2
2018-01-28 2018-02-21 Product2
I tried doing it with min() and DateAdd() but it gives me one row for each product.
DROP TABLE IF EXISTS #MyTemp
select MIN(Date) CurrDate, DateAdd(d, 24, MIN(Date)) CurrPlus25Date, ProductName
into #MyTemp
from myTemp
group by ProductName
select * from #MyTemp
Appreciate any help.

Here is one way using recursive cte. If you have a number / tally table, you can simply take the product cte and cross join with the tally table
; with
product as
(
select ProductName,
minDate = min(Date),
maxDate = max(Date)
from myTemp
group by ProductName
),
productdates as
(
select ProductName, minDate, maxDate,
frDate = minDate, toDate = dateadd(day, 24, minDate)
from product
union all
select ProductName, minDate, maxDate,
frDate = dateadd(day, 1, toDate),
toDate = dateadd(day, 25, toDate)
from productdates
where frDate < maxDate
)
select ProductName, frDate, toDate
from productdates
order by ProductName, frDate

This following query will provide your desired output-
SELECT C.min_datre,C.Plus_24_date,C.ProductName
FROM
(
--Select data for the initial Date Range
SELECT MIN(Date) AS min_datre,DATEADD(DD,24,MIN(Date)) AS Plus_24_date,
ProductName
FROM myTemp
GROUP BY ProductName
UNION ALL
--Select Data for Out of first defined range
SELECT MIN(B.Date),
DATEADD(DD,24,MIN(B.Date)),
ProductName
FROM
(
--Select records per product with date greater
--than overall MIN(Date)+24
SELECT
mt.date Date,
mt.ProductName,
A.min_date
FROM myTemp mt
INNER JOIN (
SELECT MIN(Date) min_date,
DATEADD(DD,24,MIN(Date)) with_24_day,
ProductName
FROM myTemp
GROUP BY ProductName
) A
ON mt.ProductName = A.ProductName
AND mt.Date > A.with_24_day
) B
GROUP BY B.ProductName
) C
ORDER BY 3,1

Related

add missing month in sales

I have a sales table with below values.
TransactionDate,CustomerID,Quantity
2020-01-01,1234,5
2020-07-01,1234,9
2020-03-01,3241,8
2020-07-01,3241,4
As you can see first purchase was for CustomerID = 1234 in Jan 2020 and for CustomerID = 3241 in MAR 2020.
I want on output where in all the date should be filled up with 0 purchase value.
means if there is no sale between Jan and July Then output should be as below.
TransactionDate,CustomerID,Quantity
2020-01-01,1234,5
2020-02-01,1234,0
2020-03-01,1234,0
2020-04-01,1234,0
2020-05-01,1234,0
2020-06-01,1234,0
2020-07-01,1234,9
2020-03-01,3241,8
2020-04-01,3241,0
2020-05-01,3241,0
2020-06-01,3241,0
2020-07-01,3241,4
You can use a recursive query to create the missing dates per customer.
with recursive dates (customerid, transactiondate, max_transactiondate) as
(
select customerid, min(transactiondate), max(transactiondate)
from sales
group by customerid
union all
select customerid, dateadd(month, 1, transactiondate), max_transactiondate
from dates
where transactiondate < max_transactiondate
)
select
d.customerid,
d.transactiondate,
coalesce(s.quantity, 0) as quantity
from dates d
left join sales s on s.customerid = d.customerid and s.transactiondate = d.transactiondate
order by d.customerid, d.transactiondate;
This is a convenient place to use a recursive CTE. Assuming all your dates are on the first of the month:
with cr as (
select customerid, min(transactiondate) as mindate, max(transactiondate) as maxdate
from t
group by customerid
union all
select customerid, dateadd(month, 1, mindate), maxdate
from cr
where mindate < maxdate
)
select cr.customerid, cr.mindate as transactiondate, coalesce(t.quantity, 0) as quantity
from cr left join
t
on cr.customerid = t.customerid and
cr.mindate = t.transactiondate;
Here is a db<>fiddle.
Note that if you have more than 100 months to fill in, then you will need option (maxrecursion 0).
Also, this can easily be adapted if the dates are not all on the first of the month. But you would need to explain what the result set should look like in that case.
[EDIT] Based on what other posted I updated the code.
;with
min_date_cte(MinTransactionDate, MaxTransactionDate) as (
select min(TransactionDate), max(TransactionDate) from tsales),
unq_yrs_cte(year_int) as (
select distinct year(TransactionDate) from tsales),
unq_cust_cte(CustomerID) as (
select distinct CustomerID from tsales)
select datefromparts(uyc.year_int, v.month_int, 1) TransactionDate,
ucc.CustomerID,
isnull(t.Quantity, 0) Quantity
from min_date_cte mdc
cross join unq_yrs_cte uyc
cross join unq_cust_cte ucc
cross join (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) v(month_int)
left join tsales t on datefromparts(uyc.year_int, v.month_int, 1)=t.TransactionDate
and ucc.CustomerID=t.CustomerId
where
datefromparts(uyc.year_int, v.month_int, 1)>=mdc.MinTransactionDate
and datefromparts(uyc.year_int, v.month_int, 1)<=mdc.MaxTransactionDate;
Results
TransactionDate CustomerID Quantity
2020-01-01 1234 5
2020-01-01 3241 0
2020-02-01 1234 0
2020-02-01 3241 0
2020-03-01 1234 0
2020-03-01 3241 8
2020-04-01 1234 0
2020-04-01 3241 0
2020-05-01 1234 0
2020-05-01 3241 0
2020-06-01 1234 0
2020-06-01 3241 0
2020-07-01 1234 9
2020-07-01 3241 4
You can make use of recursive query:
WITH cte1 as
(
select customerid, min([TransactionDate]) as Monthly_date, max([TransactionDate]) as end_date from calender_table
group by customerid
union all
select customerid, dateadd(month, 1, Monthly_date), end_date from cte1
where Monthly_date < end_date
)
select a.Monthly_date, a.customerid,coalesce(b.quantity, 0) from cte1 a left outer join calender_table b
on (a.Monthly_date = b.[TransactionDate] and a.customerid = b.customerid)
order by a.customerid, a.Monthly_date;

How to join tally dates with list of periods to retrieve wanted results?

I have a list of tally dates that I want to combine with prices, but I want for results to have all the dates from tally and dates and price values from prices (and null prices when no periods correspond to tally date)
Dates
Date
2017-12-22
2017-12-23
2017-12-24
2017-12-25
2017-12-26
2017-12-27
2017-12-28
2017-12-29
2017-12-30
2017-12-31
Prices
periodstart periodend price productID
2017-12-23 2017-12-25 50 1
2017-12-26 2017-12-29 10 1
Sql query result
date price productid
2017-12-22 null 1
2017-12-23 50 1
2017-12-24 50 1
2017-12-25 50 1
2017-12-26 10 1
2017-12-27 10 1
2017-12-28 10 1
2017-12-29 10 1
2017-12-30 null 1
2017-12-31 null 1
UPDATE
I added productID column in prices
rextester: http://rextester.com/ADJZSW20744
create table dbo.calendar (
[date] date primary key clustered
);
insert into dbo.calendar values
('2017-12-22'),('2017-12-23'),('2017-12-24')
,('2017-12-25'),('2017-12-26'),('2017-12-27')
,('2017-12-28'),('2017-12-29'),('2017-12-30')
,('2017-12-31');
create table prices (
periodstart date
, periodend date
, price int
, productid int
);
insert into prices values
('2017-12-23','2017-12-25',50,1)
,('2017-12-26','2017-12-29',10,1)
,('2017-12-22','2017-12-23',50,2)
,('2017-12-26','2017-12-27',10,2);
query: This will work with multiple products:
select
c.Date
, p.Price
, x.ProductId
from dbo.Calendar c
outer apply (
select distinct
ProductId
from prices
) x
left join dbo.Prices p on
c.Date >= p.PeriodStart
and c.Date <= p.PeriodEnd
and x.ProductId = p.ProductId
order by x.ProductId, c.Date;
A simple left join should do the trick
Select A.Date
,B.Price
From Dates A
Left Join Prices B on A.Date Between B.periodstart and B.periodend
Try this:
SELECT Date
, price
FROM Dates d
LEFT JOIN Prices p
ON d.Date BETWEEN p.periodstart AND ISNULL(p.periodend, d.Date)
To avoid conflicts in case your periods are intersecting or don't have an ending date, take the latest start period using an apply:
SELECT Date
, price
FROM Dates d
OUTER APPLY
(
SELECT TOP 1 price
FROM Prices p
WHERE d.Date BETWEEN p.periodstart AND ISNULL(p.periodend, d.Date)
ORDER BY p.periodstart DESC
) oa

SQL To sum up amount for multiple rows at same column

I need to sum up the amount for different products but same month for same serviceid. Here is the table:
ServiceID PRODUCT AMT DATE
1 prod1 20 1/1/2013
1 prod2 40 1/1/2013
1 prod1 30 2/1/2013
1 prod2 50 2/1/2013
I need to add prod1+prod2 for 1/1/2013, prod1+prod2 for 2/1/2013
This is the result that I want:
ServiceID PRODUCT AMT DATE
1 prod1 60 1/1/2013
1 prod2 80 2/1/2013
select serviceID, product, sum(amt), date
from table
where date >= 1/1/2013
and date <= 2/1/2013
group by 1, 2, 4
The group by doesn't get the result that I want.
In reality I can't specify product because it has more than what I post here.
select serviceID, 'prod1+prod2', sum(amt), date
from table
where date >= 1/1/2013
and date <= 2/1/2013
group by 1, 4
you can use DATEADD, DATEDIFF to get monthly aggregation
SELECT serviceID,
STUFF( ( SELECT DISTINCT '&' + PRODUCT
FROM Table1 T1
WHERE T1.ServiceID = T.ServiceID
FOR XML PATH(''), TYPE).value('.','nvarchar(max)') , 1,1,'') as Products,
sum(amt) as totalAmount,
DATEADD(MONTH, DATEDIFF(MONTH,0,[DATE]), 0) as month
FROM Table1 T
GROUP BY serviceId, DATEADD(MONTH, DATEDIFF(MONTH,0,[DATE]), 0)
As latest comment, adding total column using cross apply
SELECT T1.ServiceID, PRODUCT, AMT, [DATE], C.Total
FROM Table1 T1
CROSS APPLY
(
SELECT serviceID,
sum(amt) as Total,
DATEADD(MONTH, DATEDIFF(MONTH,0,[DATE]), 0) as month
FROM Table1 T
GROUP BY serviceId, DATEADD(MONTH, DATEDIFF(MONTH,0,[DATE]), 0)
) C
where T1.ServiceID = C.serviceId
AND T1.[DATE] = C.month

how to replace "Nothing" cell by last month value

I am using SSRS develop a report via Matrix.
I sum the product sales qty every month but if some month sale qty is 0, the cell will be Nothing.
I'm trying to using Previous function to solve problem but this function seems not work for Matrix object.
Is there any way to do this?
1st table is currently report result.
2nd table is that i want.
Assuming that you have a table with the structure and content like this:
CREATE TABLE a (date Date, product_name NVarchar(20), sale_qty Decimal(10,2))
INSERT INTO a (date, product_name, sale_qty) VALUES ('20130510','product A',1)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20130601','product A',0)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20130501','product B',5)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20140205','product A',1)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20140215','product A',1)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20140202','product B',2)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20140301','product A',0)
Then the below sql-statement will work for you (as far as I understood the requirement):
SELECT
y.Date2 AS 'yyyy/mm'
, y.product_name
, ISNULL(z.Month_sale_QTY,0) AS Month_sale_QTY
FROM
(SELECT
DATEADD(MONTH, x.number, y.StartDate) AS Date2
, (SELECT TOP 1 DATEADD(d,1-DATEPART(d,a.date),a.date) FROM a WHERE DATEADD(d,1-DATEPART(d,a.date),a.date) <= DATEADD(MONTH, x.number, y.StartDate) AND a.product_name = p.product_name AND ISNULL(a.sale_qty,0)<>0 ORDER BY 1 DESC) AS Date3
, p.product_name
FROM
master.dbo.spt_values x
INNER JOIN
(SELECT Dateadd(d,1-DATEPART(d, MIN(Date)), MIN(Date)) AS StartDate, MAX(Date) AS EndDate FROM a) AS y
ON x.number <= DATEDIFF(MONTH, y.StartDate, y.EndDate)
,(SELECT DISTINCT product_name FROM a) AS p WHERE x.type = 'P') y
LEFT JOIN
(SELECT
DATEADD(d,1-DATEPART(d,a.date),a.date) AS Date2
, a.product_name
, SUM(a.sale_qty) as Month_sale_QTY
FROM a
GROUP BY
DATEADD(d,1-DATEPART(d,a.date),a.date)
, a.product_name
) z ON y.Date3 = z.Date2 AND y.product_name = z.product_name
It returns the following:
yyyy/mm product_name Month_sale_QTY
---------- -------------------- ---------------------------------------
2013-05-01 product A 1.00
2013-06-01 product A 1.00
2013-07-01 product A 1.00
2013-08-01 product A 1.00
2013-09-01 product A 1.00
2013-10-01 product A 1.00
2013-11-01 product A 1.00
2013-12-01 product A 1.00
2014-01-01 product A 1.00
2014-02-01 product A 2.00
2014-03-01 product A 2.00
2013-05-01 product B 5.00
2013-06-01 product B 5.00
2013-07-01 product B 5.00
2013-08-01 product B 5.00
2013-09-01 product B 5.00
2013-10-01 product B 5.00
2013-11-01 product B 5.00
2013-12-01 product B 5.00
2014-01-01 product B 5.00
2014-02-01 product B 2.00
2014-03-01 product B 2.00
I'm sure it can be optimized using windowing functions and/or CTE, but as a quick solution this one will work too.

Running Total on date column

I have the following data in my table:
id invoice_id date ammount
1 1 2012-01-01 100.00
20 1 2012-01-31 50.00
470 1 2012-01-15 300.00
Now, I need to calculate running total for an invoice in some period. So, the output for this data sample should look like this:
id invoice_id date ammount running_total
1 1 2012-01-01 100.00 100.00
470 1 2012-01-15 300.00 400.00
20 1 2012-01-31 50.00 450.00
I tried with this samples http://www.sqlusa.com/bestpractices/runningtotal/ and several others, but the problem is that I could have entries like id 20, date 2012-01-31 and id 120, date 2012-01-01, and then I couldn't use NO = ROW_NUMBER(over by date)... in first select and then ID < NO in second select for calculating running total.
DECLARE #DateStart DATE='2012-01-01';
WITH cte
AS (SELECT id = Row_number() OVER(ORDER BY [date]),
DATE,
myid = id,
invoice_id,
orderdate = CONVERT(DATE, DATE),
ammount
FROM [Table_2]
WHERE DATE >= #DateStart)
SELECT myid,
invoice_id,
DATE,
ammount,
runningtotal = (SELECT SUM(ammount)
FROM cte
WHERE id <= a.id)
FROM cte AS a
ORDER BY id