SQL To sum up amount for multiple rows at same column - sql

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

Related

SQL - How to sum revenue by customer over the last 7 days for each date

I want to sum the previous 7 days revenue from each date for each customer. There are some missing dates for some customers and various different customers so I cannot use a Lag function. I was previously using windows but I could only partition by customer_ID and could not partition by the date range as well.
Some sample data as follows:
Customer_ID
Date
Revenue
1
01/02/21
$20
2
01/02/21
$30
1
02/02/21
$40
2
02/02/21
$50
1
03/02/21
$20
2
03/02/21
$60
1
04/02/21
$10
2
04/02/21
$80
1
05/02/21
$100
2
05/02/21
$40
1
06/02/21
$20
2
06/02/21
$30
1
07/02/21
$50
2
07/02/21
$70
1
08/02/21
$10
2
08/02/21
$20
1
09/02/21
$3
2
09/02/21
$40
This result would give the sum of the previous seven days revenue broken down by customer ID for each date. It is ordered by Customer_ID and Date
Customer_ID
Date
Revenue
1
01/02/21
$20
1
02/02/21
$60
1
03/02/21
$80
1
04/02/21
$90
1
05/02/21
$190
1
06/02/21
$210
1
07/02/21
$260
1
08/02/21
$250
1
09/02/21
$240
2
01/02/21
$30
2
02/02/21
$80
2
03/02/21
$140
2
04/02/21
$220
2
05/02/21
$260
2
06/02/21
$290
2
07/02/21
$360
2
08/02/21
$350
2
09/02/21
$340
Data:
Database table
Query Result:
Query Result
select customer_id,date,sum(revenue) from customer_table where date >= sysdate-7 and date < =sysdate group by customer_id,date;
Hope this helps you
You can try going with a self join, where you match on:
tab1.customer_id = table2.customer_id
tab1.date being matched with till-6-days-before records of tab2.date.
Then apply the SUM on t2.revenues and aggregate on the selected fields.
SELECT t1.Customer_ID,
t1.Date,
SUM(t2.Revenue) AS total
FROM tab t1
LEFT JOIN tab t2
ON t1.Customer_ID = t2.Customer_ID
AND t1.Date BETWEEN t2.Date AND DATEADD(day, -6, t2.Date)
GROUP BY t1.Customer_ID,
t1.Date
This approach would avoid the issue of missing dates for customers, as long as you are comparing dates instead of taking the "last 7 records" with LAG.
with cte as (-- Customer_ID Date Revenue
select 1 customer_id, DATE( '01/02/2021','DD/MM/YYYY') Some_date, 20 Revenue
union all select 2 customer_id, DATE( '01/02/2021','DD/MM/YYYY') Some_date, 30 Revenue
union all select 1 customer_id, DATE( '03/02/2021','DD/MM/YYYY') Some_date, 20 Revenue
union all select 2 customer_id, DATE( '03/02/2021','DD/MM/YYYY') Some_date, 60 Revenue
union all select 1 customer_id, DATE( '04/02/2021','DD/MM/YYYY') Some_date, 10 Revenue
union all select 2 customer_id, DATE( '04/02/2021','DD/MM/YYYY') Some_date, 80 Revenue
union all select 1 customer_id, DATE( '05/02/2021','DD/MM/YYYY') Some_date, 100 Revenue
union all select 2 customer_id, DATE( '05/02/2021','DD/MM/YYYY') Some_date, 40 Revenue
union all select 1 customer_id, DATE( '06/02/2021','DD/MM/YYYY') Some_date, 20 Revenue
union all select 2 customer_id, DATE( '06/02/2021','DD/MM/YYYY') Some_date, 30 Revenue
union all select 1 customer_id, DATE( '07/02/2021','DD/MM/YYYY') Some_date, 50 Revenue
union all select 2 customer_id, DATE( '07/02/2021','DD/MM/YYYY') Some_date, 70 Revenue
union all select 1 customer_id, DATE( '08/02/2021','DD/MM/YYYY') Some_date, 10 Revenue
union all select 2 customer_id, DATE( '08/02/2021','DD/MM/YYYY') Some_date, 20 Revenue
union all select 1 customer_id, DATE( '09/02/2021','DD/MM/YYYY') Some_date, 3 Revenue
union all select 1 customer_id, DATE( '02/02/2021','DD/MM/YYYY') Some_date, 40 Revenue
union all select 2 customer_id, DATE( '02/02/2021','DD/MM/YYYY') Some_date, 50 Revenue
union all select 2 customer_id, DATE( '09/02/2021','DD/MM/YYYY') Some_date, 40 Revenue)
select customer_id, revenue
, DATE_TRUNC('week', Some_date ) week_number
, sum(revenue)
over(partition by customer_id,week_number
order by Some_date asc
rows between unbounded preceding and current row) volia
from cte

SUM from Specific Date until the end of the month SQL

I have the following table:
ID GROUPID oDate oValue
1 A 2014-06-01 100
2 A 2014-06-02 200
3 A 2014-06-03 300
4 A 2014-06-04 400
5 A 2014-06-05 500
FF. until the end of the month
30 A 2014-06-30 600
I have 3 kinds of GROUPID, and each group will create one record per day.
I want to calculate the total of oValue from the 2nd day of each month until the end of the month. So the total of June would be from 2/Jun/2014 until 30/Jun/2014. If July, then the total would be from 2/Jul/2014 until 31/Jul/2014.
The output will be like this (sample):
GROUPID MONTH YEAR tot_oValue
A 6 2014 2000
A 7 2014 3000
B 6 2014 1500
B 7 2014 5000
Does anyone know how to solve this with sql syntax?
Thank you.
You can use a correlated subquery to get this:
SELECT T.ID,
T.GroupID,
t.oDate,
T.oValue,
ct.TotalToEndOfMonth
FROM T
OUTER APPLY
( SELECT TotalToEndOfMonth = SUM(oValue)
FROM T AS T2
WHERE T2.GroupID = T.GroupID
AND T2.oDate >= T.oDate
AND T2.oDate < DATEADD(MONTH, DATEDIFF(MONTH, 0, T.oDate) + 1, 0)
) AS ct;
For your example data this gives:
ID GROUPID ODATE OVALUE TOTALTOENDOFMONTH
1 A 2014-06-01 100 2100
2 A 2014-06-02 200 2000
3 A 2014-06-03 300 1800
4 A 2014-06-04 400 1500
5 A 2014-06-05 500 1100
30 A 2014-06-30 600 600
Example on SQL Fiddle
For future reference if you ever upgrade, in SQL Server 2012 (and later) this becomes even easier with windowed aggregate functions that allow ordering:
SELECT T.*,
TotalToEndOfMonth = SUM(oValue)
OVER (PARTITION BY GroupID,
DATEPART(YEAR, oDate),
DATEPART(MONTH, oDate)
ORDER BY oDate DESC)
FROM T
ORDER BY oDate;
Example on SQL Fiddle
EDIT
If you only want this for the 2nd of each month, but still need all the fields then you can just filter the results of the first query I posted:
SELECT T.ID,
T.GroupID,
t.oDate,
T.oValue,
ct.TotalToEndOfMonth
FROM T
OUTER APPLY
( SELECT TotalToEndOfMonth = SUM(oValue)
FROM T AS T2
WHERE T2.GroupID = T.GroupID
AND T2.oDate >= T.oDate
AND T2.oDate < DATEADD(MONTH, DATEDIFF(MONTH, 0, T.oDate) + 1, 0)
) AS ct
WHERE DATEPART(DAY, T.oDate) = 2;
Example on SQL Fiddle
If you are only concerned with the total then you can use:
SELECT T.GroupID,
[Month] = DATEPART(MONTH, oDate),
[Year] = DATEPART(YEAR, oDate),
tot_oValue = SUM(T.oValue)
FROM T
WHERE DATEPART(DAY, T.oDate) >= 2
GROUP BY T.GroupID, DATEPART(MONTH, oDate), DATEPART(YEAR, oDate);
Example on SQL Fiddle
Not sure whether you have data for different years
Select YEAR(oDate),MONTH(oDate),SUM(Value)
From #Temp
Where DAY(oDate)>1
Group By YEAR(oDate),MONTH(oDate)
If you want grouped per GROUPID, year and month this should do it:
SELECT
GROUPID,
[MONTH] = MONTH(oDate),
[YEAR] = YEAR(oDate),
tot_oValue = SUM(ovalue)
FROM your_table
WHERE DAY(odate) > 1
GROUP BY GROUPID, YEAR(oDate), MONTH(oDate)
ORDER BY GROUPID, YEAR(oDate), MONTH(oDate)
This query produces required output:
SELECT GROUPID, MONTH(oDate) AS "Month", YEAR(oDate) AS "Year", SUM(oValue) AS tot_oValue
FROM table_name
WHERE DAY(oDate) > 1
GROUP BY GROUPID, YEAR(oDate), MONTH(oDate)
ORDER BY GROUPID, YEAR(oDate), MONTH(oDate)

Get Products By Minimum Duration Between StartDate and EndDate?

I am having some problem to get distinct products between start-data and end-date and minimum duration. My table structure is,
ID SKU Desc1 Desc2 Price PriceFrom PriceTo
-------------------------------------------------------
1 xxxx xxxx xxxx 12 1/1/2014 1/1/2015
1 xxx xxxx xxxx 12 1/1/2014 2/1/2014
1 xxx xxxx xxxx 12 9/1/2014 10/1/2014
Let's say today's date is 09/04/2014. So we have 2 options record 1 and 3(because 2 is outside the range of today's date) but I choose 3 because the duration of 3rd record is less than 1st record?
You can do this by using order by and top:
select top 1 t.*
from table t
where cast(getdate() as date) >= PriceFrom and cast(getdate() as date) <= PriceTo
order by datediff(day, PriceFrom, PriceTo) asc;
update:
SELECT
MIN(DATEDIFF(DAY, t.PriceFrom, t.PriceTo)),
t.ID,
t.Name,
t.ModelNumber,
t.Description,
t.Price,
t.NewPrice,
t.SKU
FROM Products t
WHERE GETDATE() BETWEEN PriceFrom AND PriceTo
GROUP BY t.ID,
t.Name,
t.ModelNumber,
t.Description,
t.Price,
t.NewPrice,
t.SKU

SQL spread month value into weeks

I have a table where I have values by month and I want to spread these values by week, taking into account that weeks that spread into two month need to take part of the value of each of the month and weight on the number of days that correspond to each month.
For example I have the table with a different price of steel by month
Product Month Price
------------------------------------
Steel 1/Jan/2014 100
Steel 1/Feb/2014 200
Steel 1/Mar/2014 300
I need to convert it into weeks as follows
Product Week Price
-------------------------------------------
Steel 06-Jan-14 100
Steel 13-Jan-14 100
Steel 20-Jan-14 100
Steel 27-Jan-14 128.57
Steel 03-Feb-14 200
Steel 10-Feb-14 200
Steel 17-Feb-14 200
As you see above, the week that overlaps between Jan and Feb needs to be calculated as follows
(100*5/7)+(200*2/7)
This takes into account tha the week of the 27th has 5 days that fall into Jan and 2 into Feb.
Is there any possible way to create a query in SQL that would achieve this?
I tried the following
First attempt:
select
WD.week,
PM.PRICE,
DATEADD(m,1,PM.Month),
SUM(PM.PRICE/7) * COUNT(*)
from
( select '2014-1-1' as Month, 100 as PRICE
union
select '2014-2-1' as Month, 200 as PRICE
)PM
join
( select '2014-1-20' as week
union
select '2014-1-27' as week
union
select '2014-2-3' as week
)WD
ON WD.week>=PM.Month
AND WD.week < DATEADD(m,1,PM.Month)
group by
WD.week,PM.PRICE, DATEADD(m,1,PM.Month)
This gives me the following
week PRICE
2014-1-20 100 2014-02-01 00:00:00.000 14
2014-1-27 100 2014-02-01 00:00:00.000 14
2014-2-3 200 2014-03-01 00:00:00.000 28
I tried also the following
;with x as (
select price,
datepart(week,dateadd(day, n.n-2, t1.month)) wk,
dateadd(day, n.n-1, t1.month) dt
from
(select '2014-1-1' as Month, 100 as PRICE
union
select '2014-2-1' as Month, 200 as PRICE) t1
cross apply (
select datediff(day, t.month, dateadd(month, 1, t.month)) nd
from
(select '2014-1-1' as Month, 100 as PRICE
union
select '2014-2-1' as Month, 200 as PRICE)
t
where t1.month = t.month) ndm
inner join
(SELECT (a.Number * 256) + b.Number AS N FROM
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) a (Number),
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) b (Number)) n --numbers
on n.n <= ndm.nd
)
select min(dt) as week, cast(sum(price)/count(*) as decimal(9,2)) as price
from x
group by wk
having count(*) = 7
order by wk
This gimes me the following
week price
2014-01-07 00:00:00.000 100.00
2014-01-14 00:00:00.000 100.00
2014-01-21 00:00:00.000 100.00
2014-02-04 00:00:00.000 200.00
2014-02-11 00:00:00.000 200.00
2014-02-18 00:00:00.000 200.00
Thanks
If you have a calendar table it's a simple join:
SELECT
product,
calendar_date - (day_of_week-1) AS week,
SUM(price/7) * COUNT(*)
FROM prices AS p
JOIN calendar AS c
ON c.calendar_date >= month
AND c.calendar_date < DATEADD(m,1,month)
GROUP BY product,
calendar_date - (day_of_week-1)
This could be further simplified to join only to mondays and then do some more date arithmetic in a CASE to get 7 or less days.
Edit:
Your last query returned jan 31st two times, you need to remove the =from on n.n < ndm.nd. And as you seem to work with ISO weeks you better change the DATEPART to avoid problems with different DATEFIRST settings.
Based on your last query I created a fiddle.
;with x as (
select price,
datepart(isowk,dateadd(day, n.n, t1.month)) wk,
dateadd(day, n.n-1, t1.month) dt
from
(select '2014-1-1' as Month, 100.00 as PRICE
union
select '2014-2-1' as Month, 200.00 as PRICE) t1
cross apply (
select datediff(day, t.month, dateadd(month, 1, t.month)) nd
from
(select '2014-1-1' as Month, 100.00 as PRICE
union
select '2014-2-1' as Month, 200.00 as PRICE)
t
where t1.month = t.month) ndm
inner join
(SELECT (a.Number * 256) + b.Number AS N FROM
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) a (Number),
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) b (Number)) n --numbers
on n.n < ndm.nd
) select min(dt) as week, cast(sum(price)/count(*) as decimal(9,2)) as price
from x
group by wk
having count(*) = 7
order by wk
Of course the dates might be from multiple years, so you need to GROUP BY by the year, too.
Actually, you need to spred it over days, and then get the averages by week. To get the days we'll use the Numbers table.
;with x as (
select product, price,
datepart(week,dateadd(day, n.n-2, t1.month)) wk,
dateadd(day, n.n-1, t1.month) dt
from #t t1
cross apply (
select datediff(day, t.month, dateadd(month, 1, t.month)) nd
from #t t
where t1.month = t.month and t1.product = t.product) ndm
inner join numbers n on n.n <= ndm.nd
)
select product, min(dt) as week, cast(sum(price)/count(*) as decimal(9,2)) as price
from x
group by product, wk
having count(*) = 7
order by product, wk
The result of datepart(week,dateadd(day, n.n-2, t1.month)) expression depends on SET DATEFIRST so you might need to adjust accordingly.

Select sum of items based on their monthly entry date

I am trying to select sum of items based on their monthly entry date:
The Inventory table is as below:
EntryDate Items
1/1/2013 2
1/20/2013 5
1/23/2013 3
1/30/2013 2
2/4/2013 4
2/17/2013 34
The desired output with Total row added:
EntryDate Items
1/1/2013 2
1/20/2013 5
1/23/2013 3
1/30/2013 2
**Total 12**
2/4/2013 4
2/17/2013 34
**Total 38**
Below is my attempt. I am trying to do this using rollup but its counting all items at once and not by monthly basis, how to achieve this:
Select Convert(date, EntryDate) AS [DATE],SUM(Items) AS Total,
Case WHEN GROUPING(Items) = 1 THEN 'Rollup'
Else Status end AS Total From [Inventory] Group by Convert(date, EntryDate) WITH
Rollup
If you actually want results like your example, you can use the following:
SELECT EntryDate, Items
FROM (SELECT YEAR(EntryDate)'Year_',MONTH(EntryDate)'Month_',CAST(EntryDate AS VARCHAR(12))'EntryDate',Items,1 'sort'
FROM Inventory
UNION ALL
SELECT YEAR(EntryDate)'Year_',MONTH(EntryDate)'Month_','Total',SUM(Items)'Items',2 'sort'
FROM Inventory
GROUP BY YEAR(EntryDate),MONTH(EntryDate)
)sub
ORDER BY Year_,Month_,sort
Demo: SQL Fiddle
Try this, it will give you the month total in a new column, it will also work when your dates stretch over more than 1 year
SELECT EntryDate, Items,
SUM(Items) OVER (partition by dateadd(month, datediff(month, 0, entrydate), 0)) Total
FROM [Inventory]
Result:
EntryDate Items Total
2013-01-01 2 12
2013-01-20 5 12
2013-01-23 3 12
2013-01-30 2 12
2013-02-04 4 38
2013-02-17 34 38
DATEPART will do try this,
SELECT DATEPART(month, EntryDate) AS 'Month Number',sum(items1) FROM ITEMS
GROUP BY DATEPART(month, EntryDate)
# sqlfiddle
Try:
select entryDate, Items, sum(Items)
from (select entryDate,Items, to_date(entry_date, 'mm/yyyy') month_date from Inventory)
group by month_date
Here's a perfect solution for you
select isnull(cast(DATEFROMPARTS(year,month,day) as varchar),'Total'),
items
from ( SELECT datepart(yyyy,EntryDate) year,
DATEPART(mm, EntryDate) month,
DATEPART(dd, EntryDate) day,
sum(items) items
From Inventory
GROUP BY
ROLLUP (datepart(yyyy,EntryDate),
DATEPART(mm, EntryDate),
DATEPART(dd, EntryDate)
)
) a
where a.year is not null
and a.month is not null;
Demo HERE