Joining tables without duplicate - sql

I have 3 SQL tables as below:
Table 1
ItemId Name
----------
A aa
B bb
Table 2
ItemId Category
----------
A 1
A 2
A 3
B 1
Table 3
ItemId Dep
----------
A D1
B D2
B D3
I need result as this
ItemId Name Category Dep
------------------------
A aa 1 D1
2
3
B bb 1 D2
D3
Is there any way to get this result without looping tables?

You can first JOIN the tables on ItemId and then use ROW_NUMBER and RANK for formatting.
I suggest you do the display format in the client side
SQL Fiddle
WITH CTE AS(
SELECT
t1.ItemId, t1.Name, t2.Category, t3.Dep,
Rn_Cat = ROW_NUMBER() OVER(PARTITION BY t1.ItemId, t1.Name ORDER BY t2.Category),
Rn_Dep = ROW_NUMBER() OVER(PARTITION BY t1.ItemId, t1.Name ORDER BY t3.Dep),
Rnk_Cat = RANK() OVER(PARTITION BY t1.ItemId, t1.Name ORDER BY t2.Category),
Rnk_Dep = RANK() OVER(PARTITION BY t1.ItemId, t1.Name ORDER BY t3.Dep)
FROM Table1 t1
LEFT JOIN Table2 t2
ON t2.ItemId = t1.ItemId
LEFT JOIN Table3 t3
ON t3.ItemId = t1.ItemId
)
SELECT
ItemId = CASE WHEN Rn_Cat = 1 THEN ItemId ELSE '' END,
Name = CASE WHEN Rn_Cat = 1 THEN Name ELSE '' END,
Category = CASE WHEN Rn_Cat = Rnk_Cat THEN CONVERT(VARCHAR(10), Category) ELSE '' END,
Dep = CASE WHEN Rn_Dep = Rnk_Dep THEN CONVERT(VARCHAR(10), Dep) ELSE '' END
FROM CTE

May be Using UNION
Fiddle Here
WITH CTE AS(
SELECT
t2.ItemId,t1.Name,t2.Category,t3.Dep,
Rank() over(Partition by t1.ItemId,t1.Name order by t2.Category,t3.Dep) as rn
from
Table1 t1 join Table2 t2 on t1.ItemId=t2.ItemId
join Table3 t3 on t1.ItemId=t3.ItemId
)
SELECT
ItemId,Name,Category,Dep,Rn
FROM CTE where rn=1
union
SELECT
'','',Category,Dep,Rn
FROM CTE where rn>1

this will probably work:
;with cte_t1 as
(
select 'A' as ItemId, 'aa' as Name
union
select 'B' as ItemId, 'bb' as Name
),
cte_t2 as
(
select 'A' AS ItemId, 1 as Category
union
select 'A' AS ItemId, 2 as Category
union
select 'A' AS ItemId, 3 as Category
union
select 'B' AS ItemId, 1 as Category
),
cte_t3 as
(
select 'A' AS ItemId, 'D1' as Dep
union
select 'B' AS ItemId, 'D2' as Dep
union
select 'B' AS ItemId, 'D3' as Dep
),
cte_t4 as
(
SELECT T1.ItemId, t1.Name, T2.Category, T3.Dep, ROW_NUMBER() over(order by T1.ItemId, T2.Category) RowNumber
FROM cte_t1 t1 inner join cte_t2 t2
on t1.ItemId = t2.ItemId
inner join cte_t3 t3
on t1.ItemId = t3.ItemId
and t2.ItemId = t3.ItemId
)
select
case when a.ItemId = b.ItemId then '' else a.ItemId end as ItemId,
case when a.Name = b.Name then '' else a.Name end as Name,
case when a.Category = b.Category then '' else cast(a.Category as varchar(100)) end as Category,
case when a.Dep = b.Dep then '' else a.Dep end as Dep
from cte_t4 a
left join cte_t4 b
on a.RowNumber-1= b.RowNumber

Using this code -
SELECT table1.ItemID,
table1.Name,
table2.Category,
table3.Dep
FROM table1, table2, table3
WHERE table1.ItemID = table2.ItemID AND table1.ItemID = table3.ItemID;
produces this output -
+--------+------+----------+-----+
| ItemID | Name | Category | Dep |
+--------+------+----------+-----+
| A | aa | 1 | D1 |
| A | aa | 2 | D1 |
| A | aa | 3 | D1 |
| B | bb | 1 | D2 |
| B | bb | 1 | D3 |
+--------+------+----------+-----+
Is this what you are after, or do you wish to display a blank space for every repeat of a value?

Related

find the max for each value in SQL

i have tables like this
table 1
|cl.1|
| -- |
| a |
| b |
| c |
table 2
|cl.1|cl.2|para|
|----|---| --- |
| a | 3 | t |
| a | 3 | f |
| b | 2 | t |
| a | 1 | b |
| c | 4 | t |
| b | 7 | d |
i want to get the max value for each element in table1 from table2
and the different parameter
so the expecited tabel should be like this
|cl.1|max|para|
|----|---| --- |
| a | 3 | t |
| a | 3 | f |
| c | 4 | t |
| b | 7 | d |
You can try to compute all the maximums:
with Maxes as (
select cl1,
max(cl2) as cl2
from Table2
group by cl1)
and then join them with the original Table2, e.g.
with Maxes as (
select cl1,
max(cl2) as cl2
from Table2
group by cl1)
select t.*
from Table2 t join
Maxes m on (t.cl1 = m.cl1 and t.cl2 = m.cl2)
Depends on what features your RDBMS supports.
With Oracle you could do a CROSS APPLY to order table2 by descending cl2 and keep the top values (with ties):
select T1.c1, TM.maximum, TM.para
from Table1 T1
cross apply (
select *
from Table2 T2
where T2.c1 = T1.c1
order by T2.maximum descending
fetch first 1 row with ties
) TM
You can do the same in SQL Server with syntax select top 1 with ties instead of fetch first 1 row with ties.
Another option could be to use Analytical Functions to rank the results per col1 and then keep only the first ones.
select T.c1, T.maximum, T.para
from (
select
T1.c1, T2.maximum, T2.para,
rank() over (partition by T1.c1 order by T2.maximum desc) r
from T1
join T2 on T1.c1 = T2.c2
) T
where T.r = 1
Less stylish and probably(?) less performant would be computing the maximum for each c1 and then doing an equality:
select T1.c1, T2.maximum, T2.para
from T1
join T2 on T1.c1 = T2.c1
where T2.maximum = (select max(maximum) from T2 where c1 = T1.c1)
If you are trying to get the max tl.1 and if for the same values it is equal, you could try:
SELECT *
FROM table2
WHERE cl_2 in ( SELECT MAX(cl_2)
FROM table2
group by cl_1
);
Result:
cl_1 cl_2 para
a 3 t
a 3 f
c 4 t
b 7 d
Tested on MySQL : https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=42a6bc20622a210b18101588540995ec
You could use a join , but it makes no difference:
SELECT t1.cl_1,t2.cl_2,t2.para
FROM table2 t2
INNER JOIN table1 t1 on t2.cl_1=t1.cl_1
WHERE t2.cl_2 in (SELECT MAX(cl_2) FROM table2 group by cl_1 );
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=4b2eed9bcee3532cc7c4e7b3862bc3ef
DENSE_RANK can be used to get whole rows that have a maximum of something within a partition.
Because when sorted descending, the top 1 will have rank 1.
select cl_1, cl_2, para
from
(
select cl_1, cl_2, para
, dense_rank() over (partition by cl_1 order by cl_2 desc) as rnk
from table1 t1
join table2 t2 using (cl_1)
) q
where rnk = 1
Use a CTE to get the max values, then select the rows with those values:
with maxes as
(
select t1.[cl.1]
, max(t2.[cl.2]) max_val
from table1 t1
inner join table2 t2
on t1.[cl.1] = t2.[cl.1]
group by t1.[cl.1]
)
select t1.[cl.1]
, t2.[cl.2]
, t2.para
from table1 t1
inner join table2 t2
on t1.[cl.1] = t2.[cl.1]
where t2.[cl.2] = (select m.max_val from maxes m where m.[cl.1] = t1.[cl.1])
This can also be achieved by joining the CTE:
with maxes as
(
select t1.[cl.1]
, max(t2.[cl.2]) max_val
from table1 t1
inner join table2 t2
on t1.[cl.1] = t2.[cl.1]
group by t1.[cl.1]
)
select t1.[cl.1]
, t2.[cl.2]
, t2.para
from table1 t1
inner join table2 t2
on t1.[cl.1] = t2.[cl.1]
inner join maxes m
on t2.[cl.2] = m.max_val

Filter rows and select in to another columns in SQL?

I have a table like below.
If(OBJECT_ID('tempdb..#temp') Is Not Null)
Begin
Drop Table #Temp
End
create table #Temp
(
Type int,
Code Varchar(50),
)
Insert Into #Temp
SELECT 1,'1'
UNION
SELECT 1,'2'
UNION
SELECT 1,'3'
UNION
SELECT 2,'4'
UNION
SELECT 2,'5'
UNION
SELECT 2,'6'
select * from #Temp
And would like to get the below result.
Type_1
Code_1
Type_2
Code_2
1
1
2
4
1
2
2
5
1
3
2
6
I have tried with union and inner join, but not getting desired result. Please help.
You can use full outer join and cte as follows:
With cte as
(Select type, code,
Row_number() over (partition by type order by code) as rn
From your_table t)
Select t1.type, t1.code, t2.type, t2.code
From cte t1 full join cte t2
On t1.rn = t2.rn and t1.type =1 and t2.type = 2
Here is a query which will produce the output you expect:
WITH cte AS (
SELECT t.[Type], t.Code
, rn = ROW_NUMBER() OVER (PARTITION BY t.[Type] ORDER BY t.Code)
FROM #Temp t
)
SELECT Type_1 = t1.[Type], Code_1 = t1.Code
, Type_2 = t2.[Type], Code_2 = t2.Code
FROM cte t1
JOIN cte t2 ON t1.rn = t2.rn AND t2.[Type] = 2
AND t1.[Type] = 1
This query is will filter out any Type_1 records which do not have a Type_2 record. This means if there are an uneven number of Type_1 vs Type_2 records, the extra records will get eliminated.
Explanation:
Since there is no obvious way to join the two sets of data, because there is no shared key between them, we need to create one.
So we use this query:
SELECT t.[Type], t.Code
, rn = ROW_NUMBER() OVER (PARTITION BY t.[Type] ORDER BY t.Code)
FROM #Temp t
Which assigns a ROW_NUMBER to every row...It restarts the numbering for every Type value, and it orders the numbering by the Code.
So it will produce:
| Type | Code | rn |
|------|------|----|
| 1 | 1 | 1 |
| 1 | 2 | 2 |
| 1 | 3 | 3 |
| 2 | 4 | 1 |
| 2 | 5 | 2 |
| 2 | 6 | 3 |
Now you can see that we have assigned a key to each row of Type 1's and Type 2's which we can use for the joining process.
In order for us to re-use this output, we can stick it in a CTE and perform a self join (not an actual type of join, it just means we want to join a table to itself).
That's what this query is doing:
SELECT *
FROM cte t1
JOIN cte t2 ON t1.rn = t2.rn AND t2.[Type] = 2
AND t1.[Type] = 1
It's saying, "give me a list of all Type 1 records, and then join all Type 2 records to that using the new ROW_NUMBER we've generated".
Note: All of this works based on the assumption that you always want to join the Type 1's and Type 2's based on the order of their Code.
You can also do this using aggregation:
select max(case when type = 1 then type end) as type_1,
max(case when type = 1 then code end) as code_1,
max(case when type = 2 then type end) as type_2,
max(case when type = 2 then code end) as code_2
from (select type, code,
row_number() over (partition by type order by code) as seqnum
from your_table t
) t
group by seqnum;
It would be interesting to know which is faster -- a join approach or aggregation.
Here is a db<>fiddle.

Create table with values from one column and another column without intersection

I have a table like so:
userid | clothesid
-------|-----------
1 | 1
1 | 3
2 | 1
2 | 4
2 | 5
What I want from this table is a table like so:
userid | clothesid
-------|-----------
1 | 4
1 | 5
2 | 3
How can I do this?
I've tried it with one entry as:
select distinct r.clothesid from table r where r.clothes not in (select r1.clothes from table r1 where r1.userid=1);
and this returns 4,5, but I'm not sure where to proceed from here
You can cross join the list of userids and the list of clothesid to generate all combinations, and then use not exists on the original table to identify the missing rows:
select u.userid, c.clothesid
from (select distinct userid from mytable) u
cross join (select distinct clothesid from mytable) c
where not exists(
select 1 from mytable t on t.userid = u.userid and t.clothesid = c.clothesid
)
I think you want:
select (case when t1.clothesid is not null then 2 else 1 end),
coalesce(t1.clothesid, t2.clothesid)
from (select t.*
from t
where t.userid = 1
) t1 full join
(select t.*
from t
where t.userid = 2
) t2
on t1.clothesid = t2.clothesid
where t1.clothesid is null or t2.clothesid is null;
Actually, I think I have a simpler solution:
select (case when min(t.userid) = 1 then 2 else 1 end), clothesid
from t
group by clothesid
having count(*) = 1;
Here is a db<>fiddle.
Left join all the combinations of userid and clothesid to the table and return only the unmatched rows:
select t1.userid, t2.clothesid
from (select distinct userid from tablename) t1
cross join (select distinct clothesid from tablename) t2
left join tablename t on t.userid = t1.userid and t.clothesid = t2.clothesid
where t.userid is null
Or with the operator EXCEPT:
select t1.userid, t2.clothesid
from (select distinct userid from tablename) t1
cross join (select distinct clothesid from tablename) t2
except
select userid, clothesid
from tablename
See the demo.
Results:
> userid | clothesid
> -----: | --------:
> 1 | 4
> 1 | 5
> 2 | 3

What would be the best way to write a query to produce a table given the following data?

I have a table that contains the following data:
ADD_Col Data OrderId Output NEW_ADD Col1 Col2
----- ------ ------- -----> ------- -------- -------
AD*A*1 A 96 A 1 2
AD*A*1 B 95 B 1 1
AD*A*1 C 94 C 0.8 1
AD*A*1 D 93 D 5 2
AD*A*2 1 92
AD*A*2 1 91
AD*A*2 0.8 90
AD*A*2 5 89
AD*A*3 2 88
AD*A*3 1 87
AD*A*3 1 86
AD*A*3 2 85
This data is all in the same table and I need to link each letter to each factor. I was thinking of doing a ROW_NUMBER() and joining based on the respective row number and assign my letter the same number either that or DENSERANK. What would be the best way to achieve this? If you can please provide query examples that would be great thanks.
Seems like what you need to do is normalise your data here. Here I use PARSENAME to get the "column Number", and then ROW_NUMBER to number the relevant rows in the groups. Finally I use a Cross tab to Pivot to data:
WITH CTE AS(
SELECT V.[Key],
V.data,
V.[Order],
PARSENAME(REPLACE(V.[Key],'*','.'),1) AS ColNo,
ROW_NUMBER() OVER (PARTITION BY V.[Key] ORDER BY V.[Order] DESC) AS RN
FROM (VALUES('AD*A*1','A',96),
('AD*A*1','B',95),
('AD*A*1','C',94),
('AD*A*1','D',93),
('AD*A*2','1',92),
('AD*A*2','1',91),
('AD*A*2','0.8',90),
('AD*A*2','5',89),
('AD*A*3','2',88),
('AD*A*3','1',87),
('AD*A*3','1',86),
('AD*A*3','2',85))V([Key],[data],[Order]))
SELECT MAX(CASE C.ColNo WHEN '1' THEN C.[data] END) AS New_ADD,
MAX(CASE C.ColNo WHEN '2' THEN C.[data] END) AS Col1,
MAX(CASE C.ColNo WHEN '3' THEN C.[data] END) AS Col2
FROM CTE C
GROUP BY C.RN;
For your sample data this will work:
with cte as (
select *,
row_number() over (partition by [key] order by [OrderId desc]) rn,
dense_rank() over (order by [key]) rk
from tablename
)
select t1.data,
max(case when t2.rk = 2 then t2.data end) col1,
max(case when t2.rk = 3 then t2.data end) col2
from (select * from cte where rk = 1) t1
inner join (select * from cte where rk in (2, 3)) t2
on t2.rn = t1.rn
group by t1.data
See the demo.
Results:
> data | col1 | col2
> :--- | :--- | :---
> A | 1 | 2
> B | 1 | 1
> C | 0.8 | 1
> D | 5 | 2
select t1.Data "Key"
, t2.Data "Col1"
, t3.Data "Col2"
from ((SELECT Data,
row_number() over (order by Key_C) rn
from my_table
where Key_C = 'AD*A*1') t1
left join
(SELECT Data,
row_number() over (order by Key_C) rn
from my_table
where Key_C = 'AD*A*2') t2
on t1.rn = t2.rn
left join
(SELECT Data,
row_number() over (order by Key_C) rn
from my_table
where Key_C = 'AD*A*3') t3
on t2.rn = t3.rn);
Here is the DEMO
DROP TABLE IF EXISTS #RawData
SELECT
[ADD_Col]
,[Data]
,[OrderId]
,REPLACE([ADD_Col], 'AD*A*', '') AS [Level]
,DENSE_RANK() OVER (PARTITION BY [ADD_Col] ORDER BY [OrderId] DESC) AS [Grouping]
INTO
#RawData
FROM
[SourceTable]
SELECT
rd.[Data]
,rdc1.[Data] AS [Col1]
,rdc2.[Data] AS [Col2]
FROM
#RawData AS rd
LEFT OUTER JOIN #RawData AS rdc1
ON rdc1.[Level] = 2
AND rd.[Grouping] = rdc1.[Grouping]
LEFT OUTER JOIN #RawData AS rdc2
ON rdc2.[Level] = 3
AND rd.[Grouping] = rdc2.[Grouping]
WHERE
rd.[Level] = 1

Is it possible to JOIN a table on the TOP 1 if there is no unique identifier?

For Example
SELECT
a.SomethingInCommon,
tbl1.Status AS Status1,
tbl2.Status AS Status2,
tbl3.Status AS Status3
FROM Maintable a
LEFT OUTER JOIN SecondTable tbl1 ON
tbl1.ID = (SELECT TOP 1 ID
FROM SecondTable SomethingInCommon = a.SomethingInCommon)
LEFT OUTER JOIN SecondTable tbl2 ON
tbl2.ID = (SELECT TOP 1 ID
FROM SecondTable WHERE SomethingInCommon = a.SomethingInCommon
AND ID NOT IN (SELECT TOP 1 ID
FROM SecondTABLE
WHERE SomethingInCommon = a.SomethingInCommon))
LEFT OUTER JOIN SecondTable tbl3 ON
tbl23.ID = (SELECT TOP 1 ID
FROM SecondTable
WHERE SomethingInCommon = a.SomethingInCommon
AND ID NOT IN (SELECT TOP 2 ID
FROM SecondTABLE WHERE SomethingInCommon = a.SomethingInCommon))
This query joins SecondTable three times to show a record like
SomethingInCommon | Status1 | Status2 | Status 3
Is there anyway to accomplish these results if SecondTable does not have the unique identifier column (ID) ?
Perhaps maybe creating a temporary unique ID on the fly?
If you don't have IDs but know the order you want, you could create artificial IDs using ROW_NUMBER() and then do your TOP 1's off of that.
WITH TEMP AS (
SELECT 3 a, 1 b UNION ALL
SELECT 2, 1 UNION ALL
SELECT 1, 1 UNION ALL
SELECT 2, 2 UNION ALL
SELECT 1, 2)
SELECT A, B, ROW_NUMBER() OVER (PARTITION BY B ORDER BY B ASC) as RowNumber FROM TEMP
;WITH TEMP AS (
SELECT 3 a, 1 b UNION ALL
SELECT 2, 1 UNION ALL
SELECT 1, 1 UNION ALL
SELECT 2, 2 UNION ALL
SELECT 1, 2)
SELECT A, B, ROW_NUMBER() OVER (ORDER BY A ASC) as RowNumber FROM TEMP
As Raphael said in the comment, this can be done with CTE like below
with cte
as
(
SELECT M.SomethingInCommon, S.ID, ROW_NUMBER() OVER ( Partition by S.SomethingInCommon ORDER BY S.ID desc) as rn
FROM Maintable M
LEFT JOIN SecondTable S
on M.SomethingInCommon = S.SomethingInCommon
)
SELECT cte.SomethingInCommon
case when rn =1 then cte.ID end as Status1,
case when rn =2 then cte.ID end as Status2,
case when rn =3 then cte.ID end as Status3
where rn <=3
If you want the top three statuses, then you can use conditional aggregation:
select m.somethingincommon,
max(case when seqnum = 1 then status end) as status1,
max(case when seqnum = 2 then status end) as status2,
max(case when seqnum = 3 then status end) as status3
from maintable m left join
(select s.*,
row_number() over (partition by s.somethingincommon order by (select NULL)) as seqnum
from secondtable
) s
on m.somethingincommon = s.somethingincommon
group by m.somethingincommon;
If you prefer, you can do this with multiple joins:
with s as (
select s.*,
row_number() over (partition by s.somethingincommon order by (select NULL)) as seqnum
from secondtable
)
select m.*, s1.status as status, s2.status as status2, s3.status as status3
from maintable m left join
s s1
on m.somethingincommon = s1.somethingincommon and
s1.seqnum = 1 left join
s s2
on m.somethingincommon = s2.somethingincommon and
s2.seqnum = 2 left join
s s3
on m.somethingincommon = s3.somethingincommon and
s3.seqnum = 3;