How use recursive CTE content for specific child node? - sql-server

I have a recursive CTE query in SQL Server which handles getting the depth of each parent and child values. However I want to get the full depth for only on specific child. For example the data I have looks something like this, I only want to get the results for where c_key is equal to child my initial query below the table gets the results for all links.
I tried to add a where clause in to the unioned select statement, but this just returned the the 0th depth rows.
c_key
p_key
child
teen
teen
adult
adult
old
young
middle
middle
old
WITH cte_data (c_key, p_key, depth) AS
(
SELECT c_key, p_key, 0 AS depth
FROM my_table
WHERE p_key = 'old'
UNION ALL
SELECT c.c_key, c.p_key, o.depth + 1 AS depth
FROM cte_data o
INNER JOIN my_table c ON o.c_key = c.p_key
)
SELECT *
FROM cte_data

Seemingly, you should swap columns for the join condition and change the where search condition according to your task condition.
WITH cte_data (c_key, p_key, depth) AS
(
SELECT c_key, p_key, 0 AS depth
FROM my_table
WHERE c_key = 'child'
UNION ALL
SELECT c.c_key, c.p_key, o.depth + 1 AS depth
FROM cte_data o
INNER JOIN my_table c ON o.p_key = c.c_key
)
SELECT *
FROM cte_data
ORDER by depth

Related

SQL Update query using select statement

I am trying to update a column in a table where the another column matches and selecting the top 1 for that column as the value to update.
Hard to explain, but this is what I wrote:
UPDATE CameraSpecifications AS a
SET a.Variant = (
SELECT TOP 1 GTIN
FROM CameraSpecifcations
WHERE b.ModelGroup = a.ModelGroup )
Hopefully that explains what I am trying to do.
I have a select statement that might also help:
SELECT
(
SELECT TOP 1 b.GTIN
FROM CameraSpecifications AS b
WHERE b.ModelGroup = a.ModelGroup
) AS Gtin,
a.ModelGroup,
COUNT(a.ModelGroup)
FROM CameraSpecifications AS a
GROUP BY a.ModelGroup
We can try doing an update join from CameraSpecifications to a CTE which finds the top GTIN value for each model group. Note carefully that I use an ORDER BY clause in ROW_NUMBER. It makes no sense to use TOP 1 without ORDER BY, so you should at some point update your question and mention TOP 1 with regard to a certain column.
WITH cte AS (
SELECT ModelGroup, GTIN,
ROW_NUMBER() OVER (PARTITION BY ModelGroup ORDER BY some_col) rn
FROM CameraSpecifications
)
UPDATE cs
SET Variant = t.GTIN
FROM CameraSpecifcations cs
INNER JOIN cte t
ON cs.ModelGroup = t.ModelGroup
WHERE
t.rn = 1;

Select 1st matching record from table one that has a match in table two

I'm struggling to achieve what I trying to do, I need to select the 1st record from table 1 that has a match in table 2 and update table 2 with the value.
I have tried numerous ways and have looked at numerous post. But I either can only return 1 record or all records.
My previous attempts are below:
SELECT a.URL,
a.Caption
FROM [EAN].[DBO].[tblHotelImageList] a
WHERE a.EANHotelID IN
(SELECT b.EanHotelId FROM [EAN].[DBO].[tblEanFullTextSearch] b
)
GROUP BY a.URL,
a.Caption;
Above returns all records
SELECT a.Caption,
a.URL
FROM [EAN].[dbo].[tblHotelImageList] a
LEFT JOIN [EAN].[dbo].[tblEanFullTextSearch] b
ON b.EanHotelId = a.EANHotelID
AND b.EanHotelId =
( SELECT TOP 1
a.EANHotelID
FROM [EAN].[dbo].[tblHotelImageList]
WHERE a.EANHotelID = b.EanHotelId
)
;
Above returns all records
The above code at the moment is only trying to retrieve the records, I have not started on trying to update table with records
Instead of left join, use outer apply:
SELECT hil.Caption, hil.URL, . . .
FROM [EAN].[dbo].[tblHotelImageList] hil OUTER APPLY
(SELECT TOP 1 efts.*
FROM [EAN].[dbo].[tblEanFullTextSearch] efts
WHERE efts.EanHotelId = hil.EANHotelID
ORDER BY efts.??
) efts;
Usually, when you want one row, you want the row based on a particular order. That is what the '??' is for.
You can use similar logic for an UPDATE.
Since the mandatory record is in table 2 (tblEanFullTextSearch), make that the driver of your query. This query does what you asked for:
SELECT TOP 1
a.Caption,
a.URL
FROM [EAN].[dbo].[tblEanFullTextSearch] b
INNER JOIN [EAN].[dbo].[tblHotelImageList] a
ON b.EanHotelId = a.EANHotelID
ORDER BY a.EANHotelID;
If you need the first match for all EanHotelId values, you can modify the query like this:
SELECT TOP 1
a.Caption,
a.URL
FROM [EAN].[dbo].[tblEanFullTextSearch] b
INNER JOIN (
SELECT Caption,
URL,
EanHotelId,
ROW_NUMBER() OVER (Order By EanHotelId) as RowID
FROM [EAN].[dbo].[tblHotelImageList]
) a
ON b.EanHotelId = a.EANHotelID
WHERE RowID = 1
ORDER BY a.EANHotelID;
There is probably another criteria for selecting the first match, but it is not in your question.

how do i get hierarchy of data from two different tables?

I have two tables TwitterStatus and twitterstatusDetails,
I have data respectively in following manner.
Here as given in twitterstatus table 4th record with the ts_StatusID "587573444633427968" is parent of twitterstatus_Details table 3rd record, which having td_inReplyTostatusID value is "587573444633427968", for next record td_StatusID of recent child record is parent of twitterstatus table having that child ts_StatusID value in ts_InreplytoStatusID field, so like that it is the hierarchy, so
how can I get the records from both the tables based on child parent relationship?
still any concern then please ask in comment.
Thank you.
I think what you are looking for is recursive CTE to get every nested level.
Hope this get you closer to your goal.
--REPLACE #TwitterStatus and #twitterstatus_Details with your tablename.
;WITH CteTStatus
AS
(
SELECT h.*, d.*, 0 as lvl
FROM #TwitterStatus AS h
INNER JOIN #twitterstatus_Details AS d ON h.id_StatusID = d.td_InreplytoStatusID
WHERE ts_StatusID = '587573444633427968'--WHERE CLAUSE TO GET ROOT PARENT
UNION ALL -- UNION TO DIG INTO CHILD's
SELECT h.*, d.*, lvl + 1 as lvl
FROM #TwitterStatus AS h
INNER JOIN #twitterstatus_Details AS d ON h.id_StatusID = d.td_InreplytoStatusID
INNER JOIN CteTStatus AS Parent ON h.ts_InreplytoStatusID = Parent.td_StatusID
)
SELECT *
FROM CteTStatus

Join the table valued function in the query

I have one table vwuser. I want join this table with the table valued function fnuserrank(userID). So I need to cross apply with table valued function:
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
For each userID it generates multiple records. I only want the last record for each empid that does not have a Rank of Term(inated). How can I do this?
Data:
HistoryID empid Rank MonitorDate
1 A1 E1 2012-8-9
2 A1 E2 2012-9-12
3 A1 Term 2012-10-13
4 A2 E3 2011-10-09
5 A2 TERM 2012-11-9
From this 2nd record and 4th record must be selected.
In SQL Server 2005+ you can use this Common Table Expression (CTE) to determine the latest record by MonitorDate that doesn't have a Rank of 'Term':
WITH EmployeeData AS
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY empId, ORDER BY MonitorDate DESC) AS RowNumber
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
WHERE Rank != 'Term'
)
SELECT *
FROM EmployeeData AS ed
WHERE ed.RowNumber = 1;
Note: The statement before this CTE will need to end in a semi-colon. Because of this, I have seen many people write them like ;WITH EmployeeData AS...
You'll have to play with this. Having trouble mocking your schema on sqlfiddle.
Select bar.*
from
(
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
where rank != 'TERM'
) foo
left join
(
SELECT *
FROM vwuser AS b
CROSS APPLY fnuserrank(b.userid)
where rank != 'TERM'
) bar
on foo.empId = bar.empId
and foo.MonitorDate > bar.MonitorDate
where bar.empid is null
I always need to test out left outers on dates being higher. The way it works is you do a left outer. Every row EXCEPT one per user has row(s) with a higher monitor date. That one row is the one you want. I usually use an example from my code, but i'm on the wrong laptop. to get it working you can select foo., bar. and look at the results and spot the row you want and make the condition correct.
You could also do this, which is easier to remember
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
) foo
join
(
select empid, max(monitordate) maxdate
FROM vwuser AS b
CROSS APPLY fnuserrank(b.userid)
where rank != 'TERM'
) bar
on foo.empid = bar.empid
and foo.monitordate = bar.maxdate
I usually prefer to use set based logic over aggregate functions, but whatever works. You can tweak it also by caching the results of your TVF join into a table variable.
EDIT:
http://www.sqlfiddle.com/#!3/613e4/17 - I mocked up your TVF here. Apparently sqlfiddle didn't like "go".
select foo.*, bar.*
from
(
SELECT f.*
FROM vwuser AS a
join fnuserrank f
on a.empid = f.empid
where rank != 'TERM'
) foo
left join
(
SELECT f1.empid [barempid], f1.monitordate [barmonitordate]
FROM vwuser AS b
join fnuserrank f1
on b.empid = f1.empid
where rank != 'TERM'
) bar
on foo.empId = bar.barempid
and foo.MonitorDate > bar.barmonitordate
where bar.barempid is null

TSQL - While loop within select?

In SQL server
Ok, so I'm working with a database table in which rows can have parent rows, which can then have parent rows of their own. I need to select the root 'row'. I don't know the best way to do this.
There is a field called ParentId, which links the row to the row with that ID. When the ParentId = 0, it is the root row.
This is my query now:
SELECT Releases.Name,WorkLog.WorkLogId
FROM WorkLog,Releases
WHERE
Releases.ReleaseId = WorkLog.ReleaseId
and WorkLogDateTime >= #StartDate
and WorkLogDateTime <= #end
I don't really need the Release Name of the child releases, I want only the root Release Name, so I want to select the result of a While loop like this:
WHILE (ParentReleaseId != 0)
BEGIN
#ReleaseId = ParentReleaseId
END
Select Release.Name
where Release.RealeaseId = #ReleaseId
I know that syntax is horrible, but hopefully I'm giving you an idea of what I'm trying to acheive.
Here is an example, which could be usefull:
This query is getting a lower element of a tree, and searching up to the parent of parents.
Like I have 4 level in my table -> category 7->5, 5->3, 3-> 1. If i give it to the 5 it will find the 1, because this is the top level of the three.
(Changing the last select you can have all of the parents up on the way.)
DECLARE #ID int
SET #ID = 5;
WITH CTE_Table_1
(
ID,
Name,
ParentID
)
AS(
SELECT
ID,
Name,
ParentID
FROM Table_1
WHERE ID = #ID
UNION ALL
SELECT
T.ID,
T.Name,
T.ParentID
FROM Table_1 T
INNER JOIN CTE_Table_1 ON CTE_Table_1.ParentID = T.ID
)
SELECT * FROM CTE_Table_1 WHERE ParentID = 0
something like this
with cte as
(
select id,parent_id from t where t.id=#myStartingValue
union all
select t.id,t.parent_id
from cte
join t on cte.parent_id = t.id where cte.parent_id<>0
)
select *
from cte
join t on cte.id=t.id where cte.parent_id = 0
and with fiddle : http://sqlfiddle.com/#!3/a5fa1/1/0
Using Andras approach, I edited the final select to directly give me the ID of the root release
WITH cte_Releases
(
ReleaseId,
ParentReleaseID
)
AS(
SELECT
ReleaseId,
ParentReleaseID
FROM Releases
Where ReleaseId = 905
UNION ALL
SELECT
R.ReleaseId,
R.ParentReleaseID
FROM Releases R
INNER JOIN cte_Releases ON cte_Releases.ParentReleaseID = R.ReleaseId
)
SELECT max(ReleaseId) as ReleaseId, min(ReleaseId) as RootReleaseId FROM cte_Releases
My problem now is I want to run through all #IDs (905 in that code) and join each record to a result

Resources