How to rewrite the following into an EXISTS - mysql

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.

Related

Emulating a ROLLUP without that keyword

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

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';

Create 3rd Table SQL with difference of 2 other tables

Afternoon all
I am trying to create a view with 3 tables.
Create view breakdown as
SELECT x.month_
, x.returns_
, y.sales_
, z.profit_
FROM
(SELECT SUM(Return_Value_Eur) AS returns_
, monthname(Return_Date) AS month_
FROM returns
GROUP BY month_) x
INNER JOIN
(SELECT SUM(Total_Price_Eur) AS sales_
, monthname(Order_Date) AS month_
FROM orders
GROUP BY month_) y ON x.month_ = y.month_
INNER JOIN
(SELECT (SUM(Total_Price_Eur)-SUM(Return_Value_Eur)) AS profit_
, monthname(Order_Date) AS month_
FROM returns, orders
GROUP BY month_) z ON y.month_ = z.month_
I have the following code however I'm struggling with the profit table. This table should be a difference between the y.sales and x.returns. However the figures are coming up as follows, the profit column should be showing 394.
Month_ Returns_ Sales_ Profit_
January 108 502 -251
Any help would be greatly appreciated.
Don't mix the old-school comma syntax for the join operation with the JOIN keyword; actually, just ditch the old-school comma syntax altogether for new queries.
Looks like the problem is the cartesian product of the join between returns and orders, the inline view returning "profit".
It's not clear what we are trying to achieve (broken SQL is like that, in that it fails to accurately communicate the actual specification)
It looks to me (and this is just a guess here) is that the intent is to subtract total returns from total sales, by month.
I'd do it like this:
SELECT v.month_
, IFNULL( SUM(v.returns_) ,0) AS returns_
, IFNULL( SUM(v.sales_ ) ,0) AS sales
, IFNULL( SUM(v.sales_ ) ,0)
- IFNULL( SUM(v.returns_) ,0) AS net_
FROM (
SELECT 0.0 AS sales_
, SUM(r.return_value_eur) AS returns_
, MONTHNAME(r.return_date) AS month_
, MONTH(r.return_date) AS month_num
FROM returns r
GROUP
BY MONTHNAME(r.return_date)
, MONTH(r.return_date)
UNION ALL
SELECT SUM(o.total_price_eur) AS sales_
, 0.0 AS returns_
, MONTHNAME(o.order_date) AS month_
, MONTH(o.order_date) AS month_num
FROM orders o
GROUP
BY MONTHNAME(o.order_date)
, MONTH(o.order_date)
) v
GROUP
BY v.month_
, v.month_num
ORDER
BY v.month_num
Note that this query ignores the year, which is a bit odd, mashing together November 2019, with November 2018, 2017, et al.
The example query orders rows by calendar order, January, February, March, ... that's the reason for the additional month_num column.
If there are no rows in returns or orders for a given month, then that month will not appear in the resultset.
To guarantee rows returned for all 12 calendar months, I would use row source that was guaranteed to return me one row for each month. Then do outer joins to aggregated returns by month and aggregated sales by month. (I'd also have the queries work with the integer value for the month, and then translate the integer to a string "month name" as a final output step.)
Something like this:
SELECT MONTHNAME(STR_TO_DATE(c.month_num,'%m')) AS month_
, IFNULL( tr.returns_ ) ,0) AS returns_
, IFNULL( ts.sales_ ) ,0) AS sales_
, IFNULL( ts.sales_ ) ,0)
- IFNULL( tr.returns_ ) ,0) AS net_
FROM ( -- row source for calendar month 1..12
SELECT 1 AS month_num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8
UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
) c
LEFT
JOIN ( -- total returns by month
SELECT SUM(r.return_value_eur) AS returns_
, MONTH(r.return_date) AS month_num
FROM returns r
GROUP
BY MONTH(r.return_date)
) tr
ON tr.month_num = c.month_num
LEFT
JOIN ( -- total orders by month
SELECT SUM(o.total_price_eur) AS sales_
, MONTH(o.order_date) AS month_num
FROM orders o
GROUP
BY MONTH(o.order_date)
) ts
ON ts.month_num = c.month_num
ORDER
BY c.month_num

Query for current salary based on date

id name salary date
1 aaa 10000 10/2/2014
1 aaa 15000 06/04/2015
1 aaa 20000 16/07/2016
1 aaa 25000 10/04/2017
If an employee got salary hike every year,
how can I get an employee's current year salary?
That query should be working fine for every year.
If I execute it in 2013 it displays 2013 year salary, if execute in 2016 it displays 2016 year salary, similarly, for 2017 also it would be display 2017 salary.
Check this SQL Fiddle for MySQL
SELECT
SUM(salary) as salary
FROM salary
WHERE YEAR(STR_TO_DATE(date, '%d/%m/%Y')) = YEAR(CURDATE())
GROUP BY id
in Mysql you can do like this:
select * form tablename where YEAR(now()) = YEAR(fieldname)
Solution for ORACLE
SELECT *
FROM SALARY_TABLE
WHERE NAME = 'whatever_the_name_is'
AND TRUNC(HIKE_DATE,'YYYY') = TRUNC(SYSDATE,'YYYY');
Sample query with dummy data
WITH SALARY_TABLE AS -- Sample data queried from DUAL
(SELECT 1 AS ID,
'aaa' AS name,
10000 AS salary,
TO_DATE('10/2/2014', 'dd/mm/yyyy') AS hike_date
FROM dual
UNION
SELECT 2, 'aaa', 15000, TO_DATE('06/04/2015', 'dd/mm/yyyy') FROM dual
UNION
SELECT 3, 'aaa', 20000, TO_DATE('16/07/2016', 'dd/mm/yyyy') FROM dual
UNION
SELECT 3, 'aaa', 25000, TO_DATE('10/04/2017', 'dd/mm/yyyy') FROM dual
)
SELECT * -- Actual solution
FROM SALARY_TABLE
WHERE NAME = 'aaa'
AND TRUNC(HIKE_DATE,'YYYY') = TRUNC(SYSDATE,'YYYY');

Give a sequential number for each GROUP BY values

I have a temp table with many rows (#TümDATA). I INSERT it's rows into another temp table (#GrupTOT) with GROUP BY clause. But I'm stuck here, I need to give the rows a sequential number after they are GROUPED.
Here is my SQL:
INSERT INTO #GrupTOT(AY, BLK, DRE, TOT)
SELECT J.AY, J.BLK, J.DRE, SUM(J.BORÇ)
FROM #TümDATA J
GROUP BY J.AY, J.BLK, J.DRE
You can try SELECT INTO using an IDENTITY column. This will create the new temporary table #GrupTOT.
Here is a Fiddle example.
SELECT SeqNo = identity(int,1,1), --Identity column
AY = J.AY
BLK = J.BLK,
DRE = J.DRE,
TOT = SUM(J.BORÇ)
INTO #GrupTOT
FROM #TümDATA J
GROUP BY J.AY, J.BLK, J.DRE;
--SELECT * FROM #GrupTOT
You can use ROW_NUMBER to get a number based on an ORDER BY. Or you could add an IDENTITY column to autoincrement a number on insert if that is what you want.
The ROW_NUMBER approach:
WITH CTE AS
(
SELECT Col1, Col2, Col3, Count(*) as [COUNT]
FROM dbo.Table1
GROUP BY Col1, Col2, Col3
)
INSERT INTO dbo.Table2
SELECT RowNum = ROW_NUMBER() OVER ( ORDER BY Col1, Col2, Col3, [COUNT] DESC ),
Col1, Col2, Col3, [COUNT]
FROM CTE
Try this: (Works in Oracle)
WITH ORDERS
AS (SELECT
TO_DATE ( '2013-09-18 00:00:01',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'John' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-19 00:00:01',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'James' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-20 00:00:01',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'John' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-20 00:00:02',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'John' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-20 00:00:03',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'John' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-20 00:00:04',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'John' AS NAME
FROM
DUAL
UNION ALL
SELECT
TO_DATE ( '2013-09-21 16:00:01',
'YYYY-MM-DD HH24:MI:SS' )
AS THE_DATE,
'Jennifer' AS NAME
FROM
DUAL)
SELECT
THE_DATE,
NAME,
ROWNUM
FROM
(SELECT
TRUNC ( THE_DATE ) THE_DATE,
NAME,
COUNT ( 1 )
FROM
ORDERS
GROUP BY
TRUNC ( THE_DATE ),
NAME);
Original Data:
9/18/2013 12:00:01 AM John
9/19/2013 12:00:01 AM James
9/20/2013 12:00:01 AM John
9/20/2013 12:00:02 AM John
9/20/2013 12:00:03 AM John
9/20/2013 12:00:04 AM John
9/21/2013 4:00:01 PM Jennifer
Result:
9/21/2013 Jennifer 1
9/19/2013 James 2
9/20/2013 John 3
9/18/2013 John 4
You can use the ROW_NUMBER() function.
E.g.
WITH q AS (
SELECT field_a
FROM SomeTable
GROUP BY field_a
)
SELECT ROW_NUMBER() OVER(ORDER BY field_a) AS row_num,
field_a
FROM q