Recursive Common Table Expression - sql

I know I am probably going about this the wrong way, but I am trying to understand Recursive CTE's.
I created a simple table
RowNum Type Amount
1 Anch 10
2 Amt 1
3 Amt 2
4 Amt 3
5 Amt 4
The idea was to anchor at the amount 10, the to recursively loop through and remove the amount from the total.
I came up with below
WITH cte_Rec (RowNum, [Type], Amount, Amount2, RT, RN)
AS (SELECT RowNum,
[Type],
Amount,
Amount,
Amount,
RowNum
FROM dbo.tbl_RecursiveCTE
WHERE [Type] = 'Anch'
UNION ALL
SELECT r.RowNum,
r.[Type],
r.Amount,
ct.Amount,
ct.Amount - r.Amount AS RT,
ct.RowNum
FROM dbo.tbl_RecursiveCTE r
INNER JOIN cte_Rec ct
ON ct.RowNum = r.RowNum - 1)
SELECT *
FROM cte_Rec
Which obv does not work.
Any ideas?

Not sure what doesn't work for you and what exactly you really want .....
But something like this should work:
;WITH cte_Rec AS
(
SELECT RowNum, RowType, Amount AS 'Amount', Amount AS 'SumAmt'
FROM dbo.tbl_RecursiveCTE
WHERE RowType = 'Anch'
UNION ALL
SELECT r.RowNum, r.RowType, r.Amount, CAST(ct.SumAmt - r.Amount AS DECIMAL(18,2))
from dbo.tbl_RecursiveCTE r
INNER JOIN cte_Rec ct on ct.RowNum = r.RowNum - 1
)
SELECT *
FROM cte_Rec
I get an output of:
RowNum RowType Amount SumAmt
1 Anch 10.00 10.00
2 Amt 1.00 9.00
3 Amt 2.00 7.00
4 Amt 3.00 4.00
5 Amt 4.00 0.00
The Amount row shows the amount for that specific row, while SumAmt starts with the 10.00 and then consecutively subtracts the other amounts - is that what you're looking for??

Related

is there a function in sql to find second minimum in table, which then used in case

For example, there are two tables, and in one - prices with articles, and another table - checks, some articles in checks, and quantity of article
TABLE checks
checks
art
quantity
1check
1toy
2
1check
1toy
5
1check
1toy
1
1check
2toy
1
1check
4toy
3
2check
2toy
1
2check
1toy
2
TABLE articles
art
price
1toy
2.00
2toy
2.50
3toy
1.50
4toy
6.00
1toy
2.50
1toy
3.00
and i need to count the sum of sales of 1check,where i need to take the second minimum of price if the articles repeat.
for 1toy in 1check the price have to be 2.5.- for sum (quantity*price)
i try to write a code - but i finally confused.
help please
SELECT
a.check,
Sump
FROM
(
SELECT
price2,
Case
WHEN COUNT( a.art ) > 1 THEN
SUM( a.quantity * a.price )
ELSE
SUM( a.quantity * price2 )
END AS sump,
a.art,
a.check
FROM
checks AS a
INNER JOIN
(
SELECT
art,
price,
LEAD( price, 1 ) OVER (
PARTITION BY art
ORDER BY price ASC
) AS price2
FROM
prices
) AS b on a.art = b.art
WHERE
a.quantity > 0
GROUP BY
a.checks,
a.art,
price2
)
WHERE
a.checks = '1check'
You may use the ROW_NUMBER() and COUNT window functions as the following:
SELECT T.checks, T.art, SUM(T.quantity * D.price) sump
FROM
checks T JOIN
(
SELECT art, price,
COUNT(*) OVER (PARTITION BY art) cn,
ROW_NUMBER() OVER (PARTITION BY art ORDER BY price) rn
FROM articles
) D
ON T.art = D.art
WHERE (D.cn = 1 OR D.rn = 2) AND T.checks = '1check'
GROUP BY T.checks, T.art
ORDER BY T.checks, T.art
The WHERE (D.cn = 1 OR D.rn = 2) ensures that the returned price is the second minimum (rn=2) or it's only the existed price (cn=1).
The output according to your provided data:
CHECKS
ART
SUMP
1check
1toy
20
1check
2toy
2.5
1check
4toy
18
See a demo on Oracle 11g.
We use dense_rank() to find the second lowest price in the case count(*) > 1. Then we merge the tables, group by art and total the sales.
with a as (
select art
,price
from
(
select a.*
,dense_rank() over(partition by art order by price) as dns_rnk
,count(*) over(partition by art) as cnt
from articles a
) a
where cnt > 1 and dns_rnk = 2
or cnt = 1
)
select art
,sum(quantity)*price as total
from a left join checks c using(art)
group by art, price, checks
having checks = '1check'
order by art
ART
TOTAL
1toy
20
2toy
2.5
4toy
18
Fiddle

SQL sum grouped by field with all rows

I have this table:
id sale_id price
-------------------
1 1 100
2 1 200
3 2 50
4 3 50
I want this result:
id sale_id price sum(price by sale_id)
------------------------------------------
1 1 100 300
2 1 200 300
3 2 50 50
4 3 50 50
I tried this:
SELECT id, sale_id, price,
(SELECT sum(price) FROM sale_lines GROUP BY sale_id)
FROM sale_lines
But get the error that subquery returns different number of rows.
How can I do it?
I want all the rows of sale_lines table selecting all fields and adding the sum(price) grouped by sale_id.
You can use window function :
sum(price) over (partition by sale_id) as sum
If you want sub-query then you need to correlate them :
SELECT sl.id, sl.sale_id, sl.price,
(SELECT sum(sll.price)
FROM sale_lines sll
WHERE sl.sale_id = sll.sale_id
)
FROM sale_lines sl;
Don't use GROUP BY in the sub-query, make it a co-related sub-query:
SELECT sl1.id, sl1.sale_id, sl1.price,
(SELECT sum(sl2.price) FROM sale_lines sl2 where sl2.sale_id = sl.sale_id) as total
FROM sale_lines sl1
In addition to other approaches, You can use CROSS APPLY and get the sum.
SELECT id, sale_id,price, Price_Sum
FROM YourTable AS ot
CROSS APPLY
(SELECT SUM(price) AS Price_Sum
FROM YourTable
WHERE sale_id = ot.sale_id);
SELECT t1.*,
total_price
FROM `sale_lines` AS t1
JOIN(SELECT Sum(price) AS total_price,
sale_id
FROM sale_lines
GROUP BY sale_id) AS t2
ON t1.sale_id = t2.sale_id

Left Join not returning the missing information - SQL Oracle

One is for cash and the other is for the stage with the following structure
Cash
FileID Cash Date
1 50 03.04.2017
2 100 08.07.2015
3 70 14.09.2018
Stage
FileID Stage Date_of_stage
1 Finished 06.04.2016
1 In Process 08.07.2015
2 Complication 17.08.2018
2 In Process 14.03.2018
Though my tables have many more rows. So I am joining these 2 tables coz I wanna group the cash by the stage using this select:
select fileID, date, cash, max(date_of_stage) as max_date
from (select c.fileID, c.date, c.cash, s.stage, s.date_of_stage
from cash c
inner join stage s
on c.fileID=s.fileID
and s.date_of_stage < c.date
) x
group by fileID, date, cash
I only need max(date_of_stage) because it makes logically sense for our report and this isn't part of the question anyway.
The thing is: when I compare the total cash from cash table and the above select I get a little bit less total sum from the above select than from the cash table ( 7 Million from cash and 6.9 Milliom from the above select). Now I am trying to identify the missing records using a left join:
select *
from (select fileID, date, cash
from cash) x
left join
(select fileID, date, cash, max(date_of_stage) as max_date
from (select c.fileID, c.date, c.cash, s.stage, s.date_of_stage
from cash c
inner join stage s
on c.fileID=s.fileID
and s.date_of_stage < c.date
)
group by fileID, date, cash ) y
on x.fileID=y.fileID
and x.date=y.date
and x.cash=y.cash
where y.fileID is null
But this left join doesn't give out anything so I can't identify and examine the missing records. Any tips what to do?
try like below by chaning the left table
select x.*
(select fileID, date, cash, max(date_of_stage) as max_date
from (select c.fileID, c.date, c.cash, s.stage, s.date_of_stage
from cash c
inner join stage s
on c.fileID=s.fileID
and s.date_of_stage < c.date
)
group by fileID, date, cash ) x left join
(select fileID, date, cash
from cash) y
on x.fileID=y.fileID
and x.date=y.date
and x.cash=y.cash
where y.fileID is null
I think all you need is to do the left outer join in the original query, rather than an inner join, e.g.:
WITH cash AS (SELECT 1 fileid, 50 cash, to_date('03/04/2017', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 2 fileid, 100 cash, to_date('08/07/2015', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 3 fileid, 70 cash, to_date('14/09/2018', 'dd/mm/yyyy') dt FROM dual),
stage AS (SELECT 1 fileid, 'Finished' stage, to_date('06/04/2016', 'dd/mm/yyyy') date_of_stage FROM dual UNION ALL
SELECT 1 fileid, 'In Process' stage, to_date('08/07/2015', 'dd/mm/yyyy') date_of_stage FROM dual UNION ALL
SELECT 2 fileid, 'Complication' stage, to_date('17/08/2018', 'dd/mm/yyyy') date_of_stage FROM dual UNION ALL
SELECT 2 fileid, 'In Process' stage, to_date('14/03/2018', 'dd/mm/yyyy') date_of_stage FROM dual)
SELECT c.fileid,
c.dt,
c.cash,
MAX(s.date_of_stage) max_date
FROM cash c
LEFT OUTER JOIN stage s ON c.fileid = s.fileid AND s.date_of_stage < c.dt
GROUP BY c.fileid,
c.dt,
c.cash;
FILEID DT CASH MAX_DATE
---------- ----------- ---------- -----------
1 03/04/2017 50 06/04/2016
2 08/07/2015 100
3 14/09/2018 70
It is strange. With the data you provided your "checking" query works fine and shows two rows. Here is the dbfiddle demo.
Anyway if you need only to attach max date from second table use simple subquery:
select fileID, date_, cash,
(select max(date_of_stage)
from stage s
where fileid = c.fileid and s.date_of_stage < c.date_) as max_date
from cash c
demo

How do I calculate the ratio of two values within a SQL group?

My input is:
My desired output is:
I am unable to figure out how to calculate the ratio for cash-to-coupons quantities for both rows belonging to that particular item.
Can anyone help me, please?
Example
Select *
,Ratio = convert(decimal(10,2),
sum(case when [Payment_Mode]='Cash' then [Quantity]+0.0 end) over (Partition By [Item])
/sum(case when [Payment_Mode]='Coupons' then [Quantity] end) over (Partition By [Item])
)
From YourTable
Returns
Item Payment_Mode Quantity Ratio
Apples Cash 20 2.00
Apples Coupons 10 2.00
Grapes Cash 45 15.00
Grapes Coupons 3 15.00
Oranges Cash 300 20.00
Oranges Coupons 15 20.00
EDIT - Another Option is with a simple Join and a conditional aggregation
Select A.*
,B.Ratio
From YourTable A
Join (
Select Item
,Ratio = sum(case when [Payment_Mode]='Cash' then [Quantity]+0.0 end) /NullIF(sum(case when [Payment_Mode]='Coupons' then [Quantity] end),0)
From YourTable
Group By Item
) B on A.Item=B.Item
Use max as a window function.
select t.*,
1.0*max(case when payment = 'Cash' then Payment end) over(partition by Item) /
max(case when payment = 'Coupon' then Payment end) over(partition by Item)
from tbl t
Fully formatted:
SELECT ix.Item,
ix.Seq,
ix.Payment_Mode,
ix.Quantity,
ix.PrevQuantity,
ix.NextQuantity,
CASE WHEN ix.PrevQuantity = 0 THEN ix.Quantity/ix.NextQuantity ELSE ix.PrevQuantity/ix.Quantity END [Ratio]
FROM
(
SELECT ROW_NUMBER() OVER
(
PARTITION BY
i.Item
ORDER BY i.Item, i.Payment_Mode
) AS Seq,
i.Item,
i.Payment_Mode,
i.Quantity,
LEAD(Quantity, 1, 0) OVER (PARTITION BY i.Item ORDER BY i.Item ASC) AS NextQuantity,
LAG(Quantity, 1, 0) OVER (PARTITION BY i.Item ORDER BY i.Item ASC) AS PrevQuantity
FROM #ITEMS i
) AS ix

Selecting rows with highest date

I have the following query that throws a result like in the example:
SELECT P.IdArt, P.IdAdr, P.gDate, P.Price
FROM dbo.T_PriceData AS P INNER JOIN
dbo.T_Adr AS A ON P.IdAdr = A.IdAdr INNER JOIN
dbo.T_Stat AS S ON A.IdStat = S.IdStat
GROUP BY P.IdArt, P.IdAdr, P.gDate, P.Price
IdArt IdAdr gDate Price
1 10 01/01/2018 1.25
1 10 02/01/2018 1.17
1 10 03/01/2018 1.18
2 15 01/01/2018 1.03
2 18 10/01/2018 0.12
3 25 12/01/2018 0.98
3 25 28/01/2018 1.99
4 30 15/01/2018 2.55
5 35 08/01/2018 0.11
The final result I want is:
When the IdArt and IdAdr are the same, there should be only one row with the highest date of all rows (CASE IdArt 1)
When IdArt is the same but IdAdr is different, there should be a row with each IdAdr with the highest date for each IdAdr. (CASE IdArt 2)
Price doens't affect anything.
So the final table I would like to have is:
IdArt IdAdr gDate Price
1 10 03/01/2018 1.18
2 15 01/01/2018 1.03
2 18 10/01/2018 0.12
3 25 28/01/2018 1.99
4 30 15/01/2018 2.55
5 35 08/01/2018 0.11
How can I do that?
I tried with a having clausule selecting by MAX(gDate) but, of course, I only get one row with the max date from the whole database.
There are lots of answers out there on how to do this, however, this gets you what you are after:
SELECT TOP 1 WITH TIES
P.IdArt,
P.IdAdr,
P.gDate,
P.Price
FROM dbo.T_PriceData P
--INNER JOIN dbo.T_Adr A ON P.IdAdr = A.IdAdr --You don't reference this in the SELECT or WHERE. Why is it here?
--INNER JOIN dbo.T_Stat S ON A.IdStat = S.IdStat --You don't reference this in the SELECT or WHERE. Why is it here?
ORDER BY ROW_NUMBER() OVER (PARTITION BY P.IdArt, P.IdAdr ORDER BY P.gDate DESC);
Edit: If the JOINs are there to ensure that there are rows in the other tables, then as per the comments I would use EXISTS. If you just use JOIN, and only returning rows from the first table, then you could end up with duplicate rows.
SELECT TOP 1 WITH TIES
P.IdArt,
P.IdAdr,
P.gDate,
P.Price
FROM dbo.T_PriceData P
WHERE EXISTS (SELECT 1
FROM dbo.T_Adr A
WHERE P.IdAdr = A.IdAdr)
AND EXISTS (SELECT 1
FROM dbo.T_Stat S
WHERE A.IdStat = S.IdStat)
ORDER BY ROW_NUMBER() OVER (PARTITION BY P.IdArt, P.IdAdr ORDER BY P.gDate DESC);
You want the highest date for each IdArt/IdAdr combination. Window functions are tempting, but the most efficient method is often a correlated subquery.
Your query is only selecting from T_PriceData, so the rest of the query (the joins and group by) do not seem necessary -- unless the joins are filtering the data which seems unlikely because the joins are to reference tables.
So I would recommend:
SELECT P.IdArt, P.IdAdr, P.gDate, P.Price
FROM dbo.T_PriceData P
WHERE P.gDate = (SELECT MAX(P2.gDate)
FROM dbo.T_PriceData P2
WHERE P2.IdArt = P.IdArt AND
P2.IAdr = P.IdAdr
);
For performance you want indexes on (IdArt, IdAdr, gDate).
You can use ROW_Number():
SELECT
q.IdArt
, q.IdArt
, q.IdADr
, q.gDate
, q.Price
FROM (
SELECT
t.IdArt
, t.IdADr
, t.gDate
, t.Price
, ROW_NUMBER() OVER (PARTITION BY t.IdArt, t.IdADr ORDER BY t.gDate DESC) rn
FROM dbo.T_PriceData t
) q
WHERE q.rn = 1