window function count aggregation - sql

I have two quite complex queries going on here. Both should return per given country (Netherlands in this case) the monthly use over the total of 12 months and the percentage that makes up that month of the total of those 12 months.
But the one where i use count as a windowing function returns one more row than the one where i don't use the count as a windowing function.
The query for the left side picture is:
;WITH MonthUsage AS
(
SELECT customer_id, country_name, [Month], [Year], SUM(ItemsPerMonth)
AS ItemsPerMonth
FROM (
SELECT cs.country_name, c.customer_id, YEAR(mol.date_watched) AS Year, MONTH(mol.date_watched) AS [Month], COUNT(*) AS ItemsPerMonth
FROM Customer c
JOIN Customer_Subscription CS ON C.customer_id = CS.customer_id
JOIN Movie_Order_Line mol ON mol.customer_id = c.customer_id
WHERE (mol.date_watched BETWEEN '2017-07-01' AND '2018-07-01') AND cs.country_name = 'Nederland'
GROUP BY c.customer_id,cs.country_name, YEAR(mol.date_watched),
MONTH(mol.date_watched)
UNION ALL
SELECT cs.country_name, c.customer_id, YEAR(sol.date_watched) AS Year,
MONTH(sol.date_watched), COUNT(*) AS ItemsPerMonth
FROM Customer c
JOIN Customer_Subscription CS ON C.customer_id = CS.customer_id
JOIN Show_Order_Line sol ON sol.customer_id = c.customer_id
WHERE sol.date_watched BETWEEN '2017-07-01' AND '2018-07-01'
GROUP BY c.customer_id, cs.country_name, YEAR(sol.date_watched),
MONTH(sol.date_watched)
) AS MonthItems
WHERE country_name = 'Nederland'
GROUP BY customer_id, country_name, [Month], [Year]
),
Months(MonthNumber) AS
(
SELECT 1
UNION ALL
SELECT MonthNumber + 1
FROM months
WHERE MonthNumber < 12
)
SELECT cmb.[Year], ISNULL(cmb.[Month], m.MonthNumber) AS [Month],
ISNULL(ItemsPerMonth, 0) AS ItemsPerMonth,
ISNULL(FORMAT(((CAST(ItemsPerMonth AS decimal) / CAST((
SELECT SUM(ItemsPerMonth)
FROM MonthUsage) AS decimal
))),'P0'), '0%') AS [PercentageOfTotal]
FROM MonthUsage cmb
JOIN Customer c ON c.customer_id = cmb.customer_id
JOIN Months m ON m.MonthNumber = cmb.[Month]
ORDER BY cmb.[Year] ASC, [Month] ASC
And the query for the right picture is:
;WITH MonthUsage AS (
SELECT *
FROM (
SELECT
YEAR(date_watched) AS [Year],
MONTH(date_watched) AS [Month],
COUNT(order_id) OVER(PARTITION BY CONCAT(YEAR(date_watched), MONTH(date_watched))) AS ItemsPerMonth
FROM Movie_Order_Line mov
JOIN Customer c ON c.customer_id = mov.customer_id
JOIN Customer_Subscription cs ON C.customer_id = cs.customer_id
WHERE date_watched BETWEEN '2017-07-01' AND '2018-07-01' AND cs.country_name = 'Nederland'
UNION ALL
SELECT YEAR(date_watched) AS [Year], MONTH(date_watched) AS [Month], COUNT(order_id) OVER(PARTITION BY CONCAT(YEAR(date_watched), MONTH(date_watched))) AS ItemsPerMonth
FROM Show_Order_Line sol
JOIN Customer c ON c.customer_id = sol.customer_id
JOIN Customer_Subscription cs ON C.customer_id = cs.customer_id
WHERE date_watched BETWEEN '2017-07-01' AND '2018-07-01' AND cs.country_name = 'Nederland'
) AS Combined
GROUP BY [YEAR], [Month], ItemsPerMonth
)
SELECT *, ISNULL(FORMAT(((CAST(ItemsPerMonth AS decimal) / CAST((SELECT
SUM(ItemsPerMonth) FROM MonthUsage) AS decimal))),'P0'), '0%') AS
[PercentageOfTotal]
FROM MonthUsage
ORDER BY [Year] ASC, [Month] ASC
I can't seem to figure out why i'm getting different results. Any help is much appreciated. Thank you in advance for your time.

Related

Add running or cumulative total

I have below query which gives me expected results:
SELECT
total_orders,
quantity,
available_store_credits
FROM
(
SELECT
COUNT(orders.id) as total_orders,
date_trunc('year', confirmed_at) as year,
date_trunc('month', confirmed_at) as month,
SUM( quantity ) as quantity,
FROM
orders
INNER JOIN (
SELECT
orders.id,
sum(quantity) as quantity
FROM
orders
INNER JOIN line_items ON line_items.order_id = orders.id
WHERE
orders.deleted_at IS NULL
AND orders.status IN (
'paid', 'packed', 'in_transit', 'delivered'
)
GROUP BY
orders.id
) as order_quantity
ON order_quantity.id = orders.id
GROUP BY month, year) as orders_transactions
FULL OUTER JOIN
(
SELECT
date_trunc('year', created_at) as year,
date_trunc('month', created_at) as month,
SUM( ROUND( ( CASE WHEN amount_in_cents > 0 THEN amount_in_cents end) / 100, 2 )) AS store_credit_given,
SUM( ROUND( amount_in_cents / 100, 2 )) AS available_store_credits
FROM
store_credit_transactions
GROUP BY month, year
) as store_credit_results
ON orders_transactions.month = store_credit_results.month
I want to add one more column beside available_store_credits which will calculate running total of available_store_credits.
These are my trials, but none are working:
Attempt #1
SELECT
total_orders,
quantity,
available_store_credits,
cum_amt
FROM
(
SELECT
COUNT(orders.id) as total_orders,
date_trunc('year', confirmed_at) as year,
date_trunc('month', confirmed_at) as month,
SUM( quantity ) as quantity,
FROM
orders
INNER JOIN (
SELECT
orders.id,
sum(quantity) as quantity
FROM
orders
INNER JOIN line_items ON line_items.order_id = orders.id
WHERE
orders.deleted_at IS NULL
AND orders.status IN (
'paid', 'packed', 'in_transit', 'delivered'
)
GROUP BY
orders.id
) as order_quantity
ON order_quantity.id = orders.id
GROUP BY month, year) as orders_transactions
FULL OUTER JOIN
(
SELECT
date_trunc('year', created_at) as year,
date_trunc('month', created_at) as month,
SUM( ROUND( ( CASE WHEN amount_in_cents > 0 THEN amount_in_cents end) / 100, 2 )) AS store_credit_given,
SUM( ROUND( amount_in_cents / 100, 2 )) AS available_store_credits
SUM( amount_in_cents ) OVER (ORDER BY date_trunc('month', created_at), date_trunc('year', created_at)) AS cum_amt
FROM
store_credit_transactions
GROUP BY month, year
) as store_credit_results
ON orders_transactions.month = store_credit_results.month
Attempt #2
SELECT
total_orders,
quantity,
available_store_credits,
running_tot
FROM
(
SELECT
COUNT(orders.id) as total_orders,
date_trunc('year', confirmed_at) as year,
date_trunc('month', confirmed_at) as month,
FROM
orders
INNER JOIN (
SELECT
orders.id,
sum(quantity) as quantity
FROM
orders
INNER JOIN line_items ON line_items.order_id = orders.id
WHERE
orders.deleted_at IS NULL
AND orders.status IN (
'paid', 'packed', 'in_transit', 'delivered'
)
GROUP BY
orders.id
) as order_quantity
ON order_quantity.id = orders.id
GROUP BY month, year) as orders_transactions
FULL OUTER JOIN
(
SELECT
date_trunc('year', created_at) as year,
date_trunc('month', created_at) as month,
SUM( ROUND( amount_in_cents / 100, 2 )) AS available_store_credits,
SUM (available_store_creds) as running_tot
FROM
store_credit_transactions
INNER JOIN (
SELECT t0.id,
(
SELECT SUM( ROUND( amount_in_cents / 100, 2 )) as running_total
FROM store_credit_transactions as t1
WHERE date_trunc('month', t1.created_at) <= date_trunc('month', t0.created_at)
) AS available_store_creds
FROM store_credit_transactions AS t0
) as results
ON results.id = store_credit_transactions.id
GROUP BY month, year
) as store_credit_results
ON orders_transactions.month = store_credit_results.month
Making some assumptions about the undisclosed table definition and Postgres version (assuming current Postgres 14), this should do it:
SELECT total_orders, quantity, available_store_credits
, sum(available_store_credits) OVER (ORDER BY month) AS cum_amt -- HERE!!
FROM (
SELECT date_trunc('month', confirmed_at) AS month
, count(*) AS total_orders
, sum(quantity) AS quantity
FROM (
SELECT o.id, o.confirmed_at, sum(quantity) AS quantity
FROM orders o
JOIN line_items l ON l.order_id = o.id
WHERE o.deleted_at IS NULL
AND o.status IN ('paid', 'packed', 'in_transit', 'delivered')
GROUP BY 1
) o
GROUP BY 1
) orders_transactions
FULL JOIN (
SELECT date_trunc('month', created_at) AS month
, round(sum(amount_in_cents) FILTER (WHERE amount_in_cents > 0) / 100, 2) AS store_credit_given
, round(sum(amount_in_cents) / 100, 2) AS available_store_credits
FROM store_credit_transactions
GROUP BY 1
) store_credit_results USING (month)
Assuming you want the running sum to show up in every row and order of the date.
First, I simplified and removed some cruft:
date_trunc('year', confirmed_at) as year, was 100 % redundant noise in your query. I removed it.
As was another join to orders. Removed that, too. Assuming orders.id is defined as PK, we can further simplify. See:
PostgreSQL - GROUP BY clause
Use the superior aggregate FILTER. See:
Aggregate columns with additional (distinct) filters
Simplified a couple of other minor bits.

T-SQL query to summarize sales for ALL YEARS: with total per month per year, and cumulative monthly amounts

I need to create a Sales Report that shows all years sales per month, and cumulative sales.
The database table is simple:
Transactions
(
ID INT,
TransactionDate DATETIME,
SalesAmount MONEY
)
I want the results to look similar to ExcelSheet below (I am showing only 2017/2018 amounts, but actual query needs to return results for all available years according to TransactionDate)
This is aggregation and a cumulative sum:
select year(TransactionDate), month(TransactionDate),
sum(SalesAmount),
sum(sum(SalesAmount)) over (partition by year(TransactionDate) order by min(TransactionDate))
from Transactions
group by year(TransactionDate), month(TransactionDate)
order by year(TransactionDate), month(TransactionDate);
Try it:
With Q
as
(
Select DatePart(yyyy,TransactionDate) 'Year',DatePart(m,TransactionDate) 'Month', sum(SalesAmount) 'Sales'
From Transactions
Group by DatePart(yyyy,TransactionDate),DatePart(m,TransactionDate)
)
Select q.Year,q.Month,q.sales,( Select sum(q1.Sales)
From Q q1
Where q1.Year=q.Year
And q1.Month <= q.Month
) 'Cumulative Sale'
From Q q
Order by q.Year,q.Month
Try this:
with cte as
(
select year(TransactionDate) as Year, month(TransactionDate) as Month, SalesAmount
)
select a.Year, a.Month, a.SalesAmount, sum(b.SalesAmount) as cumulativeSalesAmount
from Transactions a inner join Transactions b on a.STORE_ID = b.STORE_ID and a.Year = b.Year and a.Month >= b.Month
group by a.Year, a.Month
order by 1, 2

Join issue on SQL request

My database :
TB_DW_VAB_FLIGHT : ID_TEC_FLIGHT
TB_DW_VAB_SALES : QUANTITY, TRANSACTION_NUMBER, UNIT_SALES_PRICE
I want to have a table with 4 columns as result : CA, QTE, NB_TRANSACTION and NB_VOLS at the same month. ( N-1 )
I tried a SQL request like this :
SELECT
sum(QUANTITY*UNIT_SALES_PRICE) as CA,
sum(QUANTITY) as QTE,
count(distinct TRANSACTION_NUMBER) as NB_TRANSACTION,
count(distinct ID_TEC_FLIGHT) as NB_VOLS
FROM TB_DW_VAB_SALES, TB_DW_VAB_FLIGHT
where to_char(TB_DW_VAB_SALES.FLIGHT_DATE,'MM')=to_char(current_date,'MM')-1 and to_char(TB_DW_VAB_SALES.FLIGHT_DATE,'YYYY')=to_char(current_date,'YYYY') and SALES_TYPE='SALES'
and to_char(TB_DW_VAB_FLIGHT.FLIGHT_DATE,'MM')=to_char(current_date,'MM')-1 and to_char(TB_DW_VAB_FLIGHT.FLIGHT_DATE,'YYYY')=to_char(current_date,'YYYY');
But Oracle can't give me an answer.
Thank you a lot for any help.
Try
with CTE1 as
(
select to_char(FLIGHT_DATE, 'MM-YYYY') as PERIOD,
sum(QUANTITY*UNIT_SALES_PRICE) as CA,
sum(QUANTITY) as QTE,
count(distinct TRANSACTION_NUMBER) as NB_TRANSACTION
from TB_DW_VAB_SALES
where SALES_TYPE = 'SALES'
group by to_char(FLIGHT_DATE, 'MM-YYYY')
)
, CTE2 as
(
select count(distinct ID_TEC_FLIGHT) as NB_VOLS,
to_char(FLIGHT_DATE, 'MM-YYYY') as PERIOD
from TB_DW_VAB_FLIGHT
group by to_char(FLIGHT_DATE, 'MM-YYYY')
)
select CTE1.CA,
CTE1.QTE,
CTE1.NB_TRANSACTION,
CTE2.NB_VOLS
from CTE1
inner join CTE2 on CTE1.PERIOD = CTE2.PERIOD
where CTE1.PERIOD = to_char(add_Months(sysdate,-1),'MM-YYYY')
or if CTE's are not available in your software:
select CTE1.CA,
CTE1.QTE,
CTE1.NB_TRANSACTION,
CTE2.NB_VOLS
from
(
select to_char(FLIGHT_DATE, 'MM-YYYY') as PERIOD,
sum(QUANTITY*UNIT_SALES_PRICE) as CA,
sum(QUANTITY) as QTE,
count(distinct TRANSACTION_NUMBER) as NB_TRANSACTION
from TB_DW_VAB_SALES
where SALES_TYPE = 'SALES'
group by to_char(FLIGHT_DATE, 'MM-YYYY')
) CTE1
inner join
(
select count(distinct ID_TEC_FLIGHT) as NB_VOLS,
to_char(FLIGHT_DATE, 'MM-YYYY') as PERIOD
from TB_DW_VAB_FLIGHT
group by to_char(FLIGHT_DATE, 'MM-YYYY')
) CTE2
on CTE1.PERIOD = CTE2.PERIOD
where CTE1.PERIOD = to_char(add_Months(sysdate,-1),'MM-YYYY')

How to return the most ordered item for each month

I am trying to return the most ordered product per month, of the year 2007. I would like to see the name of the product, how many of them where ordered that month, and the month. I am using the AdventureWorks2012 database. I have tried a few different ways but each time multiple product orders are returned for the same month, instead of the one product that had the most order quantity that month. Sorry if this is not clear. I am trying to test myself so I make up my own questions and try to answer them. If anyone knows a site that have questions and answers like this so I can verify that would be super helpful! Thanks for any help. Here is the farthest I have been able to get with the query.
WITH Ord2007Sum
AS (SELECT sum(od.orderqty) AS sorder,
od.productid,
oh.orderdate,
od.SalesOrderID
FROM Sales.SalesOrderDetail AS od
INNER JOIN
sales.SalesOrderHeader AS oh
ON od.SalesOrderID = oh.SalesOrderID
WHERE year(oh.OrderDate) = 2007
GROUP BY ProductID, oh.OrderDate, od.SalesOrderID)
SELECT max(sorder),
s.productid,
month(h.orderdate) AS morder --, s.salesorderid
FROM Ord2007Sum AS s
INNER JOIN
sales.SalesOrderheader AS h
ON s.OrderDate = h.OrderDate
GROUP BY s.ProductID, month(h.orderdate)
ORDER BY morder;
Make a CTE that groups our products by month and creates a sum
;WITH OrderRows AS
(
SELECT
od.ProductId,
MONTH(oh.OrderDate) SalesMonth,
SUM(od.orderqty) OVER (PARTITION BY od.ProductId, MONTH(oh.OrderDate) ORDER BY oh.OrderDate) ProdMonthSum
FROM SalesOrderDetail AS od
INNER JOIN SalesOrderHeader AS oh
ON od.SalesOrderID = oh.SalesOrderID
WHERE year(oh.OrderDate) = 2007
),
Make a simple numbers table to break out each month of the year
Months AS
(
SELECT 1 AS MonthNum UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8
UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12
)
We query our months table against the data and select the top product for each month based on the sum
SELECT
m.MonthNum,
d.ProductID,
d.ProdMonthSum
FROM Months m
OUTER APPLY
(
SELECT TOP 1 r.ProductID, r.ProdMonthSum
FROM OrderRows r
WHERE r.SalesMonth = m.MonthNum
ORDER BY ProdMonthSum DESC
) d
Your group by statement should not include oh.OrderDate, od.SalesOrderID because this will aggregate your data to the incorrect level. You want the ProductID that was most commonly sold per month so the group by conditions become ProductID, datepart(mm,oh.OrderDate). As Andrew suggested the Row_Number function is useful in this case as it lets you create a key that is ordered by month and sorder and which resets each month. Finally in the outer query limits the results to the first instance (which is the highest quantity)for each month.
WITH Ord2007Sum
AS(
SELECT sum(od.orderqty) AS sorder,
od.productid,
datepart(mm,oh.OrderDate) AS 'Month'
row_number() over (partition by datepart(mm,oh.OrderDate)
Order by datepart(mm,oh.OrderDate)desc, sorder desc) row
FROM Sales.SalesOrderDetail AS od
INNER JOIN
sales.SalesOrderHeader AS oh
ON od.SalesOrderID = oh.SalesOrderID
WHERE datepart(yyyy,oh.OrderDate) = 2007
GROUP BY ProductID, datepart(mm,oh.OrderDate)
)
SELECT productid,
sorder,
[month]
FROM Ord2007Sum
WHERE row =1

MySQL: Returning multiple columns from an in-line subquery

I'm creating an SQL statement that will return a month by month summary on sales.
The summary will list some simple columns for the date, total number of sales and the total value of sales.
However, in addition to these columns, i'd like to include 3 more that will list the months best customer by amount spent. For these columns, I need some kind of inline subquery that can return their ID, Name and the Amount they spent.
My current effort uses an inline SELECT statement, however, from my knowledge on how to implement these, you can only return one column and row per in-line statement.
To get around this with my scenario, I can of course create 3 separate in-line statements, however, besides this seeming impractical, it increases the query time more that necessary.
SELECT
DATE_FORMAT(OrderDate,'%M %Y') AS OrderMonth,
COUNT(OrderID) AS TotalOrders,
SUM(OrderTotal) AS TotalAmount,
(SELECT SUM(OrderTotal) FROM Orders WHERE DATE_FORMAT(OrderDate,'%M %Y') = OrderMonth GROUP BY OrderCustomerFK ORDER BY SUM(OrderTotal) DESC LIMIT 1) AS TotalCustomerAmount,
(SELECT OrderCustomerFK FROM Orders WHERE DATE_FORMAT(OrderDate,'%M %Y') = OrderMonth GROUP BY OrderCustomerFK ORDER BY SUM(OrderTotal) DESC LIMIT 1) AS CustomerID,
(SELECT CustomerName FROM Orders INNER JOIN Customers ON OrderCustomerFK = CustomerID WHERE DATE_FORMAT(OrderDate,'%M %Y') = OrderMonth GROUP BY OrderCustomerFK ORDER BY SUM(OrderTotal) DESC LIMIT 1) AS CustomerName
FROM Orders
GROUP BY DATE_FORMAT(OrderDate,'%m%y')
ORDER BY DATE_FORMAT(OrderDate,'%y%m') DESC
How can i better structure this query?
FULL ANSWER
After some tweaking of Dave Barkers solution, I have a final version for anyone in the future looking for help.
The solution by Dave Barker worked perfectly with the customer details, however, it made the simpler Total Sales and Total Sale Amount columns get some crazy figures.
SELECT
Y.OrderMonth, Y.TotalOrders, Y.TotalAmount,
Z.OrdCustFK, Z.CustCompany, Z.CustOrdTotal, Z.CustSalesTotal
FROM
(SELECT
OrdDate,
DATE_FORMAT(OrdDate,'%M %Y') AS OrderMonth,
COUNT(OrderID) AS TotalOrders,
SUM(OrdGrandTotal) AS TotalAmount
FROM Orders
WHERE OrdConfirmed = 1
GROUP BY DATE_FORMAT(OrdDate,'%m%y')
ORDER BY DATE_FORMAT(OrdDate,'%Y%m') DESC)
Y INNER JOIN
(SELECT
DATE_FORMAT(OrdDate,'%M %Y') AS CustMonth,
OrdCustFK,
CustCompany,
COUNT(OrderID) AS CustOrdTotal,
SUM(OrdGrandTotal) AS CustSalesTotal
FROM Orders INNER JOIN CustomerDetails ON OrdCustFK = CustomerID
WHERE OrdConfirmed = 1
GROUP BY DATE_FORMAT(OrdDate,'%m%y'), OrdCustFK
ORDER BY SUM(OrdGrandTotal) DESC)
Z ON Z.CustMonth = Y.OrderMonth
GROUP BY DATE_FORMAT(OrdDate,'%Y%m')
ORDER BY DATE_FORMAT(OrdDate,'%Y%m') DESC
Move the inline SQL to be a inner join query. So you'd have something like...
SELECT DATE_FORMAT(OrderDate,'%M %Y') AS OrderMonth, COUNT(OrderID) AS TotalOrders, SUM(OrderTotal) AS TotalAmount, Z.OrderCustomerFK, Z.CustomerName, z.OrderTotal as CustomerTotal
FROM Orders
INNER JOIN (SELECT DATE_FORMAT(OrderDate,'%M %Y') as Mon, OrderCustomerFK, CustomerName, SUM(OrderTotal) as OrderTotal
FROM Orders
GROUP BY DATE_FORMAT(OrderDate,'%M %Y'), OrderCustomerFK, CustomerName ORDER BY SUM(OrderTotal) DESC LIMIT 1) Z
ON Z.Mon = DATE_FORMAT(OrderDate,'%M %Y')
GROUP BY DATE_FORMAT(OrderDate,'%m%y'), Z.OrderCustomerFK, Z.CustomerName
ORDER BY DATE_FORMAT(OrderDate,'%y%m') DESC
You can also do something like:
SELECT
a.`y`,
( SELECT #c:=NULL ) AS `temp`,
( SELECT #d:=NULL ) AS `temp`,
( SELECT
CONCAT(#c:=b.`c`, #d:=b.`d`)
FROM `b`
ORDER BY b.`uid`
LIMIT 1 ) AS `temp`,
#c as c,
#d as d
FROM `a`
Give this a shot:
SELECT CONCAT(o.order_month, ' ', o.order_year),
o.total_orders,
o.total_amount,
x.sum_order_total,
x.ordercustomerfk,
x.customername
FROM (SELECT MONTH(t.orderdate) AS order_month,
YEAR(t.orderdate) AS order_year
COUNT(t.orderid) AS total_orders,
SUM(t.ordertotal) AS total_amount
FROM ORDERS t
GROUP BY MONTH(t.orderdate), YEAR(t.orderdate)) o
JOIN (SELECT MONTH(t.orderdate) AS ordermonth,
YEAR(t.orderdate) AS orderyear
SUM(t.ordertotal) 'sum_order_total',
t.ordercustomerfk,
c.customername
FROM ORDERS t
JOIN CUSTOMERS c ON c.customerid = o.ordercustomerfk
GROUP BY t.ordercustomerfk, MONTH(t.orderdate), YEAR(t.orderdate)) x ON x.order_month = o.order_month
AND x.order_year = o.order_year
ORDER BY o.order_year DESC, o.order_month DESC