Emulating a ROLLUP without that keyword - mysql

In postgres, I can emulate a two dimensional pivot table by doing a query such as:
SELECT ... FROM ...
GROUP BY
ROLLUP(x,y,z), -- ROWS
ROLLUP(a,b,c) -- COLS
As a concrete example in dbfiddle:
However, if a database did not have access to the ROLLUP keyword, how could this be emulated? I know a one-dimensional ROLLUP could be done with a UNION ALL, such as:
SELECT a, b, SUM(c) FROM Input GROUP BY ROLLUP(a, b);
-->
SELECT NULL, NULL, SUM(c) FROM Input UNION ALL
SELECT a, NULL, SUM(c) FROM Input GROUP BY a UNION ALL
SELECT a, b, SUM(c) FROM Input GROUP BY a, b;
But how could this be done without access to the ROLLUP keyword? We an use postgres (or mysql) as the database here, but in your answer just refrain from using the ROLLUP keyword.

You can emulate this also with UNION ALLs since we have:
GROUP BY ROLLUP(a,b)
--> GROUP BY (), (a), (a,b)
So with ROLLUP(a,b), ROLLUP(x,y) we multiply the products together so we get:
GROUP BY ROLLUP(a,b), (x,y)
--> GROUP BY (), a, (a,b) *cross product* (), x, (x,y)
--> (),() + (),x + (),x,y + a,() + a,x + a,x,y + a,b,(), a,b,x, a,b,x,y
So applying it to the original question we would have:
WITH sales (Year, Half, Category, Product, Revenue) AS (
SELECT 2020, 'H1', 'Electronics', 'Phone', 200 UNION ALL
SELECT 2020, 'H1', 'Electronics', 'Computer', 300 UNION ALL
SELECT 2020, 'H2', 'Electronics', 'Phone', 100 UNION ALL
SELECT 2020, 'H2', 'Electronics', 'Computer', 175 UNION ALL
SELECT 2021, 'H1', 'Electronics', 'Phone', 109 UNION ALL
SELECT 2021, 'H1', 'Electronics', 'Computer', 32 UNION ALL
SELECT 2021, 'H2', 'Electronics', 'Phone', 93 UNION ALL
SELECT 2021, 'H2', 'Electronics', 'Computer', 111
)
SELECT SUM(Revenue) AS "sum", NULL AS category, NULL AS product, NULL AS year, NULL AS half FROM Sales GROUP BY (),() UNION ALL
SELECT SUM(Revenue), NULL, NULL, Year, NULL FROM Sales GROUP BY (),Year UNION ALL
SELECT SUM(Revenue), NULL, NULL, Year, Half FROM Sales GROUP BY (),Year,Half UNION ALL
SELECT SUM(Revenue), Category, NULL, NULL, NULL FROM Sales GROUP BY Category,() UNION ALL
SELECT SUM(Revenue), Category, NULL, Year, NULL FROM Sales GROUP BY Category,Year UNION ALL
SELECT SUM(Revenue), Category, NULL, Year, Half FROM Sales GROUP BY Category,Year,half UNION ALL
SELECT SUM(Revenue), Category, Product, NULL, NULL FROM Sales GROUP BY Category,Product,() UNION ALL
SELECT SUM(Revenue), Category, Product, Year, NULL FROM Sales GROUP BY Category,Product,Year UNION ALL
SELECT SUM(Revenue), Category, Product, Year, half FROM Sales GROUP BY Category,Product,Year,Half
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=be3b0d89ad97eaf44b522caf0df9d7da

Related

Generating accurate subtotals on fanout joins

My data model:
The query:
SELECT
ProductSummary.Product,
ProductSummary.ID AS SummaryID,
Transactions.DateOfSale,
Summary.Revenue
FROM
ProductSummary JOIN
Transactions ON (Transactions.ProductID = ProductSummary.ID)
WHERE
Transactions.DateOfSale < '2014-01-10'
The data itself looks fine, however I also want to show a subtotal, and the subtotal of a table should be the amount displayed when that table is not joined.
For example, for subtotaling Revenue the answer should always be what I would get from SELECT SUM(Revenue) FROM Summary (after applying any necessary filters). How to generate that?
One way to do this would be using an analytic function to count the unique rows while totaling, for example:
WITH
ProductSummary (Product, ID, Revenue) AS (
SELECT 'Car', 1, 12 UNION ALL
SELECT 'Phone', 2, 7
),
Transactions (SummaryID, ID, DateOfSale) AS (
SELECT 1, 1, DATE '2014-01-01' UNION ALL
SELECT 1, 2, DATE '2014-01-02' UNION ALL
SELECT 1, 3, DATE '2014-01-03' UNION ALL
SELECT 2, 4, DATE '2014-01-04' UNION ALL
SELECT 1, 5, DATE '2014-01-04' UNION ALL
SELECT 1, 6, DATE '2014-01-04' UNION ALL
SELECT 1, 7, DATE '2020-01-01'
)
SELECT
ProductSummary.Product,
ProductSummary.ID AS SummaryID,
Transactions.DateOfSale,
ProductSummary.Revenue,
IF(
ROW_NUMBER() OVER (PARTITION BY ProductSummary.ID) = 1,
ProductSummary.Revenue,
0
) RevenueUnique
FROM
ProductSummary
JOIN Transactions ON (Transactions.SummaryID=ProductSummary.ID)
WHERE
Transactions.DateOfSale < DATE '2014-01-10';

How to rewrite the following into an EXISTS

I have the following SQL statement that I would like to convert to use an EXISTS. How would this be done?
with Sales as (
select 'Office Supplies' Category , 2014 Year,22593.42 Profit UNION all
select 'Technology', 2014, 21492.83 UNION all
select 'Furniture', 2014, 5457.73 UNION all
select 'Office Supplies', 2015, 25099.53 UNION all
select 'Technology', 2015, 33503.87 UNION all
select 'Furniture', 2015, 50000.00 UNION all
select 'Office Supplies', 2016, 35061.23 UNION all
select 'Technology', 2016, 39773.99 UNION all
select 'Furniture', 2016, 6959.95
)
select Category, Profit - LAG(Profit) OVER (PARTITION BY Category ORDER BY Year) Diff
FROM Sales where 1=1 qualify Diff < 0
In other words, I want the query to be something like:
SELECT * FROM tbl WHERE EXISTS (...)
If LAG exists then you can use a sub-query instead of Teradata's QUALIFY.
SELECT Category, `Year`, Profit
, PreviousProfit
, (Profit - PreviousProfit) AS Diff
FROM
(
SELECT Category, `Year`, Profit
, LAG(Profit) OVER (PARTITION BY Category ORDER BY `Year`) AS PreviousProfit
FROM Sales AS sale
) AS sale
WHERE EXISTS (
SELECT 1
FROM Sales AS sale2
WHERE sale2.Category = sale.Category
AND sale2.Year = sale.Year - 1
AND sale2.Profit > sale.Profit
)
AND PreviousProfit > Profit
The EXISTS isn't really needed then.

MySQL. Select without 1 day

Base: name, rate, dt.
Every day cost is inserted, I need to know the maximum cost without the first day. The first day of all products is different.
With all dates
SELECT `name`, MAX(rate) AS max
FROM `base`
GROUP BY `name`
This is similar to what I want, but not working.
SELECT `name`, MAX(rate) AS max, MIN(dt) AS min_dt
FROM `base`
WHERE `dt` > `min_dt`
GROUP BY `name`
Eample base
skirt, 6, 2018-10-10 00:00:00
skirt, 7, 2018-10-11 00:00:00
cap, 7, 2018-10-11 00:00:00
skirt, 8, 2018-10-12 00:00:00
cap, 6, 2018-10-12 00:00:00
Need
skirt, 8
cap, 6
One approach is to use an inline view to get the min_dt for each name. Then we can join and exclude the minimum date rows
Something like this:
SELECT b.name
, MAX(b.rate) AS `max`
FROM ( SELECT d.name
, MIN(d.dt) AS min_dt
FROM `base` d
GROUP
BY d.name
) m
JOIN `base` b
ON b.dt > m.min_dt
AND b.name = m.name
GROUP
BY b.name
There are other query patterns that will achieve an equivalent result. My preference would to avoid a correlated subquery, but something like this would also return the specified result:
SELECT b.name
, MAX(b.rate) AS `max`
FROM `base` b
WHERE b.dt > ( SELECT MIN(d.dt)
FROM `base` d
WHERE d.name = b.name
)
GROUP
BY b.name
(With both of these query forms, if there is only row in base for a given name, the query will not return a row for that name.)
You should try to exclude the min dt using subqueries:
SELECT name, MAX(rate) AS max
FROM (SELECT name, rate, dt
FROM base B
WHERE dt NOT IN (SELECT MIN(dt) FROM base WHERE name=B.name )) as A
GROUP BY name
Fiddle here

How to get the average number of Item Sold per week in SQL?

I have table of sold item which has following columns like
SoldItemID
SoldID
SoldAmount
DateOfPurchase
DateOfActivation
CreatedDate
How to get the average number of Item sold Per week in SQL?
Any help would be highly appreciated!
I know I am late to the party, but just in case other people are looking, this might work:
select date_part('week', transaction_date) week_of_the_year, item_id,
avg(sold_qty) from your_table group by 1, 2 order by 1
For SQL SERVER
WITH CTE AS(
SELECT
*
FROM (
VALUES
('2018-08-23', 'A', 10),
('2018-08-23', 'B', 10),
('2018-08-24', 'A', 14),
('2018-08-31', 'A', 8),
('2018-08-31', 'B', 10)
) as list (Date_, Item, Amount)
)
select
WKnum, MIN(Date_), MAX(Date_), Item, AVG(Amount)
from(
select
*,
DATEPART(WK, Date_) WKnum
from CTE
) as A
group by WKnum, Item
Try below query: it will work on sql server
select datepart(week,DateOfPurchase),avg(item)
from
(
select DateOfPurchase,count(SoldItemID) item
from tablename group by DateOfPurchase) a
group by datepart(week,DateOfPurchase)

Ranking SUM of Value Column Grouped by Date

I have a query which I would like to add a ranking column. My existing query has three tables as a union query, with a sum of the total order value for that week. This query produces the sum of the total order value for that week, grouped by WeekCommencing, however I am struggling to add a ranking column based on the highest to the lowest total value for that week.
My (Updated) SQLFiddle example is here http://sqlfiddle.com/#!9/f1d43/35
CREATE and INSERT statements:
CREATE TABLE IF NOT EXISTS ORD (
WeekCommencing DATE,
Value DECIMAL(20 , 6 ),
Orders INT(6)
);
CREATE TABLE IF NOT EXISTS REF (
WeekCommencing DATE,
Value DECIMAL(20 , 6 ),
Orders INT(6)
);
CREATE TABLE IF NOT EXISTS SOH (
WeekCommencing DATE,
Value DECIMAL(20 , 6 ),
Orders INT(6)
);
INSERT INTO ORD (WeekCommencing, Value, Orders) VALUES
('2017-07-24',1,1),
('2017-07-31',2,1),
('2017-07-17',3,1);
INSERT INTO REF (WeekCommencing, Value, Orders) VALUES
('2017-07-24',4,1),
('2017-07-17',5,1),
('2017-07-31',6,1);
INSERT INTO SOH (WeekCommencing, Value, Orders) VALUES
('2017-07-17',7,1),
('2017-07-24',8,1),
('2017-07-31',9,1);
My best effort to date:
SELECT
WeekCommencing,
SUM(Value) AS 'TotalValue',
SUM(Orders) AS 'Orders',
#r:=#r+1 As 'Rank'
FROM
(SELECT
WeekCommencing, Value, Orders
FROM
ORD
GROUP BY WeekCommencing UNION ALL SELECT
WeekCommencing, Value, Orders
FROM
REF
GROUP BY WeekCommencing UNION ALL SELECT
WeekCommencing, Value, Orders
FROM
SOH
GROUP BY WeekCommencing) t1,
(SELECT #r:=0) Rank
GROUP BY WeekCommencing DESC;
My attempt currently ranks the order of week commencing, rather than the ranking highest to lowest.
My desired result is
WeekCommencing TotalValue Orders Rank
2017-07-31 17 3 1
2017-07-24 13 3 3
2017-07-17 15 3 2
Thanks is advance
SELECT a.*
, #i:=#i+1 rank
FROM
( SELECT weekcommencing
, SUM(value) totalvalue
, COUNT(*) totalorders
FROM
( SELECT weekcommencing, value, orders FROM ord
UNION ALL
SELECT weekcommencing, value, orders FROM ref
UNION ALL
SELECT weekcommencing, value, orders FROM soh
) x
GROUP
BY weekcommencing
) a
, (SELECT #i:=0) vars
ORDER
BY totalvalue DESC;