How do I look for matching data in SQL? - sql-server

I have a database table for a todo list application, and i need a way to track tasks which are dependant on other tasks, I already have a table with ID,title, description, IsComplete and a DependsOnTask column, containing the unique identifier for the task another given task is dependant on.
the problem is, when I try the below in SQL it doesn't give any results!
SELECT TOP 1000 [id]
,[title]
,[description]
,[complete]
,[DependsOnTask]
FROM [master].[dbo].[ToDoItems] where ToDoItems.id =ToDoItems.DependsOnTask;
So my question is, is there a way to find all records with a unique identifier matching DependsOnTask?
Thanks in advance :)

You are missing a JOIN:
SELECT tdi.*, dot.*
FROM dbo.ToDoItems tdi JOIN
dbo.ToDoItems dot
ON dot.id = tdi.DependsOnTask;
This returns all tasks where DependsOnTask is not null, along with information from that record.
Notes:
You don't need to use square braces when they are not necessary. They just clutter up queries.
Use table aliases and qualify column names, so you know where columns are coming from.
You need to use an explicit JOIN for references back to the same table.

If you have a hierarchical structure and task could have a parent and that parent is a child to another task, yo can use recursive CTE to find all hierarchy of determined task.
Let me show an example.
You got structure like this:
SELECT *
FROM (VALUES
(1,'Title1','Do some stuff 1', 0, NULL),
(2,'Title2','Do some stuff 2', 0, NULL),
(3,'Title3','Do some stuff 3', 1, 1),
(4,'Title4','Do some stuff 4', 1, 1),
(5,'Title5','Do some stuff 5', 0, 2),
(6,'Title6','Do some stuff 6', 1, 2),
(7,'Title7','Do some stuff 7', 0, 4),
(8,'Title8','Do some stuff 8', 0, NULL)
) as t([id],[title],[description],[complete],[DependsOnTask])
So task 1 has 2 child tasks - 3 and 4. The 4th task got 1 child - 7. You want to get all child tasks of task with id = 1:
DECLARE #taskid int = 1
;WITH cte AS (
SELECT [id]
,[title]
,[description]
,[complete]
,[DependsOnTask]
FROM [ToDoItems]
WHERE [id] = #taskid
UNION ALL
SELECT t.*
FROM [ToDoItems] t
INNER JOIN cte c
ON c.id = t.DependsOnTask
)
SELECT *
FROM cte
Output:
id title description complete DependsOnTask
1 Title1 Do some stuff 1 0 NULL
3 Title3 Do some stuff 3 1 1
4 Title4 Do some stuff 4 1 1
7 Title7 Do some stuff 7 0 4
So if you change last select to:
SELECT #taskid as main,
id,
DependsOnTask
FROM cte
You will get:
main id DependsOnTask
1 1 NULL
1 3 1
1 4 1
1 7 4
So you get all child tasks of Task1.
If you change CTE like this:
;WITH cte AS (
SELECT [id]
,[title]
,[description]
,[complete]
,[DependsOnTask]
,[id] as Parent
FROM [ToDoItems]
WHERE [DependsOnTask] IS NULL
UNION ALL
SELECT t.*,
c.Parent
FROM [ToDoItems] t
INNER JOIN cte c
ON c.id = t.DependsOnTask
)
SELECT Parent,
id,
DependsOnTask
FROM cte
You will got all you need: Parent task, Child tasks and what are they dependent on:
Parent id DependsOnTask
1 1 NULL
2 2 NULL
8 8 NULL
2 5 2
2 6 2
1 3 1
1 4 1
1 7 4

Related

Add a where clause in SQL Server CTE

I have used a SQL Server CTE for selecting data from my self-referencing table whose schema is
CREATE TABLE BaseDomainTable
(
[BaseModelId] INT,
[Comments] NVARCHAR(100) NULL,
[ParentModelId] INT NULL
)
Each ModelId has comments and may or may not have parent ie any model can be a first model and any model can start a new branch of its own by starting from any parent
INSERT INTO BaseDomainTable ([BaseModelId],[Comments],[ParentModelId])
VALUES (1, 'Comments 1', NULL), (2, 'Comments 2', 1),
(3, 'Comments for 3', 2), (4, 'Comments 4', 2)
After this insert, 1 is my base parent, 2 is derived from 1, 3 and 4 are derived from 2.
To get the data in a hierarchical format I have added a cte.
WITH ParentCTECheck (BaseModelId, Comments, ParentModelId) AS
(
SELECT
Parent.BaseModelId, Parent.Comments, Parent.ParentModelId
FROM
BaseDomainTable Parent
WHERE
Parent.ParentModelId IS NULL
AND Parent.Comments IS NOT NULL
UNION ALL
SELECT
Derived.BaseModelId, Derived.Comments, Derived.ParentModelId
FROM
BaseDomainTable Derived
JOIN
ParentCTECheck ON ParentCTECheck.BaseModelId = Derived.ParentModelId
WHERE
Derived.ParentModelId IS NOT NULL
AND Derived.Comments IS NOT NULL
)
SELECT *
FROM ParentCTECheck
AND I am getting this output:
BaseModelId Comments ParentModelId
------------------------------------------
1 Comments 1 NULL
2 Comments 2 1
3 Comments for 3 2
4 Comments 4 2
I want to change it such that if I pass in the BaseModelId as 4 the cte will traverse the loop for 4 and skip all the data related to 3 in this case and return data for 4,2,1. And when I pass 2 it should skip both 3 and 4 and get the CTE will traverse loop of 2 ie 2,1
Is there a way that this can be done?
You have to traverse the tree bottom up:
;WITH ParentCTECheck AS (
-- Anchor query: get leaf node
SELECT BaseModelId, Comments, ParentModelId
FROM BaseDomainTable
WHERE BaseModelId = 4 -- <-- Id of leaf node
UNION ALL
-- Recursive query: go up the tree and get next level nodes.
-- Recursion terminates as soon as the parent node is met.
SELECT bt.BaseModelId, bt.Comments, bt.ParentModelId
FROM BaseDomainTable AS bt
JOIN ParentCTECheck AS ct ON bt.BaseModelId = ct.ParentModelId
)
SELECT *
FROM ParentCTECheck
Demo here

Comparing the length of two similar strings and picking the longest

I am Trying to compare two strings and pick the longest if they are similar, I have managed to pick the longest by using the following code:
SELECT D.RID, ProductID, Product, [Length] FROM (
SELECT RID, MAX([Length]) AS theLength FROM SortData GROUP BY RID)
AS X INNER JOIN SortData AS D ON D.RID = X.RID AND D.[Length] = X.theLength
But I am now trying to make sure that the code only pick the longest string if it is a like the word it is comparing it to, I have attempted the following code in a few ways but I would be grateful if somebody could help me:
SELECT D.RID, D.ProductID, Product, [Length] FROM (
SELECT RID, Product, MAX([Length]) AS theLength FROM SortData GROUP BY RID)
AS X INNER JOIN SortData AS D ON D.RID = X.RID AND D.[Length] = X.theLength WHERE
D.Product LIKE Product
Using this code I get the Following Error:
Msg 8120, Level 16, State 1, Line 3 Column 'SortData.Product' is
invalid in the select list because it is not contained in either an
aggregate function or the GROUP BY clause. Msg 209, Level 16, State 1,
Line 5 Ambiguous column name 'Product'. Msg 209, Level 16, State 1,
Line 2 Ambiguous column name 'Product'.
Example of the Data I would Like to pick:
1 Sam
1 Samantha
2 Oliver
3 Ollie
4 Benjamin
4 Ben
...
I would expect the output list to be like:
1 Samantha
2 Oliver
3 Ollie
4 Benjamin
...
To Clarify what I am trying to do in the context of this example, I am trying to compare the two Names and if the are LIKE (e.g. x.Name LIKE Name) then pick the longest...
As Requested here is further test data:
1 Hydrogen
1 Hydrogen Oxide
1 Carbon Monoxide
2 Carbon
2 Carbon
2 Carbon Dioxide
3 Carbon Monoxide
3 Carbon Dioxide
3 Oxygen
4 Hydrogen Dioxide
Desired Results are as so:
1 Hydrogen Oxide
1 Carbon Monoxide
2 Carbon Dioxide
3 Carbon Monoxide
3 Oxygen
4 Hydrogen Dioxide
Perhaps another option: The WITH TIES clause in concert with Row_Number()
Example
Select Top 1 with ties *
From YourTable
Order By Row_Number() over (Partition by ID Order By Len(Name) desc)
Your query doesn't come close to your sample data and output. So I built this around the sample data provided to demonstrate one way of solving this.
declare #Something table
(
Col1 int
, Col2 varchar(20)
)
insert #Something values
(1, 'Sam')
, (1, 'Samantha')
, (2, 'Oliver')
, (3, 'Ollie')
select x.Col1
, x.Col2
from
(
select *
, RowNum = ROW_NUMBER() over(partition by Col1 order by LEN(Col2) desc)
from #Something
) x
where x.RowNum = 1
---EDIT---
To demonstrate that this code still returns the desired output from your new sample data...
declare #Something table
(
Col1 int
, Col2 varchar(20)
)
insert #Something values
(1, 'Sam')
, (1, 'Samantha')
, (2, 'Oliver')
, (3, 'Ollie')
, (4, 'Benjamin')
, (4, 'Ben')
select x.Col1
, x.Col2
from
(
select *
, RowNum = ROW_NUMBER() over(partition by Col1 order by LEN(Col2) desc)
from #Something
) x
where x.RowNum = 1
This returns:
1 Samantha
2 Oliver
3 Ollie
4 Benjamin
Since you claim this still doesn't work you need to provide an example of how or why this doesn't work. You keep mentioning LIKE but have not explained or demonstrated how that comes into play here. Help me understand the problem and I can help you find a solution.
I Ended up figuring it out and using the following code:
SELECT D.RID, ProductID, D.Product, [Length] FROM
(
SELECT RID, MAX([Length]) AS theLength
FROM SortData GROUP BY RID
) AS X
INNER JOIN SortData AS D ON D.RID = X.RID AND D.[Length] = X.theLength
WHERE D.Product LIKE Product
GO

SQL Server skip duplicate relations (parent child) in recursion

I have a tree, where specific node in tree can appear in another node in tree. (2 in my example):
1
/ \
2 3
/ \ \
4 5 6
\
2
/ \
4 5
Notice 2 is duplicated. First under 1, and second under 6.
My recursion is:
with cte (ParentId, ChildId, Field1, Field2) AS (
select BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2
from BillOfMaterials BOM
WHERE ParentId=x
UNION ALL
SELECT BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 FROM BillOfMaterials BOM
JOIN cte on BOM.ParentId = cte.ChildId
)
select * from cte;
But the problem is that in result relation 2-4 and 2-5 is duplicated (first from relation 1-2 and second from relation 6-2):
ParentId ChildId OtherFields
1 2
1 3
2 4 /*from 1-2*/
2 5 /*from 1-2*/
3 6
6 2
2 4 /*from 6-2*/
2 5 /*from 6-2*/
Is there any way, to skip visiting duplicated relationships? I do no see any logic why should recursion run over rows that are already in result. It would be faster. Something like that:
with cte (ParentId, ChildId, Field1, Field2) AS (
select BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2
from BillOfMaterials BOM
WHERE ParentId=x
UNION ALL
SELECT BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 FROM BillOfMaterials BOM
JOIN cte on BOM.ParentId = cte.ChildId
------> WHERE (select count(*) FROM SoFarCollectedResult WHERE ParentId=BOM.ParentId AND ChildId=BOM.ChildId ) = 0
)
select * from cte;
I found this thread, but it is 8 years old.
I am using SQL server 2016.
If this is not possible, then my question is how can I remove duplicates from final result, but check distinct only on ParentId and ChildId columns?
Edited:
Expected result is:
ParentId ChildId OtherFields
1 2
1 3
2 4
2 5
3 6
6 2
You can, with adding to 2 little tricks to the SQL.
But you need an extra Id column with a sequential number.
For example via an identity, or a datetime field that shows when the record was inserted.
For the simple reason that as far the database is concerned, there is no order in the records as they were inserted, unless you got a column that indicates that order.
Trick 1) Join the CTE record only to Id's that are higher. Because if they were lower then those are the duplicates you don't want to join.
Trick 2) Use the window function Row_number to get only those that are nearest to the Id the recursion started from
Example:
declare #BillOfMaterials table (Id int identity(1,1) primary key, ParentId int, ChildId int, Field1 varchar(8), Field2 varchar(8));
insert into #BillOfMaterials (ParentId, ChildId, Field1, Field2) values
(1,2,'A','1-2'),
(1,3,'B','1-3'),
(2,4,'C','2-4'), -- from 1-2
(2,5,'D','2-5'), -- from 1-2
(3,6,'E','3-6'),
(6,2,'F','6-2'),
(2,4,'G','2-4'), -- from 6-2
(2,5,'H','2-5'); -- from 6-2
;with cte AS
(
select Id as BaseId, 0 as Level, BOM.*
from #BillOfMaterials BOM
WHERE ParentId in (1)
UNION ALL
SELECT CTE.BaseId, CTE.Level + 1, BOM.*
FROM cte
JOIN #BillOfMaterials BOM on (BOM.ParentId = cte.ChildId and BOM.Id > CTE.Id)
)
select ParentId, ChildId, Field1, Field2
from (
select *
--, row_number() over (partition by BaseId, ParentId, ChildId order by Id) as RNbase
, row_number() over (partition by ParentId, ChildId order by Id) as RN
from cte
) q
where RN = 1
order by ParentId, ChildId;
Result:
ParentId ChildId Field1 Field2
-------- ------- ------ ------
1 2 A 1-2
1 3 B 1-3
2 4 C 2-4
2 5 D 2-5
3 6 E 3-6
6 2 F 6-2
Anyway, as a sidenote, normally a Parent-Child relation table is used differently.
More often it's just a table with unique Parent-Child combinations that are foreign keys to another table where that Id is a primary key. So that the other fields are kept in that other table.
Change your last query from:
select * from cte;
To:
select * from cte group by ParentId, ChildId;
This will essentially take what you have right now, but go one step further and remove rows that have already appeared, which would take care of your duplicate problem. Just be sure that all * returns here is ParentId and ChildId, should it be returning other columns you will need to either add them to the GROUP BY or apply some sort of aggregator to it so that it can still group (max, min, count...).
Should you have more rows that you can't aggregate or group on, you could write the query as such:
select * from cte where ID in (select MAX(ID) from cte group by ParentId, ChildId);
Where ID would be your primary table id for cte. This would take the maximum id when rows matched, which would normally be your latest entry, if you want the earliest entry just change MAX() to MIN().

Displaying sorted hierarchy rows in SQL server?

Assuming I have this table : ( c is a child of parent p)
c p
------
40 0
2 3
2 40
3 1
7 2
1 0
Where (0 means root) — I want the order of select to be displayed as :
c b
------
1 0
3 1
2 3
40 0
2 40
7 2
That's becuase we have 2 roots (1,40) and 1 < 40.
So we start at 1 and then display below it - all it's descendants.
Then we get to 40. same logic again.
Question:
How can I do it ?
I've succeeded to display it recursively + finding level of hierarchy*(not sure if it helps though)*
WITH cte(c, p) AS (
SELECT 40, 0 UNION ALL
SELECT 2,3 UNION ALL
SELECT 2,40 UNION ALL
SELECT 3,1 UNION ALL
SELECT 7,2 UNION ALL
SELECT 1,0
) , cte2 AS(
SELECT c,
p,
PLevel = 1
FROM cte
WHERE p = 0
UNION ALL
SELECT cte.c,
cte.p,
PLevel = cte2.PLevel + 1
FROM cte
INNER JOIN cte2
ON cte2.c = cte.p
)
SELECT *
FROM cte2
Full SQL fiddle
You have almost done it. Just add a rank to identify each group and then sort the data on it.
Also, as you are working with more complex hierarchy we need to change the [level] value. In is now not a number, put the full path of the current element to its parent. Where \ means parent. For example the following string:
\1\5\4\1
represents the hierarchy below:
1
--> 5
--> 4
--> 1
I get the idea from hierarchyid type. You may want to consider storing hierarchies using it, as it has handy build-in functions for working with such structures.
Here is full working example with the new data:
DECLARE #DataSource TABLE
(
[c] TINYINT
,[p] TINYINT
);
INSERT INTO #DataSource ([c], [p])
VALUES (1,0)
,(3, 1)
,(2, 3)
,(5,1)
,(7, 2)
,(40, 0)
,(2, 40);
WITH DataSource ([c], [p], [level], [rank])AS
(
SELECT [c]
,[p]
,CAST('/' AS VARCHAR(24))
,ROW_NUMBER() OVER (ORDER BY [c] ASC)
FROM #DataSource
WHERE [p] = 0
UNION ALL
SELECT DS.[c]
,DS.[p]
,CAST(DS1.[level] + CAST(DS.[c] AS VARCHAR(3)) + '/' AS VARCHAR(24))
,DS1.[rank]
FROM #DataSource DS
INNER JOIN DataSource DS1
ON DS1.[c] = DS.[p]
)
SELECT [c]
,[p]
FROM DataSource
ORDER BY [Rank]
,CAST([level] AS hierarchyid);
Again, pay attention to the node (7,2) which is participating in the two groups (even in your example). I guess this is just a sample data and you have a way to defined where the node should be included.

CTE Recursion to get tree hierarchy

I need to get an ordered hierarchy of a tree, in a specific way. The table in question looks a bit like this (all ID fields are uniqueidentifiers, I've simplified the data for sake of example):
EstimateItemID EstimateID ParentEstimateItemID ItemType
-------------- ---------- -------------------- --------
1 A NULL product
2 A 1 product
3 A 2 service
4 A NULL product
5 A 4 product
6 A 5 service
7 A 1 service
8 A 4 product
Graphical view of the tree structure (* denotes 'service'):
A
___/ \___
/ \
1 4
/ \ / \
2 7* 5 8
/ /
3* 6*
Using this query, I can get the hierarchy (just pretend 'A' is a uniqueidentifier, I know it isn't in real life):
DECLARE #EstimateID uniqueidentifier
SELECT #EstimateID = 'A'
;WITH temp as(
SELECT * FROM EstimateItem
WHERE EstimateID = #EstimateID
UNION ALL
SELECT ei.* FROM EstimateItem ei
INNER JOIN temp x ON ei.ParentEstimateItemID = x.EstimateItemID
)
SELECT * FROM temp
This gives me the children of EstimateID 'A', but in the order that it appears in the table. ie:
EstimateItemID
--------------
1
2
3
4
5
6
7
8
Unfortunately, what I need is an ordered hierarchy with a result set that follows the following constraints:
1. each branch must be grouped
2. records with ItemType 'product' and parent are the top node
3. records with ItemType 'product' and non-NULL parent grouped after top node
4. records with ItemType 'service' are bottom node of a branch
So, the order that I need the results, in this example, is:
EstimateItemID
--------------
1
2
3
7
4
5
8
6
What do I need to add to my query to accomplish this?
Try this:
;WITH items AS (
SELECT EstimateItemID, ItemType
, 0 AS Level
, CAST(EstimateItemID AS VARCHAR(255)) AS Path
FROM EstimateItem
WHERE ParentEstimateItemID IS NULL AND EstimateID = #EstimateID
UNION ALL
SELECT i.EstimateItemID, i.ItemType
, Level + 1
, CAST(Path + '.' + CAST(i.EstimateItemID AS VARCHAR(255)) AS VARCHAR(255))
FROM EstimateItem i
INNER JOIN items itms ON itms.EstimateItemID = i.ParentEstimateItemID
)
SELECT * FROM items ORDER BY Path
With Path - rows a sorted by parents nodes
If you want sort childnodes by ItemType for each level, than you can play with Level and SUBSTRING of Pathcolumn....
Here SQLFiddle with sample of data
This is an add-on to Fabio's great idea from above. Like I said in my reply to his original post. I have re-posted his idea using more common data, table name, and fields to make it easier for others to follow.
Thank you Fabio! Great name by the way.
First some data to work with:
CREATE TABLE tblLocations (ID INT IDENTITY(1,1), Code VARCHAR(1), ParentID INT, Name VARCHAR(20));
INSERT INTO tblLocations (Code, ParentID, Name) VALUES
('A', NULL, 'West'),
('A', 1, 'WA'),
('A', 2, 'Seattle'),
('A', NULL, 'East'),
('A', 4, 'NY'),
('A', 5, 'New York'),
('A', 1, 'NV'),
('A', 7, 'Las Vegas'),
('A', 2, 'Vancouver'),
('A', 4, 'FL'),
('A', 5, 'Buffalo'),
('A', 1, 'CA'),
('A', 10, 'Miami'),
('A', 12, 'Los Angeles'),
('A', 7, 'Reno'),
('A', 12, 'San Francisco'),
('A', 10, 'Orlando'),
('A', 12, 'Sacramento');
Now the recursive query:
-- Note: The 'Code' field isn't used, but you could add it to display more info.
;WITH MyCTE AS (
SELECT ID, Name, 0 AS TreeLevel, CAST(ID AS VARCHAR(255)) AS TreePath
FROM tblLocations T1
WHERE ParentID IS NULL
UNION ALL
SELECT T2.ID, T2.Name, TreeLevel + 1, CAST(TreePath + '.' + CAST(T2.ID AS VARCHAR(255)) AS VARCHAR(255)) AS TreePath
FROM tblLocations T2
INNER JOIN MyCTE itms ON itms.ID = T2.ParentID
)
-- Note: The 'replicate' function is not needed. Added it to give a visual of the results.
SELECT ID, Replicate('.', TreeLevel * 4)+Name 'Name', TreeLevel, TreePath
FROM MyCTE
ORDER BY TreePath;
I believe that you need to add the following to the results of your CTE...
BranchID = some kind of identifier that uniquely identifies the branch. Forgive me for not being more specific, but I'm not sure what identifies a branch for your needs. Your example shows a binary tree in which all branches flow back to the root.
ItemTypeID where (for example) 0 = Product and 1 = service.
Parent = identifies the parent.
If those exist in the output, I think you should be able to use the output from your query as either another CTE or as the FROM clause in a query. Order by BranchID, ItemTypeID, Parent.

Resources