Say I have a table of items representing a tree-like structured data, and I would like to continuously tracing upward until I get to the top node, marked by a parent_id of NULL. What would my MS SQL CTE (common table expression) look like?
For example, if I were to get the path to get to the top from Bender, it would look like
Comedy
Futurama
Bender
Thanks, and here's the sample data:
DECLARE #t Table(id int, description varchar(50), parent_id int)
INSERT INTO #T
SELECT 1, 'Comedy', null UNION
SELECT 2, 'Futurama', 1 UNION
SELECT 3, 'Dr. Zoidberg', 2 UNION
SELECT 4, 'Bender', 2 UNION
SELECT 5, 'Stand-up', 1 UNION
SELECT 6, 'Unfunny', 5 UNION
SELECT 7, 'Dane Cook', 6
it should look like this:
declare #desc varchar(50)
set #desc = 'Bender'
;with Parentage as
(
select * from #t where description = #desc
union all
select t.*
from #t t
inner join Parentage p
on t.id = p.parent_id
)
select * from Parentage
order by id asc --sorts it root-first
Related
Here is my Objects table -
I am trying to write a query that can fetch all objects of type C with their parent of type A. So the query should return like
I am trying to do it using recursion but not getting the desired result. Any help would be appreciated. Thank you.
This gets you what you are after, but if the logic is right for your requirements is a different question:
DECLARE #ObjectID int = 3;
DECLARE #EndType char(1) = 'A';
WITH VTE AS(
SELECT *
FROM (VALUES(1,'A',NULL),
(2,'B',1),
(3,'C',2))V(ObjectID, ObjectType, ParentID)),
rCTE AS(
SELECT V.ObjectID,
V.ObjectType,
V.ParentID,
V.ObjectID AS StartID,
V.ObjectType AS StartType
FROM VTE V
WHERE v.ObjectID = #ObjectID
UNION ALL
SELECT V.ObjectID,
V.ObjectType,
V.ParentID,
r.StartID,
r.StartType
FROM rCTE r
JOIN VTE V ON V.ObjectID = r.ParentID)
SELECT r.StartID AS ObjectID,
r.StartType AS ObjectType,
r.ObjectID AS ParentObjectID,
r.ObjectType AS PArentObjectType
FROM rCTe r
WHERE r.ObjectType = #EndType;
Sample data
DECLARE #Temp AS TABLE (ObjectId INT,ObjectType VARCHAR(2),ParentObjectId INT)
INSERT INTO #Temp
SELECT 1,'A',NUll UNION ALL
SELECT 2,'B',1 UNION ALL
SELECT 3,'C',NULL UNION ALL
SELECT 4,'D',3 UNION ALL
SELECT 5,'E',4 UNION ALL
SELECT 6,'B',3
Sql server Script
;WITH CTE
AS
(
SELECT ObjectId,
ObjectType,
ParentObjectId,
CAST('\'+ CAST(ObjectId AS VARCHAR(MAX))AS VARCHAR(MAX)) AS [ObjectIdHierarchy] ,
CAST('\'+ ObjectType AS VARCHAR(MAX)) AS [Hierarchy]
FROM #Temp
WHERE ParentObjectId IS NULL
UNION ALL
SELECT t.ObjectId,
t.ObjectType,
t.ParentObjectId,
[ObjectIdHierarchy]+'\'+ CAST(t.ObjectId AS VARCHAR(MAX)) AS [ObjectIdHierarchy],
[Hierarchy]+'\'+ t.ObjectType AS [Hierarchy]
FROM CTE c
INNER JOIN #Temp t
ON t.ParentObjectId = c.ObjectId
)
SELECT ObjectId,
ObjectType,
LEFT(RIGHT([ObjectIdHierarchy],LEN([ObjectIdHierarchy])-1),1) AS ParentObjectId,
LEFT(RIGHT([Hierarchy],LEN([Hierarchy])-1),1) AS ParentChildHierarchy
FROM CTE
WHERE ObjectId =1
Result
ObjectId ObjectType ParentObjectId ParentChildHierarchy
------------------------------------------------------------
1 A 1 A
I have 2 tables:-
Table_1
GetID UnitID
1 1,2,3
2 4,5
3 5,6
4 6
Table_2
ID UnitID UserID
1 1 1
1 2 1
1 3 1
1 4 1
1 5 2
1 6 3
I want the 'GetID' based on 'UserID'.
Let me explain you with an example.
For e.g.
I want all the GetID where UserID is 1.
The result set should be 1 and 2. 2 is included because one of the Units of 2 has UserID 1.
I want all the GetID where UserID is 2
The result set should be 2 and 3. 2 is included because one of Units of 2 has UserID 2.
I want to achieve this.
Thank you in Advance.
You can try a query like this:
See live demo
select
distinct userid,getid
from Table_1 t1
join Table_2 t2
on t1.unitId+',' like '%' +cast(t2.unitid as varchar(max))+',%'
and t2.userid=1
The query for this will be relatively ugly, because you made the mistake of storing CSV data in the UnitID column (or maybe someone else did and you are stuck with it).
SELECT DISTINCT
t1.GetID
FROM Table_1 t1
INNER JOIN Table_2 t2
ON ',' + t1.UnitID + ',' LIKE '%,' + CONVERT(varchar(10), t2.UnitID) + ',%'
WHERE
t2.UserID = 1;
Demo
To understand the join trick being used here, for the first row of Table_1 we are comparing ,1,2,3, against other single UnitID values from Table_2, e.g. %,1,%. Hopefully it is clear that my logic would match a single UnitID value in the CSV string in any position, including the first and last.
But a much better long term approach would be to separate those CSV values across separate records. Then, in addition to requiring a much simpler query, you could take advantage of things like indices.
try this:
declare #Table_1 table(GetID INT, UnitId VARCHAR(10))
declare #Table_2 table(ID INT, UnitId INT,UserId INT)
INSERT INTO #Table_1
SELECT 1,'1,2,3'
union
SELECT 2,'4,5'
union
SELECT 3,'5,6'
union
SELECT 4,'6'
INSERT INTO #Table_2
SELECT 1,1,1
union
SELECT 1,2,1
union
SELECT 1,3,1
union
SELECT 1,4,1
union
SELECT 1,5,2
union
SELECT 1,6,3
declare #UserId INT = 2
DECLARE #UnitId VARCHAR(10)
SELECT #UnitId=COALESCE(#UnitId + ',', '') + CAST(UnitId AS VARCHAR(5)) from #Table_2 WHERE UserId=#UserId
select distinct t.GetId
from #Table_1 t
CROSS APPLY [dbo].[Split](UnitId,',') AS AA
CROSS APPLY [dbo].[Split](#UnitId,',') AS BB
WHERE AA.Value=BB.Value
Split Function:
CREATE FUNCTION [dbo].Split(#input AS Varchar(4000) )
RETURNS
#Result TABLE(Value BIGINT)
AS
BEGIN
DECLARE #str VARCHAR(20)
DECLARE #ind Int
IF(#input is not null)
BEGIN
SET #ind = CharIndex(',',#input)
WHILE #ind > 0
BEGIN
SET #str = SUBSTRING(#input,1,#ind-1)
SET #input = SUBSTRING(#input,#ind+1,LEN(#input)-#ind)
INSERT INTO #Result values (#str)
SET #ind = CharIndex(',',#input)
END
SET #str = #input
INSERT INTO #Result values (#str)
END
RETURN
END
I have two tables:
Tbl1 has 2 columns: name and state
Tbl2 has name and state and additional columns about the fields
I am trying to match tbl1 name and state with tbl2 name and state. I have remove all exact matches, but I see that I could match more if I could account for misspelling and name variations by using a scalar function that compares the 2 names and returns an integer showing how close of a match they are (the lower the number returned the better the match).
The issue is that Tbl1 has over 2M records and Tbl2 has over 4M records – it takes about 30sec to just to search one record from Tbl1 in Tbl2.
Is there some way I could arrange the data or query so the search could be completed faster?
Here’s the table structure:
CREATE TABLE Tbl1
(
Id INT NOT NULL IDENTITY( 1, 1 ) PRIMARY KEY,
Name NVARCHAR(255),
[State] VARCHAR(50),
Phone VARCHAR(50),
DoB SMALLDATETIME
)
GO
CREATE INDEX tbl1_Name_indx ON dbo.Tbl1( Name )
GO
CREATE INDEX tbl1_State_indx ON dbo.Tbl1( [State] )
GO
CREATE TABLE Tbl2
(
Id INT NOT NULL IDENTITY( 1, 1 ) PRIMARY KEY,
Name NVARCHAR(255),
[State] VARCHAR(50)
)
GO
CREATE INDEX tbl2_Name_indx ON dbo.Tbl1( Name )
GO
CREATE INDEX tbl2_State_indx ON dbo.Tbl1( [State] )
GO
Here's a sample function that I tested with to try to rule out function complexity:
CREATE FUNCTION [dbo].ScoreHowCloseOfMatch
(
#SearchString VARCHAR(200) ,
#MatchString VARCHAR(200)
)
RETURNS INT
AS
BEGIN
DECLARE #Result INT;
SET #Result = 1;
RETURN #Result;
END;
Here's some sample data:
INSERT INTO Tbl1
SELECT 'Bob Jones', 'WA', '555-333-2222', 'June 10, 1971' UNION
SELECT 'Melcome T Homes', 'CA', '927-333-2222', 'June 10, 1971' UNION
SELECT 'Janet Rengal', 'WA', '555-333-2222', 'June 10, 1971' UNION
SELECT 'Matt Francis', 'TN', '234-333-2222', 'June 10, 1971' UNION
SELECT 'Same Bojen', 'WA', '555-333-2222', 'June 10, 1971' UNION
SELECT 'Frank Tonga', 'NY', '903-333-2222', 'June 10, 1971' UNION
SELECT 'Jill Rogers', 'WA', '555-333-2222', 'June 10, 1971' UNION
SELECT 'Tim Jackson', 'OR', '757-333-2222', 'June 10, 1971'
GO
INSERT INTO Tbl2
SELECT 'BobJonez', 'WA' UNION
SELECT 'Malcome X', 'CA' UNION
SELECT 'Jan Regal', 'WA'
GO
Here's the query:
WITH cte as (
SELECT t1Id = t1.Id ,
t1Name = t1.Name ,
t1State = t1.State,
t2Name = t2.Name ,
t2State = t2.State ,
t2.Phone ,
t2.DoB,
Score = dbo.ScoreHowCloseOfMatch(t1.Name, t2.Name)
FROM dbo.Tbl1 t2
JOIN dbo.Tbl2 t1
ON t1.State = t2.State
)
SELECT *
INTO CompareResult
FROM cte
ORDER BY cte.Score ASC
GO
One possibility would be to add a column with a normalized name used only for matching purposes. You would remove all the white spaces, remove accents, replace first names by abbreviated first names, replace known nicknames by real names etc.
You could even sort the first name and the last name of one person alphabetically in order to allow swapping both.
Then you can simply join the two tables by this normalized name column.
JOIN dbo.Tbl2 t1
ON t1.State = t2.State
You are joining 2Mx4M rows on a max 50 distinct values join criteria. No wonder this is slow. You need to go back to the drawing board and redefine your problem. If you really want to figure out the 'close match' of every body with everybody else in the same state, then be prepared to pay the price...
I want to call a stored procedure in a join statement of the Select Query.
For example,
Select *
from test
left join test1 on GetID(test.id)=test1.id
The idea is to match one to many relationship.
The structure of tables would be
Table: Test
ID Name
1 abc
2 te
Table: Test1
Id TestID Name
1 1 xxx
2 1 yyy
3 1 zzz
4 2 aaa
Stored procedure:
Create procedure GETID
#id int
as
begin
select top 1 id
from test1
where testid = #id
end
You can convert the stored procedure into an inline table-valued function or you can put the query inside an OUTER APPLY:
SELECT *
FROM test t
OUTER APPLY(
SELECT TOP 1 id
FROM test1
WHERE testid = t.testid
)x
Use a scalar function instead.
CREATE FUNCTION GETID
(
#id int
)
RETURNS int
AS
BEGIN
return (select top 1 id from test1 where testid=#id)
END
Or, review methods in post: Get top 1 row of each group
Use cross apply (or outer apply), which executes once on right side of query.
Or, use row_number() over partition to rank the group rows and select based on rank.
declare #test table (id int, name varchar(100))
insert into #test (id, name) values (1, 'abc')
insert into #test (id, name) values (1, 'te')
declare #test1 table (id int, testid int, name varchar(100))
insert into #test1 (id, testid, name) values (1, 1, 'xxx')
insert into #test1 (id, testid, name) values (2, 1, 'yyy')
insert into #test1 (id, testid, name) values (3, 1, 'zzz')
insert into #test1 (id, testid, name) values (4, 2, 'aaa')
Select * from #test t
cross apply (select top 1 * from #test1
where testid = t.id
order by id) -- change group order as needed
as t1
With a MS SQL Stored Procedure I am getting the following error "Invalid Column Name NavigationID".
Can anyone let me know what I am doing incorrectly?
DECLARE #NavigationID INT
SET #NavigationID = 5
CREATE TABLE #tmp (NavigationID int , ParentID int);
INSERT INTO #tmp SELECT NavigationID, ParentID FROM Nav;
WITH Parent AS
(
SELECT NavigationID, ParentID FROM #tmp WHERE NavigationID = #NavigationID
UNION ALL
SELECT t.NavigationID, t.ParentID FROM Parent
INNER JOIN #tmp t ON t.NavigationID = Parent.ParentID
)
SELECT NavigationID FROM ParentID
WHERE NavigationID <> #NavigationID;
With the code you posted you get.
Msg 208, Level 16, State 1, Line 10
Invalid object name 'ParentID'.
Change FROM ParentID to FROM Parent.
You also need a column NavigationID in table Nav.
Try this:
declare #Nav table(NavigationID int, ParentID int)
insert into #Nav
select 1, null union all
select 2, 1 union all
select 3, 1 union all
select 4, 3 union all
select 5, 3 union all
select 6, null union all
select 7, 6
declare #NavigationID int;
set #NavigationID = 5;
with Parent as
(
select NavigationID,
ParentID
from #Nav
where NavigationID = #NavigationID
union all
select t.NavigationID, t.ParentID
from Parent
inner join #Nav t
on t.NavigationID = Parent.ParentID
)
select NavigationID
from Parent
where NavigationID <> #NavigationID;
Result:
NavigationID
------------
3
1
Replace #Nav with whatever table you are using. #Nav is only here so that this code can be copied and tested.