SQL Query for events that happend in a specific order - sql

I have the following table:
+--------+-------+------+--+
| Object | Event | Time | |
+--------+-------+------+--+
| Obj1 | A | 1 | |
| Obj1 | B | 3 | |
| Obj2 | A | 7 | |
| Obj2 | B | 4 | |
+--------+-------+------+--+
My goal is to get all objects that both had the event A & B with the condition that A happened first (in time). So far I only came up with the query to find me all objects that had A & B without including the time:
SELECT DISTINCT Object
FROM
(SELECT *
FROM
(SELECT *
FROM table
INNER JOIN
(SELECT Object Obj
FROM table
WHERE event LIKE '%A%' AS temp_table) ON table.Object = temp_table.Obj) AS temp_final
WHERE event LIKE '%B%') AS temp2;
So the end result would be that I get a table that includes only:
Obj1
Since this is the only Object that fulfills all criteria.
The time column is a Date stamp in real life, but for simplicity I used integers.
Thanks you for the help

If you are only tracking two events that happened one after the other, than you can solve this with a single JOIN.
This will work regardless of the number of events Obj1 has, as how you mentioned, you are only interested in A and B existing and being one after the other, respectively.
select distinct t1.object
from TABLE t1
inner join TABLE t2 on t1.object = t2.object
and t2.time > t1.time
and t1.event = 'A'
and t2.event = 'B'
Here is a sample of the result of the code:
declare #tbl table (obj varchar(10), event varchar(1), time int)
insert #tbl values ('Obj1', 'A', 1), ('Obj1', 'B', 3), ('Obj2', 'A', 7), ('Obj2', 'B', 4)
select distinct t1.obj
from #tbl t1
inner join #tbl t2 on t1.obj = t2.obj
and t2.time > t1.time
and t1.event = 'A'
and t2.event = 'B'

Here is a compact solution which should run across most RDBMS. This solution does not assume that there are only two events, and should run for any number of events.
SELECT t1.Object
FROM yourTable t1
INNER JOIN
(
SELECT Object, MIN(Time) AS Time
FROM yourTable
GROUP BY Object
) t2
ON t1.Object = t2.Object AND
((t1.Event = 'A' AND t1.Time = t2.Time) OR
t1.Event <> 'A')
GROUP BY t1.Object
HAVING COUNT(*) = 2 -- change this count to match # of events
Demo on MySQL:
SQLFiddle

Try this:
SELECT DISTINCT object
FROM yourtable t
WHERE EXISTS
(SELECT FROM yourtable t3
WHERE t3.object = t.object
AND t3.event = 'A'
AND EXISTS
(SELECT 'B'
FROM yourtbale t4
WHERE t4.object = t3.object
AND t4.event = 'B'
AND t4.time > t3.time)
)

If you are using sql-server:
SELECT
A.[Object]
, A.[Time]
, B.[Time]
FROM
(SELECT
Distinct [Object]
FROM
[table] AS A
WHERE
A.[Event] = 'A'
) AS A
CROSS APPLY
(SELECT
TOP 1 *
FROM
[table] AS B
WHERE
[Event] = 'B'
AND
B.[Object] = A.[Object]
AND
A.[Time] < B.[Time]) AS B

For SQL Server:
;with A as
(select Object, MIN(Time) as Time from table where Event='A' group by Object)
, B as
(select Object, MIN(Time) aS Time from table where Event='B' group by Object)
Select A.Object from A inner join B on B.Object=A.Object where A.Time < B.Time

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

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?

How to get an ID associated with at least all contents?

Suppose we have the database:
-----------
| A -|- B |
|----|----|
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
-----------
Where A and B is the primary key. Suppose we want to get all As that contain the elements in B of 1 and 2.
SELECT A FROM Table WHERE B = 1 AND B = 2;
The above fails because it never holds true as the query is only for a single record.
SELECT A FROM Table WHERE B = 1 OR B = 2;
Works but erroneously includes the primary key value 2, which only maps to 1 in B, and not both 1 and 2 in B.
GROUP BY solution, return all a's that have more than 1 different b value in (1,2):
select a from table
where b in (1,2)
group by a
having count(distinct b) > 1
Or, JOIN solution:
select distinct a
from (select a from table where b = 1) t1
join (select a from table where b = 2) t2
on t1.a = t2.a
Or an INTERSECT solution:
select a from table where b = 1
intersect
select a from table where b = 2
Edit:
GROUP BY query that perhaps is faster then the HAVING count distinct version:
select a from table
where b in (1,2)
group by a
having max(b) <> min(b)
You can use the group by method from jarlh or make a Join with a 'distinct':
select distinct a
from (select a from table where b = 1) t1
join (select a from table where b = 2) t2
on t1.a = t2.a
Something like this (assuming that you need to filter by the specific IDs in B.
SELECT DISTINCT A
FROM Table AS T
WHERE EXISTS (SELECT 1 from Table WHERE Table.A = T.A and B = 1)
AND EXISTS (SELECT 1 from Table WHERE Table.A = T.A and B = 2)
Try this
SELECT A
FROM Table
WHERE EXISTS (
SELECT 1
FROM Table t1
WHERE t1.A = Table.A
AND t1.B = 1
)
AND EXISTS (
SELECT 1
FROM Table t2
WHERE t2.A = Table.A
AND t2.B = 2
)
A cannot be the primary key here, since the column contains duplicates.
One possible solution:
SELECT * FROM (SELECT A, group_concat(B, ',') AS C FROM tab GROUP BY A) s WHERE s.C = "1,2";