Collect all Similar Persons to One Group - sql

I have a person with several Id's.
Some of them in Column Id1 and Some of them in Id2.
I want to collect all the same persons Id's to one group.
If id1=10, is in the same row with id2=20. so it's mean that person with id1=10 he is the same person like id2=20.
The Input and Output example:
Input
Id1 Id2
--- ---
10 20
10 30
30 30
10 40
50 70
60 50
70 70
Output
NewId OldId
----- -----
1 10
1 20
1 30
1 40
2 50
2 60
2 70

For recursive tasks you should use recursive CTE.
with cq as
(
select distinct Id2, Id1 from #Tmp -- get your table
union
select distinct Id1, Id2 from #Tmp -- get your table (or sort output)
union
select distinct Id1, Id1 from #Tmp -- add root from Id1
union
select distinct Id2, Id2 from #Tmp -- add root from Id2
), cte (Id1, Id2, lvl)
as (
select t.Id1, t.Id2, 0 lvl
from cq t
union all
select t2.Id2, c.Id1, lvl + 1 lvl
from cq t2, cte c
where t2.Id1 = c.Id2
and t2.Id1 != c.Id1
and c.lvl < 5 -- maximum level of recursion
)
select
Id1,
min(Id2) FirstId1,
dense_rank() over(order by min(Id2)) rn
from cte
group by Id1
Max lvl and condition with != isn't necessary if your table will be well ordered.

I suspect this could be done with recursive CTEs, but here is a less elegent solution.
-- CREATE Temps
CREATE TABLE #Table (id1 INT, id2 INT)
CREATE TABLE #NewTable (NewID INT, OldID INT)
CREATE TABLE #AllIDs (ID INT)
-- Insert Test data
INSERT #Table
( id1, id2 )
VALUES ( 10, 20 ),
( 10, 30 ),
( 30, 20 ),
( 10, 40 ),
( 50, 70 ),
( 60, 50 ),
( 70, 70 ),
( 110, 120 ),
( 120, 130 ),
( 140, 130 )
-- Assemble all possible OldIDs
INSERT INTO #AllIDs
SELECT id1 FROM #Table
UNION
SELECT id2 FROM #Table
DECLARE #NewID INT = 1,
#RowCnt int
-- Insert seed OldID
INSERT #NewTable
SELECT TOP 1 #NewID, id
FROM #AllIDs
WHERE id NOT IN (SELECT OldID FROM #NewTable)
ORDER BY 2
SET #RowCnt = ##ROWCOUNT
WHILE #RowCnt > 0
BEGIN
WHILE #RowCnt > 0
BEGIN
-- Check for id2 that match current OldID
INSERT #NewTable
SELECT DISTINCT #NewID, id2
FROM #Table t
INNER JOIN #NewTable nt ON t.id1 = nt.OldID
WHERE nt.[NewID] = #NewID
AND t.id2 NOT IN (SELECT OldID FROM #NewTable WHERE [NewID] = #NewID)
SELECT #RowCnt = ##ROWCOUNT
-- Check for id1 that match current OldID
INSERT #NewTable
SELECT DISTINCT #NewID, id1
FROM #Table t
INNER JOIN #NewTable nt ON t.id2 = nt.OldID
WHERE nt.[NewID] = #NewID
AND t.id1 NOT IN (SELECT OldID FROM #NewTable WHERE [NewID] = #NewID)
SELECT #RowCnt = #RowCnt + ##ROWCOUNT
END
SET #NewID = #NewID + 1
-- Add another seed OldID if any left
INSERT #NewTable
SELECT TOP 1 #NewID, id
FROM #AllIDs
WHERE id NOT IN (SELECT OldID FROM #NewTable)
ORDER BY 2
SELECT #RowCnt = ##ROWCOUNT
END
-- Get Results
SELECT * FROM #NewTable ORDER BY [NewID], OldID

Anna, is that a good example?
This is a connected components issue.
Input
Id1 Id2
--- ---
10 20
10 30
30 30
10 40
50 70
60 50
70 70
Output
NewId OldId
----- -----
1 10
1 20
1 30
1 40
2 50
2 60
2 70

The CTE version. Note that I have added a few more data points to simulate duplicates and lone Ids.
--create test data
declare #table table (Id1 int, Id2 int);
insert #table values
(10, 20),
(10, 30),
(30, 30),
(10, 40),
(40, 45),
(20, 40),
(50, 70),
(60, 50),
(70, 70),
(80, 80);
select *
from #table;
--join related IDs with recursive CTE
;with min_first_cte as (
select case when Id1 <= Id2 then Id1 else Id2 end Id1,
case when Id1 <= Id2 then Id2 else Id1 end Id2
from #table
), related_ids_cte as (
--anchor IDs
select distinct Id1 BaseId, Id1 ParentId, Id1 ChildId
from min_first_cte
where Id1 not in ( select Id2
from min_first_cte
where Id2 <> Id1)
union all
--related recursive IDs
select r.BaseId, m.Id1 ParentId, M.Id2 ChildId
from min_first_cte m
join related_ids_cte r
on r.ChildId = m.Id1
and m.Id1 <> m.Id2
), distinct_ids_cte as (
select distinct r.BaseId, r.ChildId
from related_ids_cte r
)
select dense_rank() over (order by d.BaseId) [NewId],
d.ChildId OldId
from distinct_ids_cte d
order by BaseId, ChildId;

Conceptually, it's about finding connected components given a list of connected pairs. And then, assign each of the groups a new id. The following implementation works:
CREATE TABLE #pairs (a int, b int)
CREATE TABLE #groups (a int, group_id int)
INSERT INTO #pairs
VALUES (1, 2), (3, 4), (5, 6), (5, 7), (3, 9), (8, 10), (11, 12), (1, 3)
-- starting stage - all items belong to their own group
INSERT INTO #groups(a, group_id)
SELECT a, a
FROM #pairs
UNION
SELECT b, b
FROM #pairs
DECLARE #a INT
DECLARE #b INT
DECLARE #cGroup INT
SET ROWCOUNT 0
SELECT * INTO #mytemp FROM #pairs
SET ROWCOUNT 1
SELECT #a = a, #b = b FROM #mytemp
WHILE ##rowcount <> 0
BEGIN
SET ROWCOUNT 0
DECLARE #aGroup INT, #bGroup INT, #newGroup INT
SELECT #aGroup = group_id FROM #groups WHERE a = #a
SELECT #bGroup = group_id FROM #groups WHERE a = #b
SELECT #newGroup = MIN(group_id) FROM #groups WHERE a IN (#a, #b)
-- update the grouping table with the new group
UPDATE #groups
SET group_id = #newGroup
WHERE group_id IN (#aGroup, #bGroup)
DELETE FROM #mytemp
WHERE a = #a
AND b = #b
SET ROWCOUNT 1
SELECT #a = a, #b = b FROM #mytemp
END
SET ROWCOUNT 0
SELECT * FROM #groups
DROP TABLE #mytemp
DROP TABLE #pairs
DROP TABLE #groups
Here's the explanation:
initially, assign each number a group of it's own value
iterate over the pairs
for each pair
find the minimum as set it as the new group id
set the group id to all the numbers where the current group id is the same as for the numbers in the current pair
In terms of a procedure, these are 2 iterations continuously updating the group ids to the minimum in the group - O(n2).

Related

Split set of values into 5 groups each group should have sum(count) evenly

SQL Server to split set of values into 5 groups each group should have sum(count) evenly distributed.
Table contains only 2 columns rid and count.
create table t1(rid int, count int)
insert into t1
values (1, 4567), (2, 3256), (3, 5678), (4, 934),
(5, 1099), (6, 3990), (7, 780), (8, 6784),
(9, 7854), (10, 435), (11, 3455), (12, 4897),
(13, 8849), (14, 1019), (15, 2387)
Actual table is
rid count
---------
1 4567
2 3256
3 5678
4 934
5 1099
6 3990
7 780
8 6784
9 7854
10 435
11 3455
12 4897
13 8849
14 1019
15 2387
I need to divide the values into into 5 groups dynamically, each group should have sum(count) evenly distributed
The sum of the columns is equivalent to 55500. I need to divide the sum by 55500/5=11100. we need to divide the values in to 5 groups, each group should have sum(count) evenly distributed, equivalent to 11110 (approximately)
I would start with 5 randomly chosen groups:
select t.*,
ntile(5) over (order by newid()) as grp
from t;
The sums should be pretty close. If you have a lot of records and the counts are reasonably distributed, then an nth sample usually does a pretty good job:
select t.*,
(row_number() over (order by count) % 5) as grp
from t;
If you have a situation where you have very disparate sizes for count and you need the optimal solution, then you have a hard problem.
You can try this script.
;WITH CTE AS (
SELECT * ,
RN = ROW_NUMBER() OVER (ORDER BY [count] DESC)
FROM T1
)
,CTE2 AS (
SELECT *,
RN2 = ROW_NUMBER() OVER(ORDER BY CEILING( RN / 5.00 ), (( 1 - CEILING( RN / 5.00 )) * [COUNT] ) DESC )
FROM CTE
)
SELECT
CTE2.rid,
CTE2.[count],
((RN2+1)%5) +1 GroupIndex,
SUM(CTE2.[count]) OVER (PARTITION BY ((RN2+1)%5)) CmlTotal
FROM CTE2
Result:
rid count GroupIndex CmlTotal
----------- ----------- -------------------- -----------
3 5678 1 10687
6 3990 1 10687
14 1019 1 10687
5 1099 2 10563
1 4567 2 10563
12 4897 2 10563
15 2387 3 11671
10 435 3 11671
13 8849 3 11671
9 7854 4 11890
7 780 4 11890
2 3256 4 11890
11 3455 5 11173
4 934 5 11173
8 6784 5 11173
Here's my go at it, what I've done is created a temp table with an identity column and an extra column ([Group]). The numbers are inserted in descending size order. Then, I've written a LOOP which inserts Groups 1 to 5 next to the largest 5 numbers in the [Group] column, then flips and inserts Groups 5 to 1 against the next 5 largest numbers, then it flips again and so on until it reaches the end of the table.
CREATE TABLE #T1
(
RID INT IDENTITY(1,1),
[Count] INT,
[Group] INT
)
INSERT INTO #T1 ([Count])
SELECT [Count] FROM T1 ORDER BY [Count] DESC
GO
DECLARE #ROWCOUNT INT = 1
WHILE #ROWCOUNT <= (SELECT MAX(RID) FROM #T1)
BEGIN
DECLARE #COUNT INT = (SELECT TOP 1 [COUNT]
FROM #T1 WHERE [GROUP] IS NULL ORDER BY [COUNT] DESC)
DECLARE #GROUP INT = 1
WHILE #GROUP <=5
BEGIN
UPDATE #T1 SET [GROUP] = #GROUP WHERE [COUNT] = #COUNT
SET #COUNT = (SELECT TOP 1 [COUNT] FROM #T1 WHERE [GROUP] IS NULL ORDER BY [COUNT] DESC)
SET #GROUP = #GROUP + 1
SET #ROWCOUNT = #ROWCOUNT +1
END
SET #GROUP = #GROUP - 1
WHILE #GROUP > 0
BEGIN
UPDATE #T1 SET [GROUP] = #GROUP WHERE [COUNT] = #COUNT
SET #COUNT = (SELECT TOP 1 [COUNT] FROM #T1 WHERE [GROUP] IS NULL ORDER BY [COUNT] DESC)
SET #GROUP = #GROUP - 1
SET #ROWCOUNT = #ROWCOUNT +1
END
END
The code below just demonstrates the actual numbers in each of the five groups and also shows the variance away from the sum of numbers divided by five.
DECLARE #AVGROUP INT = (SELECT SUM([COUNT])/5 FROM #T1);
WITH CTE (SUMCOUNT) AS
(
SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 1
UNION
SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 2
UNION
SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 3
UNION
SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 4
UNION
SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 5
)
,
CTE1 (SUMCOUNT,VARIANCE) AS
(
SELECT SUMCOUNT,#AVGROUP-SUMCOUNT FROM CTE
)
SELECT * FROM CTE1
Is this accurate enough? In other words, does a variance range of 1274 seem to be evenly distributed enough with these numbers? I think it might be possible to make it more accurate if required, if this is accurate enough, then fine.
Below is a code which shows how the Groups are comprised:
DECLARE #AVGROUP INT = (SELECT SUM([COUNT])/5 FROM #T1);
WITH CTE ([GROUP],N1,N2,N3,SUMCOUNT) AS
(
SELECT '1',
(SELECT [COUNT] FROM #T1 WHERE RID = 1),
(SELECT [COUNT] FROM #T1 WHERE RID = 10),
(SELECT [COUNT] FROM #T1 WHERE RID = 11),
(SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 1)
UNION
SELECT '2',
(SELECT [COUNT] FROM #T1 WHERE RID = 2),
(SELECT [COUNT] FROM #T1 WHERE RID = 9),
(SELECT [COUNT] FROM #T1 WHERE RID = 12),
(SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 2)
UNION
SELECT '3',
(SELECT [COUNT] FROM #T1 WHERE RID = 3),
(SELECT [COUNT] FROM #T1 WHERE RID = 8),
(SELECT [COUNT] FROM #T1 WHERE RID = 13),
(SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 3)
UNION
SELECT '4',
(SELECT [COUNT] FROM #T1 WHERE RID = 4),
(SELECT [COUNT] FROM #T1 WHERE RID = 7),
(SELECT [COUNT] FROM #T1 WHERE RID = 14),
(SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 4)
UNION
SELECT '5',
(SELECT [COUNT] FROM #T1 WHERE RID = 5),
(SELECT [COUNT] FROM #T1 WHERE RID = 6),
(SELECT [COUNT] FROM #T1 WHERE RID = 15),
(SELECT SUM([COUNT]) FROM #T1 WHERE [GROUP] = 5)
)
,
CTE1 ([GROUP],N1,N2,N3,SUMCOUNT,VARIANCE) AS
(
SELECT [GROUP],N1,N2,N3,SUMCOUNT,#AVGROUP-SUMCOUNT FROM CTE
)
SELECT * FROM CTE1

Update table in Sql

Table tblProductStock:
ID Name Qyt
1 X 50
2 Y 40
3 Z 30
Table tblStockMinus:
Id Name Qty
1 X 10
1 X 20
2 Y 30
3 Z 20
I want to update tblProductStock so that it will have:
ID Name Qyt
1 X 20
2 Y 10
3 Z 10
I have tried to use the following SQL request, but it didn't work correctly:
UPDATE ProductStock_Show
SET Quantity = ProductStock_Show.Quantity - ProductStock_Show_Minus.Quantity
FROM ProductStock_Show JOIN ProductStock_Show_Minus
ON ProductStock_Show_Minus.ProductId=ProductStock_Show.ProductId
This will give you required result.
select t1.id, t1.qty-t2.qty as qty from tblProductStock as t1 inner join
(
select id, sum(qty) as qty from tblStockMinus group by id
) as t2 on t1.id=t2.id
You need to update this (but it depends in the RDBMS. This is for MS SQL Server)
Update t
set
t.qty=t3.qty from tblProductStock as t inner join
(
select t1.id, t1.qty-t2.qty as qty from tblProductStock as t1 inner join
(
select id, sum(qty) as qty from tblStockMinus group by id
) as t2 on t1.id=t2.id
) as t3 on t.id=t3.id
DECLARE #Table1 TABLE
( ID int, Name varchar(1), Qyt int)
;
INSERT INTO #Table1
( ID , Name , Qyt )
VALUES
(1, 'X', 50),
(2, 'Y', 40),
(3, 'Z', 30)
;
DECLARE #Table2 TABLE
( Id int, Name varchar(1), Qty int)
;
INSERT INTO #Table2
( Id , Name , Qty )
VALUES
(1, 'X', 10),
(1, 'X', 20),
(2, 'Y', 30),
(3, 'Z', 20)
;
UPDATE #Table1 SET QYT =
t.Qyt - s from (
select
t.id,
t.Name,
t.Qyt ,
SUM(tt.Qty)S
FROM #Table1 t
INNER JOIN #Table2 tt
ON t.Name = tt.Name
GROUP BY t.id,t.Name,t.Qyt)T
SELECT * from #Table1

SQL revising table data to a more compact form

I have a table with data pairs modeled like the following:
Id1 Id2
-----------
100 50
120 70
70 50
34 20
50 40
40 10
Id1 is always bigger then Id2. The pairs represent replacements to be made. So 100 will be replaced with 50, but then 50 will be replaced with 40, which will then be replaced by 10.
So the result would be like this:
Id1 Id2
-----------
100 10
120 10
34 20
Is there a nice succinct way that I can alter, or join this table to represent this?
I know i can join it on itself something akin to:
SELECT t1.Id1, t2.Id2
FROM mytable t1
JOIN myTable t2 ON t2.Id1 = t1.Id2
But this will require several passes, hence why i ask if there is a nicer way to accomplish it?
declare #t table(Id1 int, Id2 int)
insert #t values (100, 50)
insert #t values ( 120, 70)
insert #t values ( 70, 50)
insert #t values ( 34, 20)
insert #t values ( 50, 40)
insert #t values ( 40, 10)
;with a as
(
-- find all rows without parent <*>
select id2, id1 from #t t where not exists (select 1 from #t where t.id1 = id2)
union all -- recusive work down to lowest child while storing the parent id1
select t.id2 , a.id1
from a
join #t t on a.id2 = t.id1
)
-- show the lowest child for each row found in <*>
select id1, min(id2) id2 from a
group by id1
Result:
id1 id2
----------- -----------
34 20
100 10
120 10

Is it possible to write a sql query that is grouped based on a running total of a column?

It would be easier to explain with an example. Suppose I wanted to get at most 5 items per group.
My input would be a table looking like this:
Item Count
A 2
A 3
A 3
B 4
B 4
B 5
C 1
And my desired output would look like this:
Item Count
A 5
A>5 3
B 4
B>5 9
C 1
An alternative output that I could also work with would be
Item Count RunningTotal
A 2 2
A 3 5
A 3 8
B 4 4
B 4 8
B 5 13
C 1 1
I can use ROW_NUMBER() to get the top X records in each group, however my requirement is to get the top X items for each group, not X records. My mind is drawing a blank as to how to do this.
declare #yourTable table (item char(1), [count] int)
insert into #yourTable
select 'A', 2 union all
select 'A', 3 union all
select 'A', 3 union all
select 'B', 4 union all
select 'B', 4 union all
select 'B', 5 union all
select 'C', 1
;with cte(item, count, row) as (
select *, row_number() over ( partition by item order by item, [count])
from #yourTable
)
select t1.Item, t1.Count, sum(t2.count) as RunningTotal from cte t1
join cte t2 on t1.item = t2.item and t2.row <= t1.row
group by t1.item, t1.count, t1.row
Result:
Item Count RunningTotal
---- ----------- ------------
A 2 2
A 3 5
A 3 8
B 4 4
B 4 8
B 5 13
C 1 1
Considering the clarifications from your comment, you should be able to produce the second kid of output from your post by running this query:
select t.Item
, t.Count
, (select sum(tt.count)
from mytable tt
where t.item=tt.item and (tt.creating_user_priority < t.creating_user_priority or
( tt.creating_user_priority = t.creating_user_priority and tt.created_date < t.createdDate))
) as RunningTotal
from mytable t
declare #yourTable table (item char(1), [count] int)
insert into #yourTable
select 'A', 2 union all
select 'A', 3 union all
select 'A', 3 union all
select 'B', 4 union all
select 'B', 4 union all
select 'B', 5 union all
select 'C', 1
;with cte(item, count, row) as (
select *, row_number() over ( partition by item order by item, [count])
from #yourTable
)
select t1.row, t1.Item, t1.Count, sum(t2.count) as RunningTotal
into #RunTotal
from cte t1
join cte t2 on t1.item = t2.item and t2.row <= t1.row
group by t1.item, t1.count, t1.row
alter table #RunTotal
add GrandTotal int
update rt
set GrandTotal = gt.Total
from #RunTotal rt
left join (
select Item, sum(Count) Total
from #RunTotal rt
group by Item) gt
on rt.Item = gt.Item
select Item, max(RunningTotal)
from #RunTotal
where RunningTotal <= 5
group by Item
union
select a.Item + '>5', total - five
from (
select Item, max(GrandTotal) total
from #RunTotal
where GrandTotal > 5
group by Item
) a
left join (
select Item, max(RunningTotal) five
from #RunTotal
where RunningTotal <= 5
group by Item
) b
on a.Item = b.Item
I've updated the accepted answer and got your desired result.
SELECT Item, SUM(Count)
FROM mytable t
GROUP BY Item
HAVING SUM(Count) <=5
UNION
SELECT Item, 5
FROM mytable t
GROUP BY Item
HAVING SUM(Count) >5
UNION
SELECT t2.Item + '>5', Sum(t2.Count) - 5
FROM mytable t2
GOUP BY Item
HAVING SUM(Count) > 5
ORDER BY 1, 2
select 'A' as Name, 2 as Cnt
into #tmp
union all select 'A',3
union all select 'A',3
union all select 'B',4
union all select 'B',4
union all select 'B',5
union all select 'C',1
select Name, case when sum(cnt) > 5 then 5 else sum(cnt) end Cnt
from #tmp
group by Name
union
select Name+'>5', sum(cnt)-5 Cnt
from #tmp
group by Name
having sum(cnt) > 5
Here is what I have so far. I know it's not complete but... this should be a good starting point.
I can get your second output by using a temp table and an update pass:
DECLARE #Data TABLE
(
ID INT IDENTITY(1,1) PRIMARY KEY
,Value VARCHAR(5)
,Number INT
,Total INT
)
INSERT INTO #Data (Value, Number) VALUES ('A',2)
INSERT INTO #Data (Value, Number) VALUES ('A',3)
INSERT INTO #Data (Value, Number) VALUES ('A',3)
INSERT INTO #Data (Value, Number) VALUES ('B',4)
INSERT INTO #Data (Value, Number) VALUES ('B',4)
INSERT INTO #Data (Value, Number) VALUES ('B',5)
INSERT INTO #Data (Value, Number) VALUES ('C',1)
DECLARE
#Value VARCHAR(5)
,#Count INT
UPDATE #Data
SET
#Count = Total = CASE WHEN Value = #Value THEN Number + #Count ELSE Number END
,#Value = Value
FROM #Data AS D
SELECT
Value
,Number
,Total
FROM #Data
There may be better ways, but this should work.

Need a tweak in SQL Server

Tbl 1
ID Name Email
1 A a#b.c
2 B b#c.d
Tbl2
ID Related_ID Value Name
1 1 Z Name1
2 1 Y Name2
3 2 X Name1
4 2 W Name2
5 2 G Name3
I can write a query to show
ID Name Email Value1
1 A a#b.c Z
1 A a#b.c Y
How can I write it so it will become like
ID Name Email Value1 Value2
1 A a#b.c Z Y
2 B b#c.d X W
Here is an example to get what you want using dynamic sql.
Note that the CTE test and the temporary table #tmp2 should map to your original tables.
create table #tmp (ID int, Related_ID int, Value nvarchar(50), Name nvarchar(50), rn int)
delete #tmp
create table #tmp2 (ID int, Name nvarchar(50), Email nvarchar(50))
delete #tmp2
;with test(ID, Related_ID, Value, Name)
as
(
select 1, 1, 'Z', 'Name1'
union all
select 2, 1, 'Y', 'Name2'
union all
select 3, 2, 'X', 'Name1'
union all
select 4, 2, 'W', 'Name2'
union all
select 5, 2, 'G', 'Name3'
)
insert into #tmp
select *, row_number() OVER (partition by Related_ID order by ID) as rn
from test
insert into #tmp2
select 1, 'A', 'a#b.c'
union all
select 2, 'B', 'b#c.d'
declare #d nvarchar(MAX)
,#e nvarchar(MAX)
SET #d = 'select a.ID, a.Name, a.Email '
SET #e = ',min(case b.rn when >rn< then Value else null end) as [Value>rn<]'
select #d = #d + (
select t from(
select distinct replace(#e,'>rn<', b.rn) as [text()]
from #tmp b
for xml path('')) c(t)
)
set #d = #d + '
from #tmp2 a
join #tmp b
on a.ID = b.Related_ID
group by a.ID, a.Name, a.Email'
exec sp_executesql #d
drop table #tmp
drop table #tmp2
declare #t1 table(id int, name varchar(20), email varchar(32))
declare #t2 table(id int, related_id int, value varchar(10), name varchar(10))
insert #t1 values(1,'A', 'a#b.c')
insert #t1 values(2,'B', 'b#c.d')
insert #t2 values(1, 1, 'Z', 'Name1')
insert #t2 values(2, 1, 'Y', 'Name2')
insert #t2 values(3, 2, 'X', 'Name1')
insert #t2 values(4, 2, 'W', 'Name2')
insert #t2 values(5, 2, 'G', 'Name3')
;with a as
(
select value, related_id, ROW_NUMBER() over(PARTITION BY related_id order by value desc) rn
from #t2
), b as
(
select value, related_id, rn from a where rn in (1,2)
)
select t5.ID, t5.Name, t5.Email, t3.value Value1, t4.value Value2
from #t1 t5
join b t3
on t3.related_id = t5.id and t3.rn = 1
join b t4
on t4.related_id = t5.id and t4.rn = 2
Result:
ID Name Email Value1 Value2
------ ------ ---------- -------- ----------
1 A a#b.c Z Y
2 B b#c.d X W
It can be done using PIVOT Statement.
Here are a couple of links you can have a look at.
http://blog.sqlauthority.com/2008/06/07/sql-server-pivot-and-unpivot-table-examples/