I'm having trouble figuring out how to write a query that checks if two tables have any overlapping rows, given a certain criteria. The main issue I have is that the number of rows that need to match can vary, sometimes it may be one row, sometimes it's several.
As an example, let's say I have two tables. I want to find which Parents are in both tables. For a parent to be in both tables, the tables need to have the same number of children, and the children should have the same names and ages. Each parent is identified by either a name or a number.
CREATE TABLE ParentsToSearch
(
ParentID INT NOT NULL,
ChildName NVARCHAR(800) NOT NULL,
ChildAge INT
);
CREATE TABLE ExistingParentsAndChildren
(
ParentName NVARCHAR(800) NOT NULL,
ChildName NVARCHAR(800) NOT NULL,
ChildAge INT
);
If I had the following sample data, I would want the query to return that ParentID 7 exists in the table ExistingParentAndChildren (as ParentID 7's children and their ages is a perfect match with John's children)
ParentsToSearch
ParentID
ChildName
ChildAge
7
Katie
17
7
Jacob
8
12
Robert
10
ExistingParentAndChildren
ParentName
ChildName
ChildAge
John
Katie
17
John
Jacob
8
Sue
Robert
5
Sue
Carter
14
Sue
Ralph
10
Alex
Rocky
12
I assume I need to use something like ALL or PIVOT? But I'm kind of lost, as I'm new to DB queries.
A simple join on the ChildName and ChildAge columns gets you close.
But then there is the possibility that the children of parent Y are a subset of the children with parent X (i.e. parent X has children x1 (5) and x2 (3) and parent Y also has child x1 (5), then the children from parent Y are a subset of the children from parent X).
If you would extend the available parent data with the count of his children in each table and matched on that number as well, then you would have a full match.
Adding the required counts can be done with a cross apply (a subquery that is executed for each row) or common table expressions (returns a table you can join with).
Sample data
CREATE TABLE ParentsToSearch
(
ParentID INT NOT NULL,
ChildName NVARCHAR(800) NOT NULL,
ChildAge INT
);
insert into ParentsToSearch (ParentId, ChildName, ChildAge) values
( 7, 'Katie', 17),
( 7, 'Jacob', 8),
(12, 'Robert', 10);
CREATE TABLE ExistingParentsAndChildren
(
ParentName NVARCHAR(800) NOT NULL,
ChildName NVARCHAR(800) NOT NULL,
ChildAge INT
);
insert into ExistingParentsAndChildren (ParentName, ChildName, ChildAge) values
('John', 'Katie', 17),
('John', 'Jacob', 8),
('Sue', 'Robert', 5),
('Sue', 'Carter', 14),
('Sue', 'Ralph', 10),
('Alex', 'Rocky', 12);
Solution
With cross apply.
select p.ParentId,
ep.ParentName
from ParentsToSearch p
cross apply ( select count(1) as ChildCount
from ParentsToSearch p2
where p2.ParentId = p.ParentId ) pc
join ExistingParentsAndChildren ep
on ep.ChildName = p.ChildName
and ep.ChildAge = p.ChildAge
cross apply ( select count(1) as ChildCount
from ExistingParentsAndChildren ep2
where ep2.ParentName = ep.ParentName ) epc
where pc.ChildCount = epc.ChildCount -- match the child counts
group by p.ParentId,
ep.ParentName;
With common table expressions.
with cte_pc as
(
select p2.ParentId,
count(1) as ChildCount
from ParentsToSearch p2
group by p2.ParentId
),
cte_epc as
(
select ep2.ParentName,
count(1) as ChildCount
from ExistingParentsAndChildren ep2
group by ep2.ParentName
)
select p.ParentId,
ep.ParentName
from ParentsToSearch p
join cte_pc p3
on p3.ParentId = p.ParentId
join ExistingParentsAndChildren ep
on ep.ChildName = p.ChildName
and ep.ChildAge = p.ChildAge
join cte_epc ep3
on ep3.ParentName = ep.ParentName
where p3.ChildCount = ep3.ChildCount -- match the child counts
group by p.ParentId,
ep.ParentName;
Result
ParentId ParentName
-------- ----------
7 John
Fiddle to see things in action (also includes some alternative queries to list the children etc.).
If using SQLServer > 2016 then I would just do it like you would think about it without thinking about coding first. Breaking down dataset using "with" statements allows the solution to flow naturally.
Solution:
Group all children under their parents for both tables using the STRING_AGG function and with statements
with ParentNameFamilies as
(
SELECT ParentName, STRING_AGG(CONVERT(NVARCHAR(max), ChildName), ',') AS ChildNames
, STRING_AGG(CONVERT(NVARCHAR(max), ChildAge), ',') AS ChildAges
FROM dbo.ExistingParentsAndChildren
group by ParentName
), ParentIDFamilies as
(
SELECT ParentID, STRING_AGG(CONVERT(NVARCHAR(max), ChildName), ',') AS ChildNames
, STRING_AGG(CONVERT(NVARCHAR(max), ChildAge), ',') AS ChildAges
FROM dbo.ParentsToSearch
group by ParentID
)
Then compare the two types of families on both child names and child ages values
select a.ParentName, b.ParentID
from ParentNameFamilies a
inner join ParentIDFamilies b on a.ChildNames=b.ChildNames and b.ChildAges=b.ChildAges
and so all together in one view or query:
with ParentNameFamilies as
(
SELECT ParentName, STRING_AGG(CONVERT(NVARCHAR(max), ChildName), ',') AS ChildNames
, STRING_AGG(CONVERT(NVARCHAR(max), ChildAge), ',') AS ChildAges
FROM dbo.ExistingParentsAndChildren
group by ParentName
), ParentIDFamilies as
(
SELECT ParentID, STRING_AGG(CONVERT(NVARCHAR(max), ChildName), ',') AS ChildNames
, STRING_AGG(CONVERT(NVARCHAR(max), ChildAge), ',') AS ChildAges
FROM dbo.ParentsToSearch
group by ParentID
)
select a.ParentName, b.ParentID
from ParentNameFamilies a
inner join ParentIDFamilies b on a.ChildNames=b.ChildNames and b.ChildAges=b.ChildAges
Related
Let's say I have 2 tables.
Users Table
and Have one more table which defines hierarchy of user.
hierarchy Table
So as you can see:
C is a supervisor of D
B is a supervisor of C
A is a supervisor of B
So when I pass User D, then it should return all the supervisor like A,B,C
same when I pass User C, then it should return all the supervisor like A,B
What I tried.
Create table Users
(
Id int primary key identity (1,1),
Name varchar(1),
)
Insert into Users values ('A')
Insert into Users values ('B')
Insert into Users values ('C')
Insert into Users values ('D')
Create table Hierarchy
(
Id int primary key identity (1,1),
EmployeeId int FOREIGN KEY REFERENCES Users(Id),
SupervisorId int FOREIGN KEY REFERENCES Users(Id)
)
Insert into Hierarchy values (4,3)
Insert into Hierarchy values (3,2)
Insert into Hierarchy values (2,1)
select * from Users
select * from Hierarchy
with HierarchyData as
(
select mbh.* from Hierarchy mbh where mbh.EmployeeId = 4
union all
select mbh.* from Hierarchy mbh
join Hierarchy on mbh.SupervisorId = Hierarchy.EmployeeId
where mbh.EmployeeId <> 4
)
select e.Name as EmpName, s.Name as SupervisorName from HierarchyData h
join Users e on h.EmployeeId = e.Id
join Users s on h.SupervisorId = s.Id
But I am getting only one level data.
Any kind of help would be appreciated.
#Vishal as per my understanding written query for you can you please check it's working or not?
here I used LEFT JOIN you can go with INNER JOIN
If you go with LEFT JOIN as per your example A not have any supervisor so the record can be empty.
If you go with INNER JOIN as per your example you got only the B, C, D record.
Please check the below test query.
DECLARE #User TABLE
(
UserID INT,
UserName NVARCHAR(50)
)
DECLARE #EmployeeTable TABLE
(
ID INT,
EmployeeID INT,
supervisorID INT
)
INSERT INTO #User VALUES(1,'A'),
(2,'B'),
(3,'C'),
(4,'D')
INSERT INTO #EmployeeTable VALUES
(1,4,3),
(2,3,2),
(3,2,1)
SELECT [U].[UserName] [EmployeeName],
[ET].[EmployeeID],
[ET].[SupervisorID],[ST].[SupervisorName]
FROM #User [U]
LEFT JOIN #EmployeeTable [ET]
ON [U].[UserID] = [ET].[EmployeeID]
LEFT JOIN
(
SELECT [U].UserName [SupervisorName] ,[ST].* FROM #User [U]
INNER JOIN #EmployeeTable [ST]
ON [ST].[supervisorID] = [U].[UserID]
) [ST]
ON [ST].[supervisorID] = [ET].[supervisorID]
Left join query result
Inner join query result
let me know if I can help more :).
I have a table 'temp' which has id and its immediate parent's id as columns. The table is as follows:
1
/ \
2 3
/|\ \
4 5 6 7
/
8
Hierarchy of nodes can be represented in tree structure as above.
Now, I want to list all the ancestor or parent nodes of each node present at all levels in pivoted table using recursive cte which has levels (like Level1, Level2 and so on) as its attributes. To get this output, I have calculated all the parent nodes in non pivoted table with level of each node with respect to its parent. The sql query for which is below:
WITH ctetable as
(
SELECT S.id, S.parent, 1 as level
FROM temp as S where S.parent is not null
UNION ALL
SELECT S2.id, p.parent, p.level + 1
FROM ctetable AS p JOIN temp as S2 on S2.parent = p.id
)
SELECT * FROM ctetable ORDER BY id;
The output of above query is shown below:
But, I want to pivot the recursive cte which contains the parent id under at each level of a particular node. Say, for id=4, it should display parent id 4, 2 and 1 under Level3, Level2 and Level1 respectively. For this I wrote the following query:
WITH ctetable as
(
SELECT S.id, S.parent, 1 as level
FROM temp as S where S.parent is not null
UNION ALL
SELECT S2.id, p.parent, p.level + 1
FROM ctetable AS p JOIN temp as S2 on S2.parent = p.id
)
SELECT
myid,
[pn].[1] AS [Level1],
[pn].[2] AS [Level2],
[pn].[3] AS [Level3]
FROM
(
SELECT [a].id,
[a].id as myid,
[a].level
FROM ctetable AS [a]
) AS [hn] PIVOT(max([hn].id) FOR [hn].level IN([1],[2],[3])) AS [pn]
But, the output table is not the desired one as it contains the same id repeated as parent id under each level for a particular node instead it should contain all the parents of that node under various levels. The output i got after executing the above query is shown below:
Can anybody help me out with this....
If you have a known or maximum number of levels, and assuming I did not reverse your desired results.
Also Best if you post sample data and desired results as text, not as an image
Example
Declare #YourTable Table ([id] int,[parent] int)
Insert Into #YourTable Values
(1,null)
,(2,1)
,(3,1)
,(7,3)
,(4,2)
,(5,2)
,(6,2)
,(8,4)
;with cteP as (
Select id
,Parent
,PathID = cast(10000+id as varchar(500))
From #YourTable
Where Parent is Null
Union All
Select id = r.id
,Parent = r.Parent
,PathID = cast(concat(p.PathID,',',10000+r.id) as varchar(500))
From #YourTable r
Join cteP p on r.Parent = p.id)
Select ID
,B.*
From cteP A
Cross Apply (
Select Level1 = xDim.value('/x[1]','int')-10000
,Level2 = xDim.value('/x[2]','int')-10000
,Level3 = xDim.value('/x[3]','int')-10000
,Level4 = xDim.value('/x[4]','int')-10000
,Level5 = xDim.value('/x[5]','int')-10000
From (Select Cast('<x>' + replace(PathID,',','</x><x>')+'</x>' as xml) as xDim) as X
) B
Order By PathID
Returns
EDIT - Added +10000
I added the +10000 so that the sequence will be maintained
I do have following table
ID Name
1 Jagan Mohan Reddy868
2 Jagan Mohan Reddy869
3 Jagan Mohan Reddy
Name column size is VARCHAR(55).
Now for some other task we need to take only 10 varchar length i.e. VARCHAR(10).
My requirement is to check that after taking the only 10 bits length of Name column value for eg if i take Name value of ID 1 i.e. Jagan Mohan Reddy868 by SUBSTRING(Name, 0,11) if it equals with another row value. here in this case the final value of SUBSTRING(Jagan Mohan Reddy868, 0,11) is equal to Name value of ID 3 row whose Name is 'Jagan Mohan Reddy'. I need to make a list of those kind rows. Can somebody help me out on how can i achieve in SQL Server.
My main check is that the truncated values of my Name column should not match with any non truncated values of Name column. If so i need to get those records.
Assuming I understand the question, I think you are looking for something like this:
Create and populate sample data (Please save us this step in your future questions)
DECLARE #T as TABLE
(
Id int identity(1,1),
Name varchar(15)
)
INSERT INTO #T VALUES
('Hi, I am Zohar.'),
('Hi, I am Peled.'),
('Hi, I am Z'),
('I''m Zohar peled')
Use a cte with a self inner join to get the list of ids that match the first 10 chars:
;WITH cte as
(
SELECT T2.Id As Id1, T1.Id As Id2
FROM #T T1
INNER JOIN #T T2 ON LEFT(T1.Name, 10) = t2.Name AND T1.Id <> T2.Id
)
Select the records from the original table, inner joined with a union of the Id1 and Id2 from the cte:
SELECT T.Id, Name
FROM #T T
INNER JOIN
(
SELECT Id1 As Id
FROM CTE
UNION
SELECT Id2
FROM CTE
) U ON T.Id = U.Id
Results:
Id Name
----------- ---------------
1 Hi, I am Zohar.
3 Hi, I am Z
Try this
SELECT Id,Name
FROM(
SELECT *,ROW_NUMBER() OVER(PARTITION BY Name, LEFT(Name,11) ORDER BY ID) RN
FROM Tbale1 T
) Tmp
WHERE Tmp.RN = 1
loop over your column for all the values and put your substring() function inside this loop and I think in Sql index of string starts from 1 instead of 0. If you pass your string to charindex() like this
CHARINDEX('Y', 'Your String')
thus you will come to know whether it is starting from 0 or 1
and you can save your substring value as value of other column with length 10
I hope it will help you..
I think this should cover all the cases you are looking for.
-- Create Table
DECLARE #T as TABLE
(
Id int identity(1,1),
Name varchar(55)
)
-- Create Data
INSERT INTO #T VALUES
('Jagan Mohan Reddy868'),
('Jagan Mohan Reddy869'),
('Jagan Mohan Reddy'),
('Mohan Reddy'),
('Mohan Reddy123551'),
('Mohan R')
-- Get Matching Items
select *, SUBSTRING(name, 0, 11) as ShorterName
from #T
where SUBSTRING(name, 0, 11) in
(
-- get all shortnames with a count > 1
select SUBSTRING(name, 0, 11) as ShortName
from #T
group by SUBSTRING(name, 0, 11)
having COUNT(*) > 1
)
order by Name, LEN(Name)
I have my database design as per the diagram.
Category table is self referencing parent child relationship
Budget will have all the categories and amount define for each category
Expense table will have entries for categories for which the amount has been spend (consider Total column from this table).
I want to write select statement that will retrieve dataset with columns given below :
ID
CategoryID
CategoryName
TotalAmount (Sum of Amount Column of all children hierarchy From BudgetTable )
SumOfExpense (Sum of Total Column of Expense all children hierarchy from expense table)
I tried to use a CTE but was unable to produce anything useful. Thanks for your help in advance. :)
Update
I just to combine and simplify data I have created one view with the query below.
SELECT
dbo.Budget.Id, dbo.Budget.ProjectId, dbo.Budget.CategoryId,
dbo.Budget.Amount,
dbo.Category.ParentID, dbo.Category.Name,
ISNULL(dbo.Expense.Total, 0) AS CostToDate
FROM
dbo.Budget
INNER JOIN
dbo.Category ON dbo.Budget.CategoryId = dbo.Category.Id
LEFT OUTER JOIN
dbo.Expense ON dbo.Category.Id = dbo.Expense.CategoryId
Basically that should produce results like this.
This is an interesting problem. And I'm going to solve it with a hierarchyid. First, the setup:
USE tempdb;
IF OBJECT_ID('dbo.Hierarchy') IS NOT NULL
DROP TABLE dbo.[Hierarchy];
CREATE TABLE dbo.Hierarchy
(
ID INT NOT NULL PRIMARY KEY,
ParentID INT NULL,
CONSTRAINT [FK_parent] FOREIGN KEY ([ParentID]) REFERENCES dbo.Hierarchy([ID]),
hid HIERARCHYID,
Amount INT NOT null
);
INSERT INTO [dbo].[Hierarchy]
( [ID], [ParentID], [Amount] )
VALUES
(1, NULL, 100 ),
(2, 1, 50),
(3, 1, 50),
(4, 2, 58),
(5, 2, 7),
(6, 3, 10),
(7, 3, 20)
SELECT * FROM dbo.[Hierarchy] AS [h];
Next, to update the hid column with a proper value for the hiearchyid. I'll use a bog standard recursive cte for that
WITH cte AS (
SELECT [h].[ID] ,
[h].[ParentID] ,
CAST('/' + CAST(h.[ID] AS VARCHAR(10)) + '/' AS VARCHAR(MAX)) AS [h],
[h].[hid]
FROM [dbo].[Hierarchy] AS [h]
WHERE [h].[ParentID] IS NULL
UNION ALL
SELECT [h].[ID] ,
[h].[ParentID] ,
CAST([c].[h] + CAST(h.[ID] AS VARCHAR(10)) + '/' AS VARCHAR(MAX)) AS [h],
[h].[hid]
FROM [dbo].[Hierarchy] AS [h]
JOIN [cte] AS [c]
ON [h].[ParentID] = [c].[ID]
)
UPDATE [h]
SET hid = [cte].[h]
FROM cte
JOIN dbo.[Hierarchy] AS [h]
ON [h].[ID] = [cte].[ID];
Now that the heavy lifting is done, the results you want are almost trivially obtained:
SELECT p.id, SUM([c].[Amount])
FROM dbo.[Hierarchy] AS [p]
JOIN [dbo].[Hierarchy] AS [c]
ON c.[hid].IsDescendantOf(p.[hid]) = 1
GROUP BY [p].[ID];
After much research and using test data, I was able to get the running totals starting from bottom of hierarchy.
The solution is made up of two steps.
Create a scalar-valued function that will decide whether a categoryId is a direct or indirect child of another categoryId. This is given in first code-snippet. Note that a recursive query is used for this since that is the best approach when dealing with hierarchy in SQL Server.
Write the running total query that will give totals according to your requirements for all categories. You can filter by category if you wanted to on this query. The second code snippet provides this query.
Scalar-valued function that tells if a child category is a direct or indirect child of another category
CREATE FUNCTION dbo.IsADirectOrIndirectChild(
#childId int, #parentId int)
RETURNS int
AS
BEGIN
DECLARE #isAChild int;
WITH h(ParentId, ChildId)
-- CTE name and columns
AS (
SELECT TOP 1 #parentId, #parentId
FROM dbo.Category AS b
UNION ALL
SELECT b.ParentId, b.Id AS ChildId
FROM h AS cte
INNER JOIN
Category AS b
ON b.ParentId = cte.ChildId AND
cte.ChildId IS NOT NULL)
SELECT #isAChild = ISNULL(ChildId, 0)
FROM h
WHERE ChildId = #childId AND
ParentId <> ChildId
OPTION(MAXRECURSION 32000);
IF #isAChild > 0
BEGIN
SET #isAChild = 1;
END;
ELSE
BEGIN
SET #isAChild = 0;
END;
RETURN #isAChild;
END;
GO
Query for running total starting from bottom of hierarchy
SELECT c.Id AS CategoryId, c.Name AS CategoryName,
(
SELECT SUM(ISNULL(b.amount, 0))
FROM dbo.Budget AS b
WHERE dbo.IsADirectOrIndirectChild( b.CategoryId, c.Id ) = 1 OR
b.CategoryId = c.Id
) AS totalAmount,
(
SELECT SUM(ISNULL(e.total, 0))
FROM dbo.Expense AS e
WHERE dbo.IsADirectOrIndirectChild( e.CategoryId, c.Id ) = 1 OR
e.CategoryId = c.Id
) AS totalCost
FROM dbo.Category AS c;
I have an integer field (essentially a counter) called PID in a sql server table called Projects that I want to increment only when the value of another field changes (Task). Initially PID=1 for all rows. The following query (which I got from one of your answers elsewhere) does exactly what I want but I need to update my table with the result and that I cannot figure out.
SELECT Task,
dense_rank() over(order by Task) PID
FROM dbo.Projects;
If I do something like
Update Projects
SET Projects.PID =(SELECT Task,
dense_rank() over(order by Task) PID
FROM dbo.Projects);
I get "The select list for the INSERT statement contains more items than the insert list. The number of SELECT values must match the number of INSERT columns." How can I update my table with a query that gives me what I want?
This is the table design:
CREATE TABLE [dbo].[Projects]
(
[PID] [int] NULL
, [TID] [int] IDENTITY(1,1) NOT NULL
, [Project] [nvarchar](127) NOT NULL
, [Task] [nvarchar](127) NOT NULL
, [Dollars] [decimal](18, 0) NOT NULL
, [TaskLead] [nvarchar](127) NULL
) ON [PRIMARY];
I populate the table with
INSERT INTO dbo.Projects(Project, Task, Dollars, TaskLead)
SELECT Project + ' ' + ProjectDescription, Task + ' ' + TaskDescription, Dollars, TaskLead
FROM TM1_1
ORDER BY Project ASC, Task ASC;
E.g. data:
PID TID Project Task
1 1 Prj1 Tsk11
1 2 Prj1 Tsk12
2 1 Prj2 Tsk21
I want to update the table such that all projects that are the same have the same PID. I am now trying:
use mbt_tm1;
;WITH cteRank AS (
SELECT PID, DENSE_RANK() OVER ( PARTITION BY Project ORDER BY Project ASC) AS Calculated_Rank
FROM Projects )
UPDATE cteRank
SET PID = Calculated_Rank
Without knowing more about your exact question, perhaps this would work?
Update Projects
SET Projects.PID = dense_rank() over(order by Task);
To help with your question, I've put together a little sample that I think shows what you are asking. If this does not reflect your environment, please add details to your question.
CREATE TABLE Projects
(
ProjectID INT
, Task INT
, PID INT
);
GO
INSERT INTO Projects (ProjectID, Task, PID) VALUES (1, 1, 1);
INSERT INTO Projects (ProjectID, Task, PID) VALUES (2, 1, 2);
INSERT INTO Projects (ProjectID, Task, PID) VALUES (3, 1, 3);
INSERT INTO Projects (ProjectID, Task, PID) VALUES (4, 2, 1);
UPDATE Projects
SET Projects.PID = (
SELECT MAX(PID) + 1
FROM Projects p
WHERE P.Task = Projects.Task
);
SELECT *
FROM Projects;
[EDIT # 2]
Unless I am misunderstanding your requirements, this seems a simple way to update the PID for all associated projects:
UPDATE Projects
SET PID = (SELECT MAX(PID) FROM Projects P WHERE P.Project = Projects.Project);
You have to return only one value from the inner query and you're looking for a rank:
Update Projects
SET Projects.PID = X.Calculated_Rank
FROM Project P
INNER JOIN
(SELECT P1.PID, DENSE_RANK() OVER ( PARTITION BY P1.Project ORDER BY P1.Project ASC) AS Calculated_Rank
FROM Projects P1) X
ON X.PID = P.PID
This is what worked:
;WITH cte AS (
SELECT PID,
DENSE_RANK() OVER (
ORDER BY Project ASC) AS Calculated_Rank
FROM Projects
)
UPDATE cte SET PID = Calculated_Rank
It transformed a query that showed the result, namely:
SELECT Project,
dense_rank() over(order by Project) PID
FROM dbo.Projects;
into one that actually updated the table. Thank you so much for your support!
sorry to say but your question is very unclear. yet the original SELECT query you told is working for you and you want to use it for update i see that update is wrong.
correct update syntex would be something like this.
update PID in table with new Rank where Task is matching.
UPDATE t1 SET t1.Projects.PID = t2.pid
FROM Projects t1
JOIN
(
SELECT Task ,DENSE_RANK() OVER ( ORDER BY Task ) PID
FROM Projects
)t2
ON t1.Task=t2.task
EDIT:-1
this query is to get the output as you explained and shown in example.
no need to use CTE.
set nocount on
declare #prj table
(
pid int null
,tid int null
,prj sysname
,tsk sysname
)
insert into #prj (prj,tsk)
select 'prj1','tsk11'
union all select 'prj1','tsk12'
union all select 'prj2','tsk21'
union all select 'prj2','tsk22'
update t1 set t1.pID=t2.pID, t1.tID=t2.tID
from #prj t1
join
(
select dense_RANK() over (order by prj) as pID
,ROW_NUMBER() over (partition by prj order by prj,tsk) as tID
,prj,tsk
from #prj
)t2
on t1.prj=t2.prj and t1.tsk=t2.tsk
select * from #prj