sql combine two subqueries - sql

I have two tables. Table A has an id column. Table B has an Aid column and a type column. Example data:
A: id
--
1
2
B: Aid | type
----+-----
1 | 1
1 | 1
1 | 3
1 | 1
1 | 4
1 | 5
1 | 4
2 | 2
2 | 4
2 | 3
I want to get all the IDs from table A where there is a certain amount of type 1 and type 3 actions. My query looks like this:
SELECT id
FROM A
WHERE (SELECT COUNT(type)
FROM B
WHERE B.Aid = A.id
AND B.type = 1) = 3
AND (SELECT COUNT(type)
FROM B
WHERE B.Aid = A.id
AND B.type = 3) = 1
so on the data above, just the id 1 should be returned.
Can I combine the 2 subqueries somehow? The goal is to make the query run faster.

Does postgres support CTEs?
WITH counts (Counts, Type, Aid) as (
select count(type), type
from b group by Type, Aid
)
select id
from A
join Counts B1 on b1.Aid = a.id and b1.type = 1
join Counts B3 on b3.Aid = a.id and b3.type = 3
where
b1.counts = 3 and b3.counts = 1
I'd suggest comparing the execution plans, but I suspect it would be similar since everything should get collapsed before execution.

Select ...
From A
Join (
Select B.Id
, Sum ( Case When B.Type = 1 Then 1 Else 0 End ) As Type1Count
, Sum ( Case When B.Type = 3 Then 1 Else 0 End ) As Type3Count
From B
Where B.Type In(1,3)
Group By B.Id
) As Z
On Z.Id = A.Id
Where Z.Type1Count = 3
And Z.Type3Count = 1

This works in TSQL, does it work in Postgres?
SELECT A.ID
FROM A
WHERE A.ID in
(
SELECT AID
FROM B
GROUP BY AID
HAVING
SUM(CASE WHEN Type = 1 THEN 1 ELSE 0 END) = 3
OR SUM(CASE WHEN Type = 3 THEN 1 ELSE 0 END) = 1
)

Another alternative:
SELECT DISTINCT Aid FROM (
SELECT Aid,type,count(*) as n from B
GROUP BY Aid,type, ) as g
WHERE ( g.n=1 AND g.type = 3 )
OR ( g.n=3 AND g.type = 1 )
I doubt this will perform better than your original, though.
You seem to be doing the best strategy: counting only the candidate rows.
Perhaps some redundant prefiltering might help:
SELECT DISTINCT Aid FROM (
SELECT Aid,type,count(*) as n from B
WHERE g.type = 3 OR g.type = 1 -- prefilter
GROUP BY Aid,type, ) as g
WHERE ( g.n=1 AND g.type = 3 )
OR ( g.n=3 AND g.type = 1 )

Related

Select table adding columns with data depending on duplicates in other column

Imagine this data.
Id
Type
1
A
1
B
1
B
2
A
3
B
I want to select table and ad two columns turning it to this. How can i do it? (In teradata)
Id
Type
Id with both A+B
Id with only A
1
A
1
0
1
B
1
0
1
B
1
0
2
A
0
1
3
B
0
0
I'm not familiar with teradata but in standard SQL next query should be working:
SELECT
T.*,
CASE WHEN Cnt = 2 THEN 1 ELSE 0 END AS BOTH_TYPES_PRESENT,
CASE WHEN Cnt = 1 AND Type = 'A' THEN 1 ELSE 0 END AS ONLY_A_PRESENT
FROM T
LEFT JOIN (
SELECT Id, COUNT(DISTINCT Type) Cnt FROM T WHERE Type IN ('A', 'B') GROUP BY Id
) CNT ON T.Id = CNT.Id;
SQL online editor

Compare rows with condition

Edit: when 5 or 9 does not exist, i need a null value (or another flag)
I have 3 columns. SECTION, STATUS and NAME. Within a SECTION there are a maximum of 10 rows (STATUS 1 to 10). I have to compare the value of NAME for STATUS 5 and 9 within a SECTION. AND then indicate if those 2 NAMES (for STATUS 5 and 9) are the same for each SECTION.
section status name
1 5 a
1 6 a
1 9 b
2 4 c
2 5 d
2 9 d
2 10 d
3 5 e
3 10 e
Desired output
Section equalnames
1 no
2 yes
3 null/flag
select
a.section,
case
when a.name = b.name then 'YES'
when a.name <> b.name then 'NO'
when (a.name is null or b.name is null) then 'NULL' end
from
(select * from <table> where status = 5) a
full join (select * from <table> where status = 9) b
on a.section = b.section
With conditional aggregation:
SELECT section,
MAX(CASE WHEN status = 5 THEN name END) =
MAX(CASE WHEN status = 9 THEN name END) equalnames
FROM tablename
WHERE status IN (5, 9)
GROUP BY section
ORDER BY section
See the demo.
Results:
section | equalnames
------- | ----------
1 | f
2 | t
3 | null
You could try using a left join on same table for 5 and 9
select a.section, a.status s5, a.name n5, b.status b9, b.name n9
, case when a.name = b.name the yes
when a.name is null or b.name is nul the NULL
when a.name <> b.name then no end equalname
from my_table a
left join my_table b a.section = b.section and a.status =5 and b.status=9
A more optimised solution could be with cte and window function:
with cte as (
select section, count(status)over(partition by section,name order by section) count_with_same_name,
count(status)over(partition by section order by section) count_with_different_name
from tname where status in (5,9))
select section,(case when (max(count_with_same_name)=2) then 'Yes' when (max(count_with_different_name)=2) then 'No' else 'null/flag' end)
from cte
group by section
Output:
I think you just want conditional aggregation:
select section,
(case when min(case when status = 5 then name end) =
min(case when status = 9 then name end)
then 'yes'
when count(case when status in (5, 9) then status end) < 2
then 'null/flag'
else 'no'
end)
from t
group by section;
No join is needed and I would not advise one for this problem.

SQL Assign Custom values to those rows with similar IDs

|id|last|
|2 |NULL|
|2 |2018|
|3 |NULL|
|3 |NULL|
|4 |2011|
|4 |2013|
This is what my current table looks like. A new 'status' column is to be created for each 'id' that must have the below 3 values.
1 - If Similar id and only one NULL value
2 - If Similar id and no NULL value
0 - If Similar id and both NULL value
EXAMPLE: Id 2 will get 1, id 3 will be assigned 0 and id 4 will get 2. There can be only 2 similar ids in the id table (there are no 3 values of 2 or 4)
I could find the similar id, but having difficulties writing the cases
select id
from table
group by id
having count(id) = 2
We can determine the status values by using aggregation:
WITH cte AS (
SELECT id,
CASE WHEN COUNT(*) > 1 AND COUNT(CASE WHEN last IS NULL THEN 1 END) = 1
THEN 1
WHEN COUNT(*) > 1 AND COUNT(CASE WHEN last IS NULL THEN 1 END) = 0
THEN 2
WHEN COUNT(*) > 1 AND COUNT(CASE WHEN last IS NULL THEN 1 END) = COUNT(*)
THEN 0 ELSE -1 END AS status
FROM yourTable
GROUP BY id
)
SELECT t1.*, t2.status
FROM yourTable t1
INNER JOIN cte t2
ON t1.id = t2.id;
Note that I assign a status value of -1 to any id which does not meet one of the three criteria. This would include any id which only appears once, among other edge cases.
You can do it this way
select a.id, last,
case
when exists(select 1 from _table b where a.id = b.id and coalesce(b.last,0) <> coalesce(a.last,0) and (a.last is null or b.last is null))
then 1
when exists(select 1 from _table b where a.id = b.id
and coalesce(b.last,0) <> coalesce(a.last,0))
and not exists(select 1 from _table b where a.id = b.id
and b.last is null)
then 2
when exists(select 1 from _table b where a.id = b.id )
and exists(select 1 from _table b where a.id = b.id and b.last is null and a.last is null having count(*) =
(select count(*) from _table b where a.id = b.id))
then 0
end as status
from _table a
Output:
id last status
2 NULL 1
2 2018 1
3 NULL 0
3 NULL 0
4 2011 2
4 2013 2
If you want one row per id:
select id,
(case count(*) filter (value is null)
when 1 then 1
when 0 then 2
when 2 then 3
end) as status
from t
group by id;
If you want this as a column on the original data, use window functions:
select t.*,
(case count(*) filter (value is null) over (partition by id)
when 1 then 1
when 0 then 2
when 2 then 3
end) as status
from t;

Filter out entire group based on item ranking in SQL

I have a table as shown below:
group item rank
1 A 1
1 B 2
1 C 3
2 A 2
2 B 1
3 A 1
3 C 2
I want those groups data only, where item A has rank 1 as shown below:
group item rank
1 A 1
1 B 2
1 C 3
3 A 1
3 C 2
In group 2, A has rank 2, therefore not a part of output.
One way is using an IN clause
select *
from yourTable
where id in (select id from yourtable where item='A' and rank = 1)
you could use a subquery for get the involved id and the join
select * from my_table m
inner join (
select distinct id
from my_table
where item = 'A'
and rank = 1
) t on t.id = m.id

SQL Server Join Query returns redundant rows

I have two table
tblMaster tblTrans
ID Desc ID IDMaster Qty Garage
== ====== == ========= ===== =====
1 Type1 1 1 1 1
2 Type2 2 2 2 1
3 1 3 2
4 2 2 2
5 1 2 3
6 2 4 3
And i want this output when i join them :
ID Desc Garage1Qty Garagae2Qty Garage3Qty Garage4Qty
== ==== =========== =========== =========== ==========
1 Type1 1 3 2 null
2 Type2 2 2 4 null
Note that the "Garage" value is something that could be added in the future. So how do i achieve that? Tried this one:
SELECT M.*, Garage1Qty.*, Garage2Qty.* FROM tblMaster M
LEFT JOIN ( SELECT a.Id, b.Qty FROM tblMaster a JOIN tblTrans b on a.Id =b.Id WHERE Garage = 1 ) as Garage1Qty on Garage1Qty.Id = M.Id )
LEFT JOIN ( SELECT a.Id, b.Qty FROM tblMaster a JOIN tblTrans b on a.Id =b.Id WHERE Garage = 2 ) as Garage2Qty on Garage2Qty.Id = M.Id )
but it always returns something like:
ID Desc Garage1Qty Garage2Qty Garage3Qty Garage4Qty
== ==== =========== =========== =========== ==========
1 Type1 1 null null null
1 Type1 null 3 null null
1 Type1 null null 2 null
2 Type2 2 null null null
2 Type2 null 2 null null
2 Type2 null null 4 null
Well, you could use subquery
select ID, Desc,
(select sum(Qty) from tblTrans where IDMaster = m.ID and Garage = 1) as Garage1Qty,
(select sum(Qty) from tblTrans where IDMaster = m.ID and Garage = 2) as Garage2Qty,
(select sum(Qty) from tblTrans where IDMaster = m.ID and Garage = 3) as Garage3Qty,
(select sum(Qty) from tblTrans where IDMaster = m.ID and Garage = 4) as Garage4Qty
from tblMaster m;
In other way, you could also do that via conditional aggregation
select m.ID, m.Desc,
sum(case when t.Garage = 1 then t.Qty else 0 end) as Garage1Qty,
...
sum(case when t.Garage = 4 then t.Qty else 0 end) as Garage4Qty
from tblMaster m left join tblTrans t
on t.IDMaster = m.ID
group by m.ID, m.Desc;