NESTED GROUP BY QUERY - sql

I have below relation:
C(id, n)
I(id,g)
IB(I_id, c_id, r)
I want to list all "n" from relation "C" in which their r equals 'H' and maximum "g" equals 't'
Sample data will be as below:
C:
C_ID , N
c1 , N1
c2 , N2
c3 , N3
IB:
C_ID , I_ID , R
c1 , i1 , H
c1 , i2 , H
c1 , i3 , N max H for c1 is t and m
c2 , i1 , N
c2 , i2 , H
c2 , i3 , N max H for C2 is m
c3 , i1 , H
c3 , i2 , H
c3 , i3 , H max H for c3 is t
I:
I_ID , G
i1 , t
i2 , m
i3 , t
For this sample data c1 and c3 have maximum 'H' for 't'
Below query is one answer but seem it is not optimized and very heavy:
SELECT c1.n
FROM I ib1, C c1, I i1
WHERE i1.I_ID = ib1.I_ID
AND i1.g IN
(SELECT i.g
FROM IB ib, C c, I i
WHERE i.id = ib.I_id
AND c.id = ib.C_id
AND ib.r = 'H'
AND i.g = 't'
AND c.id = c1.id
GROUP BY i.g, c.id
HAVING COUNT(*)=(
SELECT max(COUNT(*))
FROM IB ib, C c, I i
WHERE i.id = ib.I_id
AND c.ID = ib.C_id
AND ib.r = 'H'
AND c.id = c1.id
GROUP BY i.g, c.id))
GROUP BY c1.id, c1.name;
It is appreciated to let me know what are other possible solutions and how I can optimize my query. Also it is worth to help me with my table design as well.
Thanks

If I understand correctly, with the group by we get the count per c.id,i.g, then we 'number' the rows in descending order by count and we get only the first rows with i.g='t'
SELECT DISTINCT c1.n
FROM C c1
WHERE c1.c_id IN (SELECT DISTINCT x.c_id
FROM (SELECT c_id, g, RANK () OVER (PARTITION BY c_id ORDER BY cnt DESC) rnmbr
FROM ( SELECT c.c_id, i.g, COUNT (*) cnt
FROM IB ib, C c, I i
WHERE i.i_id = ib.I_id AND c.c_ID = ib.C_id AND ib.r = 'H'
GROUP BY i.g, c.c_id)) x
WHERE g = 't' AND rnmbr = 1)

Related

SQL : select 2 consecutive rows with conditions

I have a table event with 3 columns and would like to select two consecutive rows of the same case id with certain criteria (rules) as follows. I have about 5k+ of different case id to select based on the criteria given and below is just example of 2 case id. I have part of code to try, however, got stuck because i dont know how to select both rows if conditions is met.
Rules:
If D1 follows by D3 THEN Select both rows
IfElse D1 follows by D4 THEN Select both rows
IfElse D2 follows by D1 THEN Select both rows
IfElse D2 follows by D3 THEN Select both rows
IfElse D3 follows by D2 THEN Select both rows
IfElse D3 follows by D1 THEN Select both rows
Else Do not select
Table event:
caseID D Timestamp
-----------------------------------
1 D1 T1
1 D2 T2
1 D3 T3
1 D1 T4
1 D3 T5
1 D2 T6
1 D1 T7
1 D2 T8
1 D4 T9
2 D2 T1
2 D1 T2
2 D2 T3
2 D3 T4
2 D1 T5
2 D4 T6
2 D5 T7
Expected output:
caseID D Timestamp
----------------------------------
1 D2 T2
1 D3 T3
1 D1 T4
1 D3 T5
1 D2 T6
1 D1 T7
2 D2 T1
2 D1 T2
2 D2 T3
2 D3 T4
2 D1 T5
2 D4 T6
Code I might try:
SELECT caseID, D, Timestamp
FROM event e1
INNER JOIN event e2 ON e1.caseID = e2.caseID
WHERE
CASE #D
WHEN e1.D = D1 AND e2.D = D3 THEN ?
Here's one option using lead and lag with case:
select caseid, d, timestamp
from (
select *, lead(d) over (partition by caseId order by timestamp) lead,
lag(d) over (partition by caseId order by timestamp) lag
from event
) t
where 1 = case
when d = 'D1' and lead in ('D3','D4') then 1
when d = 'D2' and lead in ('D1','D3') then 1
when d = 'D3' and lead in ('D2','D1') then 1
when d = 'D1' and lag in ('D2', 'D3') then 1
when d = 'D2' and lag in ('D3') then 1
when d = 'D3' and lag in ('D2','D1') then 1
when d = 'D4' and lag in ('D1') then 1
else 0
end
order by caseid, timestamp
Online Demo
It could be consolidate, but wanted to be as explicit as possible to define your criteria.
Due to SQL-server 2008 didn't support Lag and Lead you can write a subquery to make it.
SELECT caseID,
D,
Timestamp
FROM (
select *,(
select TOP 1 D
FROM T tt
WHERE t1.caseID = tt.caseID
and t1.Timestamp < tt.Timestamp
ORDER BY tt.Timestamp
) nextD,(
select TOP 1 D
FROM T tt
WHERE t1.caseID = tt.caseID
and t1.Timestamp > tt.Timestamp
ORDER BY tt.Timestamp desc
) pervD
from T t1
) t1
WHERE (CASE WHEN d = 'D1' and nextD in ('D3','D4') OR
d = 'D2' and nextD in ('D1','D3') OR
d = 'D3' and nextD in ('D2','D1') OR
d = 'D1' and pervD in ('D2', 'D3') OR
d = 'D2' and pervD in ('D3') OR
d = 'D3' and pervD in ('D2','D1') OR
d = 'D4' and pervD in ('D1')
THEN D END) IS NOT NULL
sqlfiddle

Joining multiple select queries on same table PostgreSql

Below is the sample table structure what I have got,
C1 C2 C3 C4
A D G X
B E H X
C F I X
select C2 as 1_C2, C3 as 1_C3 from table1 where C1 = A and C4=X
select C2 as 2_C2, C3 as 2_C3 from table1 where C1 = B and C4=X
select C2 as 3_C2, C3 as 3_C3 from table1 where C1 = C and C4=X
Above are the three select statements what I have got. Now I need to join all three select statements and get just one row as the output like,
1_C2 2_C2 3_C2 1_C3 2_C3 3_C3
D E F G H I
Saw multiple other posts but didn't match this requirement. Any help is highly appreciated.
You could use a CASE expression, combined with MAX():
select MAX(CASE WHEN C1 = 'A' THEN C2 END) as 1_C2,
MAX(CASE WHEN C1 = 'B' THEN C2 END) as 2_C2,
MAX(CASE WHEN C1 = 'C' THEN C2 END) as 3_C2,
MAX(CASE WHEN C1 = 'A' THEN C3 END) as 1_C3,
MAX(CASE WHEN C1 = 'B' THEN C3 END) as 2_C3,
MAX(CASE WHEN C1 = 'C' THEN C3 END) as 3_C3
from table1
where C1 in ('A', 'B', 'C')
and C4 = 'X';

Get specific items from partition which should not be in other partition

I have below table -
ID type group_name creation_date
1 A G1 C1
2 B G2 C2
3 C G2 C3
4 B G1 C4
I want to extract the old type items in each group, but if that type item is latest item in other partition , then i won't extract that.
So, for G1, I will have 2 items A and B where C1 > C4
For G2, I will have 2 items B and C where C2 > C3.
Ideally, B is older for group G1 and C is older for group G2
But i don't want to extract B for G1 since it is latest for G2. Hence
the output should be C only.
Could anyone help how can i achieve this ?
Query:
SELECT DISTINCT
type
FROM (
SELECT type,
rnk,
COUNT( CASE rnk WHEN 1 THEN 1 END ) OVER ( PARTITION BY type ) AS ct
FROM (
SELECT type,
RANK() OVER ( PARTITION BY group_name ORDER BY creation_date DESC ) AS rnk
FROM table_name
)
)
WHERE rnk > 1 AND ct = 0;
Output:
TYPE
----
C

Avoid multiple rows in join

Suppose that I have three tables, A, B and C:
Table A:
C1 C2 Dt
-------------
1 2 8 pm
1 2 10 pm
Table B:
C1 C2 Ind
-------------
1 2 123
1 2 456
Table C:
C1 C2 C3 C4 Ind
-------------------
1 2 a b 123
1 2 c d 123
1 2 e f 123
1 2 g h 456
As you can see, table B and C have a matching index, while A doesn't. How can I join the three tables so that the first row of A (ordered by the 'dt' column) will only match the rows in C which index is the first in B (ordered by Ind)? The same would apply for the other rows.
What I have tried is to create a simple join:
SELECT *
FROM A JOIN B
ON A.C1 = B.C1
AND A.C2 = B.C2
JOIN C ON A.C1 = C.C1
AND A.C2 = C.C2
AND B.IND = C.IND
I know this doesn't work, because each row in A will match all the rows in B and then match all the rows in C. In other words, there is no unique match.
Another approach I have thought made use of two selects:
SELECT *
FROM B JOIN (
SELECT C1, C2, C3, C4, Ind,
row_number() OVER (PARTITION BY C1, C2, ind ORDER BY C1, C2, ind) AS num_row
FROM C
) table_c
ON B.IND = table_c.IND
AND B.C1 = table_c.C1
AND B.C2 = table_c.C2
JOIN (
SELECT C1, C2, DT, row_number() OVER (ORDER BY DT) AS num_row
FROM A
) table_a
ON table_a.num_row = table_c.num_row
AND table_a.C1 = table_c.C1
AND table_a.C2 = table_c.C2
But those tables are very big and every approach that I have tried, would use multiple selects and is very slow. So I was wondering what would be the best way to do this.
Tables A and B have a one to one relationship. So joining them on a unique id based on the order of each should solve the first part of the problem.
create table newA as select rownum as uniq_id, A.* from A order by dt;
create table newB as select rownum as uniq_id, B.* from B order by ind;
select * from newA inner join newB on newA.uniq_id = newB.uniq_id;
Then with your new query, join on C.
select *
from
C
inner join (select
newB.Ind as ind
from
newA
inner join newB on newA.uniq_id = newB.uniq_id)
as sub on C.ind = sub.ind
I'm sure this could be done with temp tables or strictly in sql but that will depend on your implementation
You can use ROW_NUMBER effectively here
WITH arn
AS (SELECT a.c1,
a.c2,
"Dt",
Row_number()
over (
PARTITION BY a.c1, a.c2
ORDER BY "Dt")rn
FROM a),
brn
AS (SELECT b.c1,
b.c2,
b."Ind",
Row_number()
over (
PARTITION BY b.c1, b.c2
ORDER BY b."Ind") rn
FROM b)
SELECT *
FROM arn a
inner join brn b
ON a.c1 = b.c1
AND a.c2 = b.c2
AND a.rn = b.rn
Inner join c
ON b.c1 = c.c1
AND b.c2 = c.c2
AND b."Ind" = c."Ind"
Demo

Select from two different tables

So, I have two tables, Table R and Table N. The data in Table R is from Table N. My problem is I don't know the SELECT query that will display the result such as below, because there are 4 names, and SQL can only choose 1. Is there a query or any other way to get the result?
Table R:
Id1 Id2 Id3 Id4
1 3 5 7
2 4 6 8
Table N:
Id Name
1 A
2 B
3 C
4 D
5 E
6 F
7 G
8 H
After the SELECT statement, the result should look like this:
Name1 Name2 Name3 Name4
A C E G
B D F H
Anyway, thanks for helping. ^_^
SELECT
N1.Name AS `Name1`,
N2.Name AS `Name2`,
N3.Name AS `Name3`,
N4.Name AS `Name4`
FROM
R
INNER JOIN
N N1
ON
N1.Id = R.Id1
INNER JOIN
N N2
ON
N2.Id = R.Id2
INNER JOIN
N N3
ON
N3.Id = R.Id3
INNER JOIN
N N4
ON
N4.Id = R.Id4
select
n1.Name as Name1,
n2.Name as Name2,
n3.Name as Name3,
n4.Name as Name4
from
R
inner join N n1 on n1.id = R.id1
inner join N n2 on n2.id = R.id2
inner join N n3 on n3.id = R.id3
inner join N n4 on n4.id = R.id4
You can rename the table in the query
You can join the same table multiple times provided you rename it, so:
select N1.Name, N2.Name, N3.Name, N4.Name
from R
join N as N1 on N1.Id = Id1
join N as N2 on N2.Id = Id2
join N as N3 on N3.Id = Id3
join N as N4 on N4.Id = Id4
(of course that's equivalent to
select N1.Name, N2.Name, N3.Name, N4.Name
from R, N as N1, N as N2, N as N3, N as N4
where N1.Id = Id1 and
N2.Id = Id2 and
N3.Id = Id3 and
N4.Id = Id4
(the on clauses are just added to where and comma is join)