SQL Sum by week from daily table - sql

I have a table with sales for products.
The sales are per day. like
product date sales
1 '2013-11-01' 100
1 '2013-11-02' 423
1 '2013-11-03' 700
1 '2013-11-04' 233
2 '2013-11-01' 623
2 '2013-11-02' 451
2 '2013-11-03' 9000
I want to get a query which will show me the week over week sum of sales
So something like:
product week ending sales
1 '2013-11-01' 10000
1 '2013-11-08' 15000
2 '2013-11-01' 4900
2 '2013-11-08' 30000
I'm not sure how I get this weekly groups when summing up.
I'm using teradata

If you are using Teradata 14 you can leverage the DayNumber_Of_Week() function in the database TD_SYSFNLIB:
SELECT s.Product
, s.Date + (7-DayNumber_Of_Week(s.date)) AS WeekEndingDate /* Saturday */
, SUM(s.Sales) AS Sales
FROM sales AS S
GROUP BY 1,2;
This should work in Teradata 13.10 as well.
Using Sys_Calendar:
SELECT s.Product
, s.DATE + (7-c.Day_Of_Week) AS WeekEndingDate /* Saturday */
, SUM(s.Sales) AS Sales
FROM sales AS S
INNER JOIN
Sys_Calendar.Calendar c
ON S.date = c.calendar_date
GROUP BY 1,2;

I know very little about TERADATA, but I believe you can leverage the sys_calendar.calendar table, something like:
SELECT s.Product, c.week_of_year, SUM(s.sales) AS Sales
FROM sales AS s
JOIN sys_calendar.calendar as C
ON s.date = c.date
You'd need the Year in there as well, so as to not group up week 1 of 2013 with week 1 of 2012.

Related

Regarding last year value from the date field

I have a table with the columns Sales_Date and Sales. I am looking for a solution to get Sales for the last year from the Sales_Date Column. Sales_Date column has values from the year 2015 onwards.
For example:
Sales_Date
Sales
1/1/2016
$25
1/8/2016
$57
1/1/2015
$125
1/8/2015
$21
I am looking for the below result set:
Sales_Date
Sales
LYear_Sales_Date
LYear_Sales
1/1/2016
$25
1/1/2015
$125
1/8/2016
$57
1/8/2015
$21
Filter all data to this year (WHERE YEAR(Sales.Sales_Date) = 2016).
LEFT JOIN to the same table, combining each date with the same date one year prior (Sales LEFT JOIN Sales AS Sales_LastYear ON Sales_LastYear.Sales_Date = DATEADD(year, -1, Sales.Sales_Date)).
SELECT the fields that you want (SELECT Sales.Sales_Date, Sales_LastYear.Sales_Date AS LYear_Sales_Date, ...).
Replace the LEFT JOIN with an INNER JOIN, if you want only those records that have a matching last-year record.
Seems like LAG would work here. Assuming you are always wanting the for the same (day and) month:
WITH CTE AS(
SELECT Sales_Date,
Sales,
LAG(Sales_Date) OVER (PARTITION BY DAY(Sales_Date), MONTH(Sales_Date) ORDER BY YEAR(Sales_Date)) AS LYear_Sales_Date,
LAG(Sales) OVER (PARTITION BY DAY(Sales_Date), MONTH(Sales_Date) ORDER BY YEAR(Sales_Date)) AS LYear_Sales
FROM dbo.YourTable)
SELECT Sales_Date,
Sales,
LYear_Sales_Date,
LYear_Sales
FROM CTE
WHERE Sales_Date >= '20160101'
AND Sales_Date < '20170101';

SQL query to compare budget to actual expenses

I'm struggling to build a query comparing budget to actual expense items.
The budget table has a single record per month/category on the first day of the month whereas the expense table has many records throughout the days of the month.
My desired result:
YEAR
MONTH
category
budgetAmt
sumExpenseAmt
2021
1
daily
100
49
2021
1
monthly
42
87
2021
2
daily
101
36
2021
2
monthly
55
82
What I'm getting:
YEAR
MONTH
category
budgetAmt
sumExpenseAmt
2021
1
daily
100
85
2021
1
monthly
42
169
2021
2
daily
101
85
2021
2
monthly
55
169
The amounts in "sumExpenseAmt" are wrong AND they're repeating.
(85 is the sum of all expense-daily items (jan + feb): 40 + 9 + 32 + 4)
(169 is the sum of all expense-monthly items (jan + feb): 83 + 4 + 75 +7)
MY SQL:
SELECT YEAR( "b"."date" ) AS "Year"
, MONTH( "b"."date" ) AS "Month"
, "b"."category"
, "b"."budgetAmt"
, SUM( "e"."expenseAmt" ) AS "sumExpenseAmt"
FROM "budget" AS "b"
JOIN "expense" AS "e" ON "b"."category" = "e"."category"
GROUP BY YEAR( "b"."date" ), MONTH( "b"."date" ), "b"."category", "b"."budgetAmt"
table: budget
date
category
budgetAmt
2021-01-01
daily
100
2021-01-01
monthly
42
2021-02-01
daily
101
2021-02-01
monthly
55
table: expense
date
category
expenseAmt
2021-01-04
daily
40
2021-01-07
daily
9
2021-01-08
monthly
83
2021-01-25
monthly
4
2021-02-01
daily
32
2021-02-05
daily
4
2021-02-15
monthly
75
2021-02-20
monthly
7
I've tried aggregating the expense table with a query and feeding the result into my initial SQL query, but that gives me the same result.
query: qry_summary_expense
date
category
budgetAmt
2021-01-01
daily
49
2021-01-01
monthly
87
2021-02-01
daily
36
2021-02-01
monthly
82
SELECT YEAR( "b"."date" ) AS "Year"
, MONTH( "b"."date" ) AS "Month"
, "b"."category", "b"."budgetAmt"
, SUM( "e"."expenseAmt" ) AS "sumExpenseAmt"
FROM "budget" AS "b"
JOIN "qry_summary_expense" AS "e" ON "b"."category" = "e"."category"
GROUP BY YEAR( "b"."date" ), MONTH( "b"."date" ), "b"."category", "b"."budgetAmt"
I'd join on both month and category
select year(b.date) as year,
month(b.date) as month,
b.category,
avg(budgetAmt) as budgetAmt,
sum(expenseAmt) as expenseAmt
from expense e
join budget b
on (month(b.date) = month(e.date)
and b.category = e.category)
group by year(b.date), month(b.date), b.category
You need to join the two tables on category AND month (using eomonth (end of month) does the trick).
SELECT Year(Eomonth(e.date)) AS year,
Month(Eomonth(e.date)) AS month,
e.category,
Avg(budgetamt) AS budgetAmt,
Sum(expenseamt) AS sumExpenseAmt
FROM expense e
INNER JOIN budget b
ON Eomonth(e.date) = Eomonth(b.date)
AND e.category = b.category
GROUP BY e.category,
Eomonth(e.date);
Fiddle
Alternatively you can use left or right joins based on your need.
select b.Year,b.Month,b.category,Budget, Expenses
from(
Select year(date) [Year] ,month(date) [Month] ,category,sum(budgetAmt) Budget
from budget
group by year(date),month(date),category
) b
Join
(
Select year(date) [Year] ,month(date) [Month] ,category,sum(expenseAmt) Expenses
from expense
group by year(date),month(date),category
) e
on b.Month = e.Month and b.Year = e.Year and b.category = e.category
Another Approach Using EOMONTH Function:
select Year(a.dates) Year ,month(a.dates) Month,a.category,sum(Budget) Budget, sum(Expenses) Expenses
from(
Select EOMONTH(date) dates,category,sum(budgetAmt) Budget
from #budget
group by EOMONTH(date),category
) a
Join
(
Select EOMONTH(date) dates,category,sum(expenseAmt) Expenses
from #expense
group by EOMONTH(date),category
) b
on a.dates = b.dates and a.category = b.category
group by Year(a.dates),month(a.dates),a.category

Query two unbalanced tables

Sum across two tables returns unwanted Sum from one table multiplied by the number of rows in the other
I have 1 table with Actual results recorded by date and the other tables contains planned results recorded by month.
Table 1(Actual)
Date Location Amount
01/01/2019 Loc1 1000
01/02/2019 Loc1 700
01/01/2019 Loc2 7500
01/02/2019 Loc2 1000
02/01/2019 Loc1 500
Table 2(Plan)
Year Month Location Amount
2019 1 Loc1 1500
2019 1 Loc2 8000
2019 2 Loc1 800
I have tried various differed Joins using YEAR(Table1.date) and Month(table1.date) and grouping by
Month(Table1.Date) but I keep running into the same problem where the PlanAmount is multiplied by however many rows in the Actual table...
in the example of loc1 for Month 1 below I get
Year Month Location PlanAmount ActualAmount
2019 1 Loc1 3000 1700
I would like to return the below
Year Month Location PlanAmount ActualAmount
2019 1 Loc1 1500 1700
2019 1 Loc2 8000 8500
2019 2 Loc1 800 500
Thanks in advance for any help
D
You can do this with a full join or union all/group by:
select yyyy, mm, location,
sum(actual_amount) as actual_amount,
sum(plan_amount) as plan_amount
from ((select year(date) as yyyy, month(date) as mm, location,
amount as actual_amount, 0 as plan_amount
from actual
group by year(date) as yyyy, month(date) as mm, location
) union all
(select year, month, location,
0 as actual_amount, amount as plan_amount
from actual
group by year, month, location
)
) ap
group by yyyy, mm, location;
This ensures that you have rows, even when there are no matches in the other table.
To get the required results you need to group the first table on year of date, month of date and location and need to select the columns year, month, location and sum of amount from group after that you need to join that resultant r
SELECT
plans.year,
plans.month,
plans.location,
plans.plan_amount,
grouped_results.actual_amount
FROM plans
INNER JOIN (
SELECT
datepart(year, date) AS year,
datepart(month, date) AS month,
location,
SUM(amount) AS actual_amount
FROM actuals
GROUP BY datepart(year, date), datepart(month, date), location
) as grouped_results
ON
grouped_results.year = plans.year AND
grouped_results.month = plans.month AND
grouped_results.location = plans.location
I think the problem is that you are using sum(PlanTable.Amount) when grouping. Try using max(PlanTable.Amount) instead.
select
p.Year,
p.Month,
p.Location,
sum(a.Amount) as actual_amount,
max(p.Amount) as plan_amount
from
[Plan] p left join Actual a
on year(a.date) = p.year
and month(a.date) = p.Month
and a.Location = p.Location
group by
p.year,
p.month,
p.Location
SQL Fiddle
get year and month from date and use them in join , most dbms has year and month functions you can use according to your DBMS
select year(t1.date) yr,month(t1.date) as monthofyr ,t1.Location,
sum(t1.amount) as actual_amoun,
sum(t2.amount) as planamount
from table1 t1 left join table2 t2 on
month(t1.date)= t2.Month and t1.Location=t2.Location
and year(t1.date)=t2.year
group by year(t1.date) ,month(t1.date),Location

sql completion of a result where it miss some results in the sequence

I have a table with sales information
like this: |product | sales | date|
Most of the time the date are consecutive from 201601 to 201652.
but some times there is a gap ex : no line for 201602 for productA
How can I make an SQL query that will return a result for this gap like this :
productA,4,201601
**productA,0,201602**
productA,5,201603
productA,8,201604
(...)
instead of :
productA,4,201601
productA,5,201603
productA,8,201604
(...)
Of course it will also be some product B,C,...
You do this by using cross join to get all the rows and then left join to pull in the values.
Assuming you have some data for each week:
select p.product, d.date, coalesce(s.sales, 0) as sales
from (select distinct product from sales) p cross join
(select distinct date from sales) d left join
sales s
on s.product = p.product and s.date = d.date;
If you have tables of products and dates, you can use those instead of the subqueries.
Starting from oracle 10g you can use partition outer join to produce desired result:
-- sample of data
with sales(product, sales, dt) as(
select 'product A', 4, 201601 from dual union all
select 'product A', 5, 201603 from dual union all
select 'product A', 8, 201604 from dual
),
-- here we generate months for the year 2016
mnth(dt) as(
select 201600 + level
from dual
connect by level <= 12
)
-- actual query
select s.product
, nvl(s.sales, 0) as sales
, m.dt as date1
from sales s
partition by(s.product)
right join mnth m
on (m.dt = s.dt)
order by s.product, m.dt
Result:
PRODUCT SALES DATE1
--------- ---------- ----------
product A 4 201601
product A 0 201602
product A 5 201603
product A 8 201604
product A 0 201605
product A 0 201606
product A 0 201607
product A 0 201608
product A 0 201609
product A 0 201610
product A 0 201611
product A 0 201612
12 rows selected
based on Gordon's response, I edited so date does not depend on Sales table. Here assumption is that tab will have atleast 52 row, if not please use appropriate data-dictionary table from oracle.
select p.product, d.date, coalesce(s.sales, 0) as sales
from (select distinct product from sales) p cross join
(select 2016 || rownum rn from tab where rownum<=52) d left join
sales s
on s.product = p.product and s.date = d.date;

Syntax to get sum(sales) group by brand but different date

My data is like so
item date country sales
----------------------------------------
motorola 2015-01-01 US 10
motorola 2015-01-01 UK 20
motorola 2015-01-02 US 40
motorola 2015-01-02 UK 80
motorola 2015-01-03 US 120
motorola 2015-01-03 UK 150
motorola 2015-01-04 US 170
motorola 2015-01-04 US 180
I want to get the daily sales delta of motorola from 2 jan 2015 until 4 jan 2015.
So for example
total sales for 1 jan 2015 is 10 (US) + 20(UK) = 30
total sales for 2 jan 2015 is 120 so daily sales delta (sales on date minus D-1) is 90
total sales for 3 jan 2015 is 270 so daily delta is 150
total sales for 4 jan 2015 is 350 so daily delta is 80
I'm expecting the result tuple :
date dailyDelta
2015-01-02 90
2015-01-03 150
2015-01-04 80
What is the syntax to get this? I'm using SQL Server 2012.
Thanks
This is it, the query logic is as simple as it gets, the performance better than inner joins:
select date, sum(sales) - coalesce(lag(sum(sales), 1) over (order by date), 0)
from my_sales
group by date
order by date
Use window function lag. Play with it: http://sqlfiddle.com/#!6/bebab/8 and read about it: https://msdn.microsoft.com/en-us/library/hh231256.aspx
briefly, lag(sum(sales), 1) over (order by date) means "get sum(sales) column of previous record of this query, ordered by date", coalesce(XXX, 0) means "when XXX is null, let's pretend it was a zero"
I don't really see much of a way around a self join. Here is how it would work:
select a.date
, a.item
, sum(a.sales) - sum(b.sales) as DailyDelta
from table a
join table b on a.product = b.product
and b.date = dateadd(day, -1, a.date)
group by a.date
, a.item
Not great performance wise but it will get the job done.
The following query is based on MySQL, but you can tweak it to make it work for SQL server. I hope SQL Server will also support the same
select o.date, t.tsales - sum(sales) delta from test o, (select date, sum(sales) tsales from test group by date) t
where o.date = t.date -1 group by o.date
For the above query I got the following result
"date" "delta"
"2015-01-01" "90"
"2015-01-02" "150"
"2015-01-03" "80"
use a self join
declare #t table (item varchar(10), [date] date,country char(2), sales int)
insert into #t (item, [date],country, sales) values
('motorola','2015-01-01','US',10),
('motorola','2015-01-01','UK',20),
('motorola','2015-01-02','US',40),
('motorola','2015-01-02','UK',80),
('motorola','2015-01-03','US',120),
('motorola','2015-01-03','UK',150),
('motorola','2015-01-04','US',170),
('motorola','2015-01-04','US',180)
;with a as (select row_number() over (order by [date]) r,[date],sum(sales) n from #t group by [date])
select a.[date],a.n-isnull(a1.n,0) dailyDelta from a join a a1 on a.r =a1.r+1
Try this..
SELECT date,
( sum(sales) - LAG(sum(sales),1,0) over (order by sales) ) as dailyDelta
FROM Table
group by date
order by date;