How to put a CTE as a subquery - sql

In SQL Server, I have a CTE and a select statement.
The CTE looks like
WITH Recursion AS (
SELECT Value, GroupID, Type
FROM #GroupMembership
WHERE GroupID = #requestingGroupID
UNION ALL
SELECT M.Value, M.GroupID, M.Type
FROM Recursion AS R
INNER JOIN #GroupMembership AS M ON R.Value = M.GroupID AND R.Type = 'Group'
)
SELECT * FROM Recursion
and the select statement is this
select *
from [Right] r
inner join Group on r.groupid = Group.id
inner join (
-- CTE but #requestingGroupID should be Group.id
) C on C.GroupID = Group.id
I want to nest the CTE inside the brackets, but the #requestingGroupID from the CTE should be referencing Group.id from outside the bracket.
When I try to do this, I get syntax errors. Does anyone know how to do it?
Thanks

Without sample data and expected results, it's hard to give a definitive answer.
But basically, you cannot use a recursive CTE as an APPLY (where you push outer references in).
The only way to do this is to put the recursive part into an inline Table Valued Function, then APPLY it to the rest of the query
CREATE OR ALTER FUNCTION GetDescendants (#requestingGroupID int)
RETURNS TABLE AS RETURN
WITH Recursion AS (
SELECT Value, GroupID, Type
FROM GroupMembership
WHERE GroupID = #requestingGroupID
UNION ALL
SELECT M.Value, M.GroupID, M.Type
FROM Recursion AS R
INNER JOIN GroupMembership AS M ON R.Value = M.GroupID AND R.Type = 'Group'
)
SELECT *
FROM Recursion;
select *
from [Right] r
inner join Group g on r.groupid = g.id
CROSS APPLY dbo.GetDescendant ( g.id) C
db<>fiddle
Alternatively, you can re-arrange your query so that the main SELECT is in the anchor part (first part) of the recursion. Admittedly this is not always possible.
WITH Recursion AS (
SELECT r.Id, Value, GroupID, Type
FROM [Right] r
inner join Group g on r.groupid = g.id
JOIN #GroupMembership gm ON gm.GroupID = g.GroupID
UNION ALL
SELECT R.Id, M.Value, M.GroupID, M.Type
FROM Recursion AS R
INNER JOIN #GroupMembership AS M ON R.Value = M.GroupID AND R.Type = 'Group'
)
SELECT *
FROM Recursion
db<>fiddle

Related

A better way to write this TSQL statement

I am looking for a better way to count values from 2 related tables, right now I join the "root" with 2 "loose related tables"
select R.ID
, IsNull(I.[count],0) as [IpWithIncidents]
, IsNull(x.[count],0) as [IpWithOutIncident]
from [dbo].[ISPRange] as r
left outer join (select R.[ID]
, count(distinct i.[CIDR]) AS [count]
from [dbo].[Incidents] as i
join [dbo].[ISPRange] as R on i.[CIDR] between R.[CIDR_FROM] and R.[CIDR_TILL]
group by R.[ID]
) as I on i.[ID]=r.[ID]
left outer join (select R.[ID]
, count(distinct v.[CIDR]) as [count]
from [dbo].[VisitStats] as v
join [dbo].[ISPRange] as R on v.[CIDR] between R.[CIDR_FROM] and R.[CIDR_TILL]
where v.[Incident] = 0
group by R.[ID]
) as x on x.[ID] = R.[ID]
where (R.[ID]= #RangeId or #RangeId is null)
Added link to execution plan
https://1drv.ms/u/s!AqeBLgyZtSGag_ou1PCoBCS7z1iEJw?e=zdMuQC
You should be able to use apply() here and will likely see a more efficient plan.
Without a minimal reproducible example I can't test the following but using outer apply like this should be equivalent:
select r.ID,
IsNull(i.cnt,0) as IpWithIncidents,
IsNull(x.cnt,0) as IpWithOutIncident
from dbo.ISPRange r
outer apply(
select Count(distinct i.cidr) cnt
from dbo.incidents i
where i.id = r.id
and i.cidr between r.cidr_from and r.cidr_till
)i
outer apply(
select Count(distinct v.cidr) cnt
from dbo.VisitStats v
where v.id = r.id
and v.Incidient = 0
and v.cidr between r.cidr_from and r.cidr_till
)x
where R.Id = #RangeId or #RangeId is null

SQL select specific rows from subquery and insert into row_to_json

I have a view for which I have created several subqueries, I'm trying to return one of the subqueries as json using -
row_to_json(err.*) as overall_totals
The subquery -
left join
(
SELECT q.id, SUM(q.total) AS total,
jsonb_agg(jsonb_build_object('count', q.total, 'type', q.name)) AS totals
FROM (
SELECT r.id AS id, e.name, COUNT(de.value_id) AS total
FROM table_c de
JOIN tests.error e
ON e.id = de.value_id
JOIN table_a p
ON de.process_id = p.id
Join table_b r on p.root = r.id
GROUP BY e.name, r.id ) q
GROUP BY q.id
) err on err.id = rs.id
This works fine and returns as json, however, I only want to return total and totals of the "err" sub-query and not q.id. I need the q.id in the sub-query so that I can join the query onto the rest of the view but I don't want it to be stored in overall_totals. How can I get around this? Is it possible to select certain values into row_to_json?
If you are fine with returning jsonb instead of json, then you can use the - operator:
to_jsonb(err.*) - 'id' as overall_totals
You can add more subquery, something like this
SELECT row_to_json(err2.*) as overall_totals
FROM
(
SELECT err.total, err.totals
FROM XXX_TABLE RS
left join
(
SELECT q.id, SUM(q.total) AS total, jsonb_agg(jsonb_build_object('count', q.total, 'type', q.name)) AS totals
FROM
(
SELECT r.id AS id, e.name, COUNT(de.value_id) AS total
FROM table_c de
JOIN tests.error e
ON e.id = de.value_id
JOIN table_a p
ON de.process_id = p.id
Join table_b r on p.root = r.id
GROUP BY e.name, r.id
) q
GROUP BY q.id
) err on err.id = rs.id
) err2

CTE TABLE WITH ORDER BY ERROR MESSAGE SQL SERVER

;WITH myTree AS
(
SELECT
y.User_id, y.user_usercode, y.user_username,
y.user_uplineID,
trans_WinLose, y.User_ID AS sourceID, trans_id,
trans_Rolling, y.User_Level,
lvl1.User_Level AS Level_lvl1, lvl2.User_Level AS Level_lvl2,
y.User_GivenPT, lvl1.User_GivenPT AS GivenPT_lvl1,
lvl2.User_GivenPT AS GivenPT_lvl2, y.User_GivenComm,
lvl1.User_GivenComm AS downline_Comm
FROM
tbl_user y
INNER JOIN
tbl_trans x ON x.trans_Robot_ID = y.User_RobotID
INNER JOIN
tbl_user lvl1 ON y.user_uplineID = lvl1.User_ID
INNER JOIN
tbl_user lvl2 ON lvl1.user_uplineID = lvl2.User_ID
UNION ALL
SELECT
u.User_ID, u.user_usercode, u.user_username, u.user_uplineID,
t.trans_WinLose, t.sourceID AS sourceID, t.trans_Id,
t.trans_Rolling, u.User_Level, t.User_Level, t.Level_lvl1,
u.User_GivenPT, t.User_GivenPT, t.GivenPT_lvl1,
u.User_GivenComm, t.User_GivenComm
FROM
myTree t
INNER JOIN
tbl_user u ON t.user_uplineID = u.User_ID
)
SELECT *
FROM
(SELECT
mytree.*,
(SELECT
CASE
WHEN Level_lvl1 = 7 THEN GivenPT_lvl1
WHEN level_lvl2 = 7 THEN User_GivenPT-GivenPT_Lvl2
ELSE (CASE
WHEN (User_GivenPT-GivenPT_lvl1) > 0
THEN User_GivenPT - GivenPT_lvl1
ELSE 0
END)
END) AS Net_PT
FROM
Mytree
ORDER BY
mytree.trans_ID) AS c
I would like to order by mytree.trans_ID, but I get an error:
Msg 1033, Level 15, State 1, Line 17
The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
How to solve this problem?
Another possibility is to remove the outer query when querying the CTE - it doesn't seem to serve any purpose.
;with myTree as (
select y.User_id,y.user_usercode,y.user_username,y.user_uplineID,trans_WinLose, y.User_ID as sourceID, trans_id,trans_Rolling,y.User_Level,lvl1.User_Level as Level_lvl1,lvl2.User_Level as Level_lvl2,y.User_GivenPT,lvl1.User_GivenPT as GivenPT_lvl1 ,lvl2.User_GivenPT as GivenPT_lvl2,y.User_GivenComm,lvl1.User_GivenComm as downline_Comm from tbl_user y
Inner join tbl_trans x on x.trans_Robot_ID = y.User_RobotID
Inner join tbl_user lvl1 on y.user_uplineID = lvl1.User_ID
Inner join tbl_user lvl2 on lvl1.user_uplineID = lvl2.User_ID
union all
select u.User_ID,u.user_usercode,u.user_username,u.user_uplineID,t.trans_WinLose, t.sourceID as sourceID, t.trans_Id,t.trans_Rolling,u.User_Level,t.User_Level,t.Level_lvl1,u.User_GivenPT,t.User_GivenPT,t.GivenPT_lvl1,u.User_GivenComm,t.User_GivenComm
from myTree t
inner join tbl_user u on t.user_uplineID = u.User_ID
)
SELECT mytree.*,
(SELECT CASE
WHEN Level_lvl1=7 THEN GivenPT_lvl1
WHEN level_lvl2=7 THEN User_GivenPT-GivenPT_Lvl2
ELSE (CASE WHEN (User_GivenPT-GivenPT_lvl1) > 0 THEN User_GivenPT-GivenPT_lvl1 else 0 END)
END) as Net_PT
From Mytree order by mytree.trans_ID
SQL behaves with data in Mathematics Sets manner. In Sets the order of data and elements are meaningless. So when you are dealing with CTE, sub-queries, views, inline table value functions etc, that are used to return a set of data, you are not allowed to order and sort them.
You need to sort data when you need to show them in output or you are using TOP or OFFSET-Fetch or etc commands, where the Order By helps these commands to work as they should be.
You should rewrite your query as:
;with myTree as (
select y.User_id,y.user_usercode,y.user_username,y.user_uplineID,trans_WinLose, y.User_ID as sourceID, trans_id,trans_Rolling,y.User_Level,lvl1.User_Level as Level_lvl1,lvl2.User_Level as Level_lvl2,y.User_GivenPT,lvl1.User_GivenPT as GivenPT_lvl1 ,lvl2.User_GivenPT as GivenPT_lvl2,y.User_GivenComm,lvl1.User_GivenComm as downline_Comm from tbl_user y
Inner join tbl_trans x on x.trans_Robot_ID = y.User_RobotID
Inner join tbl_user lvl1 on y.user_uplineID = lvl1.User_ID
Inner join tbl_user lvl2 on lvl1.user_uplineID = lvl2.User_ID
union all
select u.User_ID,u.user_usercode,u.user_username,u.user_uplineID,t.trans_WinLose, t.sourceID as sourceID, t.trans_Id,t.trans_Rolling,u.User_Level,t.User_Level,t.Level_lvl1,u.User_GivenPT,t.User_GivenPT,t.GivenPT_lvl1,u.User_GivenComm,t.User_GivenComm
from myTree t
inner join tbl_user u on t.user_uplineID = u.User_ID
)select * from (SELECT mytree.*,
(SELECT CASE
WHEN Level_lvl1=7 THEN GivenPT_lvl1
WHEN level_lvl2=7 THEN User_GivenPT-GivenPT_Lvl2
ELSE (CASE WHEN (User_GivenPT-GivenPT_lvl1) > 0 THEN User_GivenPT-GivenPT_lvl1 else 0 END)
END) as Net_PT
From Mytree) as c
order by c.trans_ID
From Mytree order by mytree.trans_ID OFFSET 0 ROWS
change it to this, solved

The multi-part identifier could not be bound (join sub query)

Possible duplicate, but providing no insight to this case:
The multi-part identifier could not be bound
I have a query of the following form:
select l.id, l.foo, r.id, r.foo
from tbl l
inner join storyevents r on l.id = r.id
right join (
select distinct foo from tbl where id= l.id
) tmp on l.foo = tmp.foo
where l.foo = 12345
But i get the following error:
The multi-part identifier "l.id" could not be bound.
in relation to the right join sub query.
Bonus points:
This is an attempt to remove duplicate rows from the inner join based on a single column. Better way to do this?
try this,
SELECT l.id, l.foo, r.id, r.foo
FROM storyevents l
INNER JOIN storyevents r
ON l.id = r.id
RIGHT JOIN
(
SELECT distinct extid, foo
FROM storyevents
) tmp on l.foo = tmp.foo AND
tmp.extid = l.id
where l.foo = 12345
Better way to remove duplicate rows?
select distinct l.id, l.foo, r.id, r.foo
....

Problem joining tables in SQL

SELECT MID, FAD.FirstOpenedDate ,LCD.LastCloseDate
FROM mwMaster.dbo.Merchant M
JOIN (
SELECT MerchID, MIN(moddate) AS FirstOpenedDate
FROM mwMaster.dbo.MerchantStatusHistory
GROUP BY MerchID
) FAD ON FAD.MerchID = M.MerchID
LEFT JOIN (
SELECT MerchID, MAX(moddate) AS LastCloseDate
FROM mwMaster.dbo.MerchantStatusHistory
GROUP BY MerchID
) LCD ON LCD.MerchID = M.MerchID
JOIN (
SELECT merchid ,avg(Transactions) ,avg(Profit)
FROM mwMaster.dbo.ResidualSummary RS
WHERE RS.Date_Processed < LCD.LastCloseDate
GROUP BY Merchid
) R ON R.MerchID = M.MerchID
I am having trouble performing the following join. I have run into this problem before and used temp tables but would like to find out what I am doing wrong. Basically the line that is not working is the 3rd to last. The "< LCD.LastClostDate" says that it cannot be bound. Is it possible to use the value from LCD which I created in a nested query above (in that query I used the M table in a similar way but I didnt run into any issue)? I am thinking becasue the LCD table is dynamically created here it cannot be used in the nested query but this is just my guess.
Any ideas?
On a side note I have also seen people using a CROSS and OVER. Not to farmiliar with how this works but may be applicable here?
I think though haven't tested you can just change your JOIN to a CROSS APPLY in SQL 2005+
SELECT MID, FAD.FirstOpenedDate ,LCD.LastCloseDate
FROM mwMaster.dbo.Merchant M
JOIN (
SELECT MerchID, MIN(moddate) AS FirstOpenedDate
FROM mwMaster.dbo.MerchantStatusHistory
GROUP BY MerchID
) FAD ON FAD.MerchID = M.MerchID
LEFT JOIN (
SELECT MerchID, MAX(moddate) AS LastCloseDate
FROM mwMaster.dbo.MerchantStatusHistory
GROUP BY MerchID
) LCD ON LCD.MerchID = M.MerchID
CROSS APPLY(
SELECT merchid ,avg(Transactions) ,avg(Profit)
FROM mwMaster.dbo.ResidualSummary RS
WHERE RS.Date_Processed < LCD.LastCloseDate
GROUP BY Merchid
) R ON R.MerchID = M.MerchID
But it might be easier to use CTEs
WITH LCD AS (SELECT MerchID, MAX(moddate) AS LastCloseDate
FROM mwMaster.dbo.MerchantStatusHistory
GROUP BY MerchID),
R AS (
SELECT merchid ,avg(Transactions) ,avg(Profit)
FROM mwMaster.dbo.ResidualSummary RS
INNER JOIN LCD on
LCD.MERCHID = RS.MERCHID
WHERE RS.Date_Processed < LCD.LastCloseDate
GROUP BY Merchid
)
SELECT MID, FAD.FirstOpenedDate ,LCD.LastCloseDate
FROM mwMaster.dbo.Merchant M
JOIN (
SELECT MerchID, MIN(moddate) AS FirstOpenedDate
FROM mwMaster.dbo.MerchantStatusHistory
GROUP BY MerchID
) FAD ON FAD.MerchID = M.MerchID
LEFT JOIN LCD ON LCD.MerchID = M.MerchID
LEFT JOIN R ON R.MerchID = M.MerchID
I can't really test this without your data, but here's one way you could do it:
SELECT MID,
MIN(moddate) OVER (PARTITION BY MerchID) as FirstOpenedDate,
MAX(moddate) OVER (PARTITION BY MerchID) as LastCloseDate
FROM mwMaster.dbo.Merchant
HAVING DateProcessed < LastCloseDate