Self joining table with a condition - sql

I have a table of the following type:
Table dummy1:
e_n t_s item
a t1 c
a t2 c
a t3 c
a t4 c
b p1 c
b p2 c
b p3 c
b p4 c
t1, t2, t3, t4, p1, p2, p3, p4 are timestamps in ascending order.
t1, t2, t3, t4 are timestamps in ascending order for event_name 'a'.
p1, p2, p3, p4 are timestamps in ascending order for event_name 'b'.
c is the item_number for which these events 'a' and 'b' are occurring.
I am trying to write a query for which the result should be as follows:
e_n1 e_n2 item t_s_1 t_s_2
a b c t1 p1
a b c t2 p2
a b c t3 p3
a b c t4 p4
I have tried the following code:
select l.e_n as e_n_1, m.e_n as e_n_2, l.item, l.t_s as t_s_a,
m.t_s as t_s_b from (
(select * from dummy where e_n = 'a') l
join
(select * from dummy where e_n = 'b') m
on l.item = m.item and l.t_s < m.t_s
The join l.item = m.item is needed as there are many other items c1, c2, c3 with the same structure
The result is:
e_n1 e_n2 item t_s_a t_s_b
a b c t1 p1
a b c t1 p2
a b c t1 p3
a b c t1 p4
a b c t2 p1
a b c t2 p2
a b c t2 p3
so on
How can I achieve my result in an efficient way?

select min (case when e_n = 'a' then 'a' end) as e_n1
,min (case when e_n = 'b' then 'b' end) as e_n2
,item
,min (case when e_n = 'a' then t_s end) as t_s_1
,min (case when e_n = 'b' then t_s end) as t_s_2
from (select d.*
,row_number () over (partition by item,e_n order by t_s) as rn
from dummy as d
) d
group by item
,rn
+------+------+------+-------+-------+
| e_n1 | e_n2 | item | t_s_1 | t_s_2 |
+------+------+------+-------+-------+
| a | b | c | t1 | p1 |
| a | b | c | t2 | p2 |
| a | b | c | t3 | p3 |
| a | b | c | t4 | p4 |
+------+------+------+-------+-------+

First, Sort by timestamp for every event then join on sorted table row number.
try below code.
select l.e_n as e_n_1, m.e_n as e_n_2, isnull(l.item,m.item) as item, l.t_s as t_s_a,
m.t_s as t_s_b from
(select *,(row_number() over (order by t_s)) as rn from dummy where e_n = 'a') l
full join
(select *,(row_number() over (order by t_s)) as rn from dummy where e_n = 'b') m
on l.item = m.item and l.rn=m.rn

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

How to get 2 values of same num to 2 separate columns

I got table like this:
num |type|value
--------------
1 | a | 5
1 | b | 7
3 | c | 9
2 | a | 6
2 | b | 9
and want this kind of result:
num| value (a) | value (b)
-------------------------
1 | 5 | 7
2 | 6 | 9
You can use a self-join which will also remove the rows with just one value (num = 3 in your sample data)
select t1.num, t1.value as value_a, t2.value as value_b
from the_table t1
join the_table t2 on t1.num = t2.num and t2.type = 'b'
where t1.type = 'a'
You can use GROUP BY and CASE, as in:
select
num,
max(case when type = 'a' then value end) as value_a,
max(case when type = 'b' then value end) as value_b
from t
group by num
I'd join the table on itself, once for a and once for b
SELECT a.num, a.value, b.value
FROM mytable a
JOIN mytable b ON a.num = b.num AND a.type = 'a' AND b.type = 'b'

SQL add values of rows, if columns are switched

After a join of the same table, I have a result like this:
c1 c2 count
A B 5
A C 4
B A 2
B C 2
C A 1
Now, the numbers should been added, if c1 and c2 are switched, like this:
c1 c2 count
A B 7
A C 5
B C 2
How can this be done with a query?
Using a left join to self join the table on inverse positions and returning those where c1 is less than c2, or it had no matching row. Using coalesce to add 0 when the left joined count is null.
select
t.c1
, t.c2
, t.count + coalesce(s.count,0) as count
from t
left join t as s
on t.c1 = s.c2
and t.c2 = s.c1
where t.c1 < t.c2 or s.c1 is null
rextester demo in sql server: http://rextester.com/VBQI62112
returns:
+----+----+-------+
| c1 | c2 | count |
+----+----+-------+
| A | B | 7 |
| A | C | 5 |
| B | C | 2 |
+----+----+-------+
Many databases support least() and greatest(). If they are available, you can do:
select least(c1, c2) as c1, greatest(c1, c2) as c2, sum(count) as cnt
from (<your query here>) t
group by least(c1, c2), greatest(c1, c2);
In databases that don't support these functions, you can use case.
Note: The semantics of least() and greatest() return NULL if either column is NULL, so you may need to be careful if either value could be NULL.
Perhaps join the output c1,c2 with the same c2,c1?
select t1.c1
,t1.c2
,sum(coalesce(t1.count,0), coalesce(t2.count,0))
from table t1
left join table t2
on t1.c1 = t2.c2
and t1.c2 = t2.c1
group by t1.c1, t1.c2
having t1.c1 < t1.c2
SELECT t.c1
, t.c2
, t.cnt + CASE WHEN s.cnt IS NULL THEN 0 ELSE s.cnt END as cnt
FROM t
LEFT JOIN
t as s
ON t.c1 = s.c2
AND t.c2 = s.c1
WHERE t.c1 < t.c2;

Removing string from sql

Please see below data..
Table A
AID NAME
1 A
2 B
3 C
4 D
5 E
6 F
Table B
BID AID NAME
1 1 T1
2 1 T2
3 2 T3
4 2 T4
5 3 T5
6 4 T6
7 1 T7
8 1 T8
9 2 T9
10 2 T10
11 3 T11
12 4 T12
I am using this statement
SELECT
dbo.A.NAME AS ANAME, dbo.B.NAME AS BNAME
FROM
dbo.A
LEFT OUTER JOIN
dbo.B ON dbo.A.AID = dbo.B.AID
Its returning these results:
ANAME BNAME
A T1
A T2
A T7
A T8
B T3
B T4
B T9
B T10
C T5
C T11
D T6
D T12
E NULL
F NULL
but I need following result
ANAME BNAME
A T1
T2
T7
T8
B T3
T4
T9
T10
C T5
T11
D T6
T12
E NULL
F NULL
How to remove extra names from above?
Try this
SELECT CASE WHEN ROW_NUMBER() OVER ( PARTITION BY A.NAME ORDER BY A.NAME ) = 1
THEN A.NAME
ELSE ''
END AS ANAME ,
dbo.B.NAME AS BNAME
FROM dbo.A
LEFT OUTER JOIN dbo.B ON dbo.A.AID = dbo.B.AID;
I guess something like the following could be done if absolutely needed to do in SQL, seeing as it is presentation.
SELECT CASE WHEN ROW_NUMBER() OVER ( PARTITION BY A.AID ORDER BY A.AID ) > 1
THEN ''
ELSE A.NAME
END AS ANAME ,
dbo.B.NAME AS BNAME
FROM dbo.A
LEFT OUTER JOIN dbo.B ON dbo.A.AID = dbo.B.AID;
You can use below SQL query to get your desired output:
SELECT (CASE WHEN SrNo = 1
THEN ANAME
ELSE ''
END) AS ANAME
, BNAME
FROM (
SELECT ROW_NUMBER OVER (PARTITION BY dbo.A.NAME ORDER BY dbo.B.NAME) AS SrNo,
dbo.A.NAME AS ANAME, dbo.B.NAME AS BNAME
FROM dbo.A LEFT OUTER JOIN
dbo.B ON dbo.A.AID = dbo.B.AID
) AS tbl

Joining tables without duplicate

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?