JOIN, GROUP BY AND SUM in single Query - sql

Invoices Table
invoice_id invoice_date
------------ --------------
1 2013-11-27
2 2013-10-09
3 2013-09-12
Orders Table
order_id invoice_id product quantity total
--------- ---------- --------- --------- -------
1 1 Product 1 100 1000
2 1 Product 2 50 200
3 2 Product 1 40 400
4 3 Product 2 50 200
And i want a single sql query that produces following result
products Month 9 Total Month 10 Total Mont 11 Total
-------- ------------- -------------- -------------
Product 1 0 400 100
Product 2 200 0 200
I have tried the following sql query
SELECT orders.products, DATEPART(Year, invoices.invoice_date) Year, DATEPART(Month, invoices.invoice_date) Month, SUM(orders.total) [Total],
FROM invoices INNER JOIN orders ON invoices.invoice_id=orders.invoice_id
GROUP BY orders.products, DATEPART(Year, invoices.invoice_date), DATEPART(Month, invoices.invoice_date)
But it returns nothing. Is it possible to get this result with single query and what should i do for that ? Thanks

I think you want to use PIVOT here ...
Try this:
WITH tmp
AS
(
SELECT orders.products,
DATEPART(Year, invoices.invoice_date) Year,
DATEPART(Month, invoices.invoice_date) Month,
SUM(orders.total) [Total]
FROM invoices INNER JOIN orders ON invoices.invoice_id = orders.invoice_id
GROUP BY
orders.products,
DATEPART(Year, invoices.invoice_date),
DATEPART(Month, invoices.invoice_date)
)
SELECT products,
ISNULL([9],0) AS Nine, ISNULL([10],0) AS Ten, ISNULL([11],0) as Eleven
FROM tmp
PIVOT
(
SUM([Total])
FOR Month IN
( [9], [10], [11])
) as PVT;
You can edit it here: http://sqlfiddle.com/#!6/6f80f/6

You better use pivot for this. Just remember that you have to list every month explicitly in pivot..for..in clause.
select
*
into #invoices
from (
select 1 as invoice_id, '2013-11-27' as invoice_date union all
select 2,'2013-10-09' union all
select 3,'2013-09-12'
) x
select
*
into #orders
from (
select 1 as order_id,1 as invoice_id,'Product 1' as product,100 as quantity,1000 as total union all
select 2,1,'Product 2',50,200 union all
select 3,2,'Product 1',40,400 union all
select 4,3,'Product 2',50,200
) x
GO
select
Product,
[9], [10], [11]
from (
select
o.product, datepart([month],i.invoice_date) as mon,
o.total
from #invoices i
join #orders o
on i.invoice_id=o.invoice_id
) x
pivot (
sum(total) for mon in ([9],[10],[11])
) p

One way to do it is with Cases:
Select
O.product,
Sum(Case
When DATEPART(M, I.invoice_Date) = 9 Then O.total
Else 0
End) as Month9,
Sum(Case
When DATEPART(M, I.invoice_Date) = 10 Then O.total
Else 0
End) as Month10,
Sum(Case
When DATEPART(M, I.invoice_Date) = 11 Then O.total
Else 0
End) as Month11
From Invoice I
Left join Orders O on I.invoice_id = O.invoice_id
Group by O.product
There is another way using Pivots (depending on your version of SQL).
Select product, [9], [10], [11]
From
(
Select O.Product, O.total, DatePart(M, I.Invoice_Date) as [MonthNum]
From Invoice I
Left join Orders O on I.invoice_id = O.invoice_id
) P
PIVOT
(
Sum(P.total)
For [MonthNum] in ([9], [10], [11])
) as O

Related

What to use in place of union in above query i wrote or more optimize query then my given query without union and union all

I am counting the birthdays , sales , order in all 12 months from customers table in SQL server like these
In Customers table birth_date ,sale_date, order_date are columns of the table
select 1 as ranking,'Birthdays' as Type,[MONTH],TOTAL
from ( select DATENAME(month, birth_date) AS [MONTH],count(*) TOTAL
from customers
group by DATENAME(month, birth_date)
)x
union
select 2 as ranking,'sales' as Type,[MONTH],TOTAL
from ( select DATENAME(month, sale_date) AS [MONTH],count(*) TOTAL
from customers
group by DATENAME(month, sale_date)
)x
union
select 3 as ranking,'Orders' as Type,[MONTH],TOTAL
from ( select DATENAME(month, order_date) AS [MONTH],count(*) TOTAL
from customers
group by DATENAME(month, order_date)
)x
And the output is like these(just dummy data)
ranking
Type
MONTH
TOTAL
1
Birthdays
January
12
1
Birthdays
April
6
1
Birthdays
May
10
2
Sales
Febrary
8
2
Sales
April
14
2
Sales
May
10
3
Orders
June
4
3
Orders
July
3
3
Orders
October
6
3
Orders
December
17
I want to find count of these all these three types without using UNION and UNION ALL, means I want these data by single query statement (or more optimize version of these query)
Another approach is to create a CTE with all available ranking values ​​and use CROSS APPLY for it, as shown below.
WITH ranks(ranking) AS (
SELECT * FROM (VALUES (1), (2), (3)) v(r)
)
SELECT
r.ranking,
CASE WHEN r.ranking = 1 THEN 'Birthdays'
WHEN r.ranking = 2 THEN 'Sales'
WHEN r.ranking = 3 THEN 'Orders'
END AS Type,
DATENAME(month, CASE WHEN r.ranking = 1 THEN c.birth_date
WHEN r.ranking = 2 THEN c.sale_date
WHEN r.ranking = 3 THEN c.order_date
END) AS MONTH,
COUNT(*) AS TOTAL
FROM customers c
CROSS APPLY ranks r
GROUP BY r.ranking,
DATENAME(month, CASE WHEN r.ranking = 1 THEN c.birth_date
WHEN r.ranking = 2 THEN c.sale_date
WHEN r.ranking = 3 THEN c.order_date
END)
ORDER BY r.ranking, MONTH

In SQL, is there a way to show all dates even if the date doesn't have data points?

I have a transaction table t as follows in MS SQL Management Studio:
If I run the following SQL to summarise the transaction:
Select
Format(Transaction_Date, 'MMM-yyyy') as 'Year/Month'
,Customer
,Count(Customer) as SalesCount
From t
Group by Format(Transaction_Date, 'MMM-yyyy'), Customer
Order by Customer, Format(Transaction_Date, 'MMM-yyyy')
I'll get:
However I was asked to add all the months for the year 2019 and if there's no transaction in a certain month then return 0 for the SalesCount column:
I tried to create a month table with all the months in 2019 and left join it with the transaction table, but it still returns the same result with no showing of the months without transactions.
Time table I created:
declare #StartDate date = '2019-01-01';
declare #EndDate date = '2020-01-01';
With cte as (
Select #StartDate AS myDate
Union All
Select Dateadd(Month,1,myDate)
From cte
Where Dateadd(Month,1,myDate) < #EndDate
)
,TimeTable as(
SELECT
year(myDate)
,Datename(Month,myDate)
,Format(myDate,'MMMM-yy') as 'Month-Year'
FROM cte
)
Select
tb.'Month-Year'
t.Format(Transaction_Date, 'MMM-yyyy') as Year/Month
,t.Customer
,t.Count(Customer) as SalesCount
From TimeTable tb
Left Join Transaction t on t.'Month/Year' = tb.'Month-Year'
Group by tb.'Month-Year', Format(Transaction_Date, 'MMM-yyyy'), Customer
Order by Customer, Format(Transaction_Date, 'MMM-yyyy')
Your help will be much appreciated!
You need to generate all the rows for the months. One method uses a recursive CTE. Then rest is then left join and aggregation.
Let me assume you are using SQL Server:
with months as (
select convert(date, '2019-01-01') as mon
union all
select dateadd(month, 1, mon)
from months
where mon < '2019-12-01'
)
select Format(m.mon, 'MMM-yyyy') as year_month,
c.Customer,
count(t.customer) as SalesCount
from months m cross join
(select distinct customer from t) c left join
t
on t.transaction_date >= m.mon and
t.transaction_date < dateadd(month, 1, mon) and
t.customer = c.customer
group by m.mon, c.customer
order by c.ustomer, c.mon ;
Note the other changes to the query:
Year/Month is not a valid column alias.
This orders the rows chronologically. That is usually (always?) preferred over alphabetic sorting of months.
You can use monthYear combination table and with distinct customer list, you can achieve this.
declare #table table(trandate date,customer char(1))
inSert into #table values
('2019-01-03','A'),
('2019-01-17','A'),
('2019-06-03','A'),
('2019-07-03','A'),
('2019-06-03','B'),
('2019-07-03','B');
;with monthYear AS
(
select * from
(
values
('Jan-19')
,('Feb-19')
,('Mar-19')
,('Apr-19')
,('May-19')
,('Jun-19')
,('Jul-19')
,('Aug-19')
,('Sep-19')
,('Oct-19')
,('Nov-19')
,('Dec-19')
) as t(mon)
)
SELECT my.mon,c.customer, isnull(t.salescount,0) as salescount FROM monthYear as my
CROSS JOIN (SELECT distinct customer from #table) as c
OUTER APPLY
(
select format(trandate,'MMM-yy') as MonthYear, count(customer) as salescount
from #table
where customer = c.customer
group by format(trandate,'MMM-yy')
having format(trandate,'MMM-yy') = my.mon
) as t
mon
customer
salescount
Jan-19
A
2
Feb-19
A
0
Mar-19
A
0
Apr-19
A
0
May-19
A
0
Jun-19
A
1
Jul-19
A
1
Aug-19
A
0
Sep-19
A
0
Oct-19
A
0
Nov-19
A
0
Dec-19
A
0
Jan-19
B
0
Feb-19
B
0
Mar-19
B
0
Apr-19
B
0
May-19
B
0
Jun-19
B
1
Jul-19
B
1
Aug-19
B
0
Sep-19
B
0
Oct-19
B
0
Nov-19
B
0
Dec-19
B
0

Return a single set of results from two SQL tables

I have two SQL queries that both return the same columns.
The returned column names are month_name, month_number and total.
How can I return a single set of results from the two tables, summing the "total" field?
Query 1
SELECT DISTINCT
DATENAME(MONTH,DATEADD(MONTH,month([date]),-1 )) as month_name,
MONTH([date]) as month_number,
SUM(pur.total_expenses - pur.vat) as 'total'
FROM (select distinct ID, MONTH([date]) as month_number, DATENAME(MONTH,DATEADD(MONTH,month([date]),-1 )) as month_name from [dbo].[purchase_invoices] WHERE YEAR([date]) = '2020' ) m
LEFT JOIN [dbo].[purchase_invoices] pur ON pur.id = m.id
LEFT JOIN [dbo].[dividends] div on month(dividend_date) = month([date]) AND month(dividend_date) = '2020'
WHERE YEAR([date]) = '2020'
GROUP BY m.month_number, MONTH([date]), m.month_name
ORDER BY month_number
Query 2
SELECT DISTINCT
DATENAME(MONTH,DATEADD(MONTH,month([dividend_date]),-1 )) as month_name,
MONTH([dividend_date]) as month_number,
SUM(div.dividend_value) as 'total'
FROM (select distinct ID, MONTH([dividend_date]) as month_number, DATENAME(MONTH,DATEADD(MONTH,month([dividend_date]),-1 )) as month_name from [dbo].[dividends] WHERE YEAR([dividend_date]) = '2020' ) m
LEFT JOIN [dbo].[dividends] div ON div.id = m.id
WHERE YEAR([dividend_date]) = '2020'
GROUP BY m.month_number, MONTH([dividend_date]), m.month_name
ORDER BY month_number
I know I need a JOIN on the month_name or number field, but I am not sure on how to achieve that.
Any help greatly appreciated.
UPDATE (Expected Output)
|---------------------|------------------|------------------|
| month_name | month_number | total |
|---------------------|------------------|------------------|
| Jan | 1 | 4500 |
|---------------------|------------------|------------------|
| Feb | 2 | 6000 |
|---------------------|------------------|------------------|
| ... | ... | ... |
Try using CTEs:
WITH invoice AS (
SELECT DISTINCT
ID,
MONTH([date]) AS month_number,
DATENAME(MONTH, DATEADD(MONTH, MONTH([date]), -1)) AS month_name
FROM[dbo].[purchase_invoices]
WHERE
YEAR([date]) = '2020'
),
q1 AS (
SELECT DISTINCT
DATENAME(MONTH, DATEADD(MONTH, MONTH([date]), -1)) AS month_name,
MONTH([date]) AS month_number,
SUM(pur.total_expenses - pur.vat) AS 'total'
FROM invoice as m
LEFT JOIN[dbo].[purchase_invoices] pur
ON pur.id = m.id
LEFT JOIN[dbo].[dividends] DIV
ON MONTH(dividend_date) = MONTH([date])
AND MONTH(dividend_date) = '2020'
WHERE
YEAR([date]) = '2020'
GROUP BY
m.month_number,
MONTH([date]),
m.month_name
ORDER BY
month_number
),
dividend AS (
SELECT DISTINCT
ID,
MONTH([dividend_date]) AS month_number,
DATENAME(MONTH, DATEADD(MONTH, MONTH([dividend_date]), -1)) AS month_name
FROM[dbo].[dividends]
WHERE
YEAR([dividend_date]) = '2020'
),
q2 AS (
SELECT DISTINCT
DATENAME(MONTH, DATEADD(MONTH, MONTH([dividend_date]), -1)) AS month_name,
MONTH([dividend_date]) AS month_number,
SUM(DIV.dividend_value) AS 'total'
FROM dividend as m
LEFT JOIN[dbo].[dividends] DIV
ON DIV.id = m.id
WHERE
YEAR([dividend_date]) = '2020'
GROUP BY
m.month_number,
MONTH([dividend_date]),
m.month_name
ORDER BY
month_number
)
SELECT
month_name,
month_number,
q1.total + q2.total AS total
FROM q1
LEFT JOIN q2
ON q1.month_name = q2.month_name
AND q1.month_number = q2.month_number
You can use union all:
select month_name, month_number, sum(total)
from ((<query1 here>) union all
(<query2 here>)
) q
group by month_name, month_number;

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;

SQL Server : calculate monthly total sales incl empty months

I'm trying to calculate the total sales of a product in a month, but I would like it to include any "empty" months (with no sales) and only select the latest 12 months.
This is my code so far.
declare
#ProductNo int
set #ProductNo = 1234
SELECT
YEAR(o.OrderDate) as 'Year', MONTH(o.OrderDate) as 'Month', sum(Amount) as 'Units sold',[ProductNo]
FROM [OrderLine] ol
inner join [Order] o on ol.OrderNo = o.OrderNo
where ProductNo = #ProductNo
Group by ProductNo, YEAR(o.OrderDate), Month(o.OrderDate)
Order by ProductNo, YEAR(o.OrderDate), Month(o.OrderDate)
This returns
Year Month Units sold
2011 6 2
2011 10 1
2011 11 1
2012 2 1
But I would like it to return.
Year Month Units sold
2011 4 0
2011 5 0
2011 6 2
2011 7 0
2011 8 0
2011 9 0
2011 10 1
2011 11 1
2011 12 0
2012 1 0
2012 2 2
2012 3 0
I'm using SQL Server 2008 R2 Sp1
I've done before I know that you have calendar table. I've used master.dbo.spt_values to generate last twelve consecutive months (including current).
declare #ProductNo int
set #ProductNo = 1234
select MONTH(d.date), YEAR(d.date), isnull(t.amnt, 0) as [Units sold] from (
SELECT
YEAR(o.OrderDate) as 'Year',
MONTH(o.OrderDate) as 'Month',
sum(Amount) as amnt,
[ProductNo]
FROM [OrderLine] ol
inner join [Order] o on ol.OrderNo = o.OrderNo
where ProductNo = #ProductNo
group by ProductNo, YEAR(o.OrderDate), Month(o.OrderDate)
) t
right join (
select dateadd(mm, -number, getdate()) as date
from master.dbo.spt_values
where type = 'p' and number < 12
) d on year(d.date) = t.[year] and month(d.date) = t.[month]
order by YEAR(d.date), MONTH(d.date)
Try:
;with CTE as
(select 0 months_ago union all
select months_ago - 1 months_ago from CTE where months_ago > -11),
month_list as
(select dateadd(MONTH,
months_ago,
dateadd(DAY,
1-datepart(DAY,getdate()),
cast(GETDATE() as DATE))) month_start
from cte)
SELECT YEAR(ml.start_date) as 'Year',
MONTH(ml.start_date) as 'Month',
sum(Amount) as 'Units sold',[ProductNo]
FROM month_list ml
left join [Order] o
on o.OrderDate >= ml.start_date and
o.OrderDate < dateadd(MONTH, 1, ml.start_date)
left join [OrderLine] ol
on ol.OrderNo = o.OrderNo and ProductNo = #ProductNo
Group by ProductNo, YEAR(ml.start_date), Month(ml.start_date)
Order by ProductNo, YEAR(ml.start_date), Month(ml.start_date)