TSQL GROUP BY in recursive CTE - sql-server

Is there a workaround to use GROUP BY inside a looping CTE or there is a workaround?
I need to group resultset of a CTE table and use it in another loop with the same CTE, but i get following error:
GROUP BY, HAVING, or aggregate functions are not allowed in the
recursive part of a recursive common table expression 'cte'.
Here's the query:
WITH cte
AS
(
SELECT
id,
dailyconsumption,
stock/dailyconsumption as cutoff
FROM items
WHERE father IS NULL
UNION ALL
SELECT
i.id,
SUM(father.dailyconsumption*i.num),
MAX(stock)/SUM(father.dailyconsumption*i.num)
FROM cte father
JOIN items i ON father.id=i.father
group by i.id
)
SELECT id, MIN(cutoff)
FROM cte
GROUP BY id
SQL-Fiddle (with sample data)
EDIT... this is the logical problem
I have a set of end-user items (father=NULL) and other sub-items made by a number of other items (field father and field num populated).
I got the dailyconsumption just for the end-user items (I start my cte with "WHERE father IS NULL"), and sub-items's dailyconsumption are calculate by SUM(father.dailyconsumption *item.num).
WITH cte AS(
SELECT
id,
dailyconsumption,
stock/dailyconsumption as cutoff
FROM items
WHERE father IS NULL
UNION ALL
SELECT
i.id,
father.dailyconsumption*i.num
0
FROM cte father
JOIN items i ON father.id=i.father
)
SELECT id, SUM(dailyconsumption)
FROM cte
GROUP BY id
http://sqlfiddle.com/#!3/f4f2a/95
With this valid query I'm going to have all dailyconsumption populated for all items (end-user and sub-items). Please mind that father-son relationship can be more than 1 level deep.
Now i need to calculate the cutoff (for how many days my stock is enought).
For end-use it is very easy and already calculated in first CTE: stock/dailyconsumption.
For sub-items it is a little more complicated:
subitem.stock/subitem.dailyconsumption + MIN(father.cutoff)
where MIN(father.cutoff) is the minimun cutoff from all fathers of this subitem.
This is because i need another group by.
May I need another CTE to loop in the same father-son relationship?
Thank you for your attention and sorry for my English.

;WITH cte AS
(
SELECT id, father,
dailyconsumption,
(stock / dailyconsumption) AS cutoff,
0 AS [Level]
FROM items
WHERE father IS NULL
UNION ALL
SELECT i.id, i.father,
c.dailyconsumption * i.num,
i.stock / (c.dailyconsumption * i.num),
[Level] + 1
FROM cte c JOIN items i ON c.id = i.father
)
SELECT c.id, c.dailyconsumption, c.cutoff AS subItemsCutoff,
MIN(ct.cutoff) OVER(PARTITION BY ct.[Level]) AS fatherCutoff,
(c.cutoff + ISNULL(MIN(ct.cutoff) OVER(PARTITION BY ct.[Level]), 0)) AS Cutoff
FROM cte c LEFT JOIN cte ct ON c.father = ct.id
Demo on SQLFiddle

I recommend using a variable table instead. Declare the table and then insert those records into it. You would need to figure out a way to loop through it on the second insert into command. I got this to get you started:
DECLARE #staging TABLE
(
id INT
,dailyconsumption FLOAT
,cutoff FLOAT
)
INSERT INTO #staging
SELECT
id,
dailyconsumption,
stock/dailyconsumption as cutoff
FROM
items
WHERE
father IS NULL
INSERT INTO #staging
SELECT
i.id,
SUM(father.dailyconsumption*i.num),
MAX(stock)/SUM(father.dailyconsumption*i.num)
FROM
#staging father
JOIN items i
ON father.id=i.father
group by
i.id
SELECT
id
,MIN(cutoff)
FROM
#staging
GROUP BY
id

Related

How to left join onto existing query in SQL?

I am looking to left join another table because there are two columns in that table that I need to add to my query..how can I left join onto my existing query? For example the query I am using is similar to the one below..
select subject, sum(cnt_daily) as cnt,
min(cnt_daily) as min_cnt_daily, max(cnt_daily) as max_cnt_daily
from (
select study_date, subject, count(*) as cnt_daily
from mytable
where study_date >= '2022-01-01'
group by study_date, subject
) t
group by subject
I tried
select *
from mytable
left join table2
on mytable.id= table1.id
order by table1.id;
But i know this isnt right
You may use CTE:
WITH t AS (
select study_date, subject, count(*) as cnt_daily
from mytable
where study_date >= '2022-01-01'
group by study_date, subject
)
select subject, sum(cnt_daily) as cnt,
min(cnt_daily) as min_cnt_daily, max(cnt_daily) as max_cnt_daily
from t
group by subject
This could encourage reuse of the query inside CTE.

SQL Data Hierarchy Mapping

The first data table shows the hierarchy structure and Im trying to show how each level relates to all the children underneath. The output I'm looking for is the second table.
Any pointers of how to do this in T-SQL please?
A recursive CTE is what you use to get this output:
WITH reccte AS
(
/*Recursive Seed (first result set upon which we iterate)*/
SELECT CUSTOMERNO, CUSTOMER_PARENT, HIERARCHY
FROM yourtable
WHERE CUSTOMERNO NOT IN (SELECT CUSTOMER_PARENT FROM yourtable)
UNION ALL
/*Recursive Member - The part that refers to itself that iterates until the join fails*/
SELECT
reccte.CUSTOMERNO, yourtable.CUSTOMER_PARENT, yourtable.HIERARCHY
FROM reccte
INNER JOIN yourtable
ON reccte.CUSTOMER_PARENT = yourtable.CUSTOMERNO
)
/*select from the CTE output*/
SELECT * FROM reccte
UNION ALL
/*Union in those level 0 records (records that aren't a parent themselves*/
SELECT CUSTOMERNO, CUSTOMERNO, 0 FROM yourtable WHERE CUSTOMERNO NOT IN (SELECT CUSTOMER_PARENT FROM yourtable)

Is there any way to sum duplicate rows when deleting duplicates using CTE?

I have a table that contains duplicated ItemId. I am using CTE to remove the duplicate records and keep only single record for each item. I am able to successfully achieve this milestone using following Query:
Create procedure sp_SumSameItems
as
begin
with cte as (select a.Id,a.ItemId,Qty, QtyPrice,
ROW_NUMBER() OVER(PARTITION by ItemId ORDER BY Id) AS rn from tblTest a)
delete x from tblTest x Join cte On x.Id = cte.Id where cte.rn > 1
end
The actual problem is I want to Sum the Qty and QtyPrice before deleting duplicate records. Where should I add Sum function ?
Problem Illustration:
You can't use update with delete statement, you need to update before :
update t
set t.qty = (select sum(t1.qty) from table t1 where t1.itemid = t.itemid);
A CTE is valid for only one statement, so you will need to either run the cte twice, once summing and then deleting or you could put the result of CTE in a temp table and then use the temp table to sum and then delete records in the original table.
At first level, you have to update Qty and QtyPrice after that remove duplicate records.
Given Example:
CREATE PROCEDURE Sp_sumsameitems
AS
BEGIN
WITH cte1
AS (SELECT a.id,
a.itemid,
Sum(qty) Qty,
Sum(qtyprice)QtyPrice,
FROM tbltest a
GROUP BY a.id)
UPDATE x
SET x.qty = c.qty,
x.qtyprice = c.qtyprice
FROM tbltest x
JOIN cte1 c
ON x.id = cte.id
WITH cte
AS (SELECT a.id,
a.itemid,
qty,
qtyprice,
Row_number()
OVER(
partition BY itemid
ORDER BY id) AS rn
FROM tbltest a)
DELETE x
FROM tbltest x
JOIN cte
ON x.id = cte.id
WHERE cte.rn > 1
END

SQL Simple Join with two tables, but one is random

I am stuck with this. I have a simple set-up with two tables. One table is holding emailaddresses one table is holding vouchercodes. I want to join them in a third table, so that each emailaddress has one random vouchercode.
Unfortunatly I am stuck with this as there are no identic Ids to match both values. What I have so far brings no result:
Select
A.Email
B.CouponCode
FROM Emailaddresses as A
JOIN CouponCodes as B
on A.Email = B.CouponCode
A hint would be great as search did not bring me any further yet.
Edit -
Table A (Addresses)
-------------------
Column A | Column B
-------------------------
email1#gmail.com True
email2#gmail.com
email3#gmail.com True
email4#gmail.com
Table B (Voucher)
-------------------
ABCD1234
ABCD5678
ABCD9876
ABCD5432
Table C
-------------------------
column A | column B
-------------------------
email1#gmail.com ABCD1234
email2#gmail.com ABCD5678
email3#gmail.com ABCD9876
email4#gmail.com ABCD5432
Sample Data:
While joining without proper keys is not a good solution, for your case you can try this. (note: not tested, just a quick suggestion)
;with cte_email as (
select row_number() over (order by Email) as rownum, Email
from Emailaddresses
)
;with cte_coupon as (
select row_number() over (order by CouponCode) as rownum, CouponCode
from CouponCodes
)
select a.Email,b.CouponCode
from cte_email a
join cte_coupon b
on a.rownum = b.rownum
You want to randomly join records, one email with one coupon each. So create random row numbers and join on these:
select
e.email,
c.couponcode
from (select t.*, row_number() over (order by newid()) as rn from emailaddresses t) e
join (select t.*, row_number() over (order by newid()) as rn from CouponCodes t) c
on c.rn = e.rn;
Give a row number for both the tables and join it with row number.
Query
;with cte as(
select [rn] = row_number() over(
order by [Column_A]
), *
from [Table_A]
),
cte2 as(
select [rn] = row_number() over(
order by [Column_A]
), *
from [Table_B]
)
select t1.[Column_A] as [Email_Id], t2.[Column_A] as [Coupon]
from cte t1
join cte2 t2
on t1.rn = t2.rn;
Find a demo here

partition by a count of a field

I have a table t1 with two int fields(id,month) and I have populated it with some values.
What I would like to see as an output is, the maximum of (count of id in a month). I have tried the following code and it works fine:
select id,max(freq) as maxfreq from
(select id,month,count(*) as freq
from t1
group by id,month) a
group by id
order by maxfreq desc
The result is:
ID MAXFREQ
1 3
2 3
3 1
4 1
This is fine. How to achieve this using the over partition by clause? And which one is more efficient? In reality my table consists of several thousands of records. So doing a subquery wont be a good idea I guess! Thanks for any help. Here's the fiddle
;WITH tmp AS (select id, row_number() over (partition by id, month order by id) rn
FROM t1)
SELECT t.id, max(tmp.rn) as maxfreq
from t1 t
INNER JOIN tmp ON tmp.id = t.id
GROUP BY t.id
You can try this -
select id,max(freq) as maxfreq from
(select id,row_number() over (partition by id,month ORDER BY id) as freq
from t1
) a
group by id
order by id,maxfreq desc
but from a performance standpoint, I do not see much difference between your original query and this one.
Same solution but with using CTE.
Actually there is no point to forcibly use windowing functions to this issue.
Compare both solutions with plan explorer.
;with c1 as
( select id,month,count(*) as freq
from t1
group by id,month
)
select id, max(freq) as maxfreq
from c1
group by id
order by maxfreq desc;

Resources