Compare rows with condition - sql

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.

Related

SQL statement to get individual values from multiple rows from right table as columns in output

I have two tables:
Table A:
ID
1
2
3
4
5
Table B:
ID UDFNumber UDFValue
1 5 ID1sUDF5Value
1 6 ID1sUDF6Value
1 7 ID1sUDF7Value
1 8 ID1sUDF8Value
1 9 ID1sUDF9Value
2 5 ID2sUDF5Value
2 6 ID2sUDF6Value
2 7 ID2sUDF7Value
2 8 ID2sUDF8Value
2 9 ID2sUDF9Value
etc
I am trying to output the values of only UDF5 and UDF9 as columns for each row in table A.
Output I am looking for:
ID UDF5 UDF9
1 ID1sUDF5Value ID1sUDF9Value
2 ID2sUDF5Value ID2sUDF9Value
3 ID3sUDF5Value ID3sUDF9Value
etc.
What join/sql statement would produce that result? MS SQL Server.
You can use conditional aggregation:
select id,
max(case when udfnumber = 5 then udfvalue end) as udf5,
max(case when udfnumber = 9 then udfvalue end) as udf9
from tableb
where udfnumber in (5, 9)
group by id
Note that you don't need tablea to generate the result that you want - unless you want to allow rows without a match in tableb. If so:
select a.id,
max(case when b.udfnumber = 5 then b.udfvalue end) as udf5,
max(case when b.udfnumber = 9 then b.udfvalue end) as udf9
from tablea a
left join tableb b on b.id = a.id and b.udfnumber in (5, 9)
group by a.id
Finally: for just two values, joining twice is a viable alternative:
select a.id, b5.udfvalue as udf5, b9.udfvalue as udf9
from tablea a
left join tableb b5 on b5.id = a.id and b5.udfnumber = 5
left join tableb b9 on b9.id = a.id and b8.udfnumber = 9

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;

Trouble ordering GROUP BY, ORDER BY AND JOIN

i'm having trouble ordering a query.
I have this table (AttendanceLog);
ClassID | StudentPin | Status
69 1 YES
8 2 NO
10 2 NO
17 3 NO
43 5 YES
58 6 YES
and this table (Students):
STUDENTPIN | FNAME | LNAME | INTERNATIONAL
1 X X NO
2 X X YES
3 X X NO
4 X X YES
I want to find out the which INTERNATIONAL students (Fname, Lname and StudentPIN) have missed 10 or more classes (attendancelog status being no).
Currently I have this (below) which tells me the studentPIN and the number of classes attended and no attended by each student, however I am unable to join the two tables together.
SELECT
ATTENDANCELOG.studentpin,
SUM(CASE WHEN status = 'YES' THEN 1 ELSE 0 END) AS number_of_yes,
SUM(CASE WHEN status = 'NO' THEN 1 ELSE 0 END) AS number_of_no
FROM attendancelog
GROUP BY ATTENDANCELOG.studentpin
ORDER BY ATTENDANCELOG.studentpin
Thanks!
you could use a join
SELECT
ATTENDANCELOG.studentpin,
Students.FNAME,
Students.LNAME,
SUM(CASE WHEN status = 'YES' THEN 1 ELSE 0 END) AS number_of_yes,
SUM(CASE WHEN status = 'NO' THEN 1 ELSE 0 END) AS number_of_no
FROM attendancelog
INNER JOIN Students ON Students.STUDENTPIN = attendancelog.StudentPin
and INTERNATIONAL='YES'
GROUP BY ATTENDANCELOG.studentpin, Students.FNAME, Students.LNAME
ORDER BY ATTENDANCELOG.studentpin
Join on student pin, put your international = 'YES' filter in the where clause, and filter for more than 10 misses in a having clause. You can also shorten the case expressions a little:
select a.studentpin
, s.fname, s.lname, s.international
, count(case a.status when 'YES' then 1 end) as attended
, count(case a.status when 'NO' then 1 end) as missed
from attendancelog a
join students s on s.studentpin = a.studentpin
where international = 'YES'
group by s.fname, s.lname, s.international, a.studentpin
having count(case a.status when 'NO' then 1 end) > 10
order by s.fname, s.lname, a.studentpin;

How to compare two table values using PLSQL

I have to compare two tables values;
TABLE_A TABLE_B
ID TYPE ID TYPE
12345 12345 3
67891 12345 7
36524 67891 3
67891 2
67891 5
36524 3
Logic: I have to compare table_A id with Table_B id
if found 3&7
good
else found 3 only
avg
else if found 7 only
bad
These good, bad and avg should go back to table A type values.
could any one help me how to write this code in PLSQL.
Assuming that you are considering type 3 and 7 only for your calculations, you can use following merge statement, no need of PL-SQL
merge into table_a a
using (select id, case (listagg(type, ',') within group (order by type))
when '3,7' then 'Good'
when '3' then 'Avg'
when '7' then 'Bad'
else null
end new_type
from table_b
where type in (3,7)
group by id) b
on (a.id = b.id)
when matched then
update set type = new_type;
For Oracle versions prior to 11 g release 2, use following:
merge into table_a a
using (select id, case (trim(both ',' from min(decode(type, 3, 3, null))||','||min(decode(type, 7, 7, null))))
when '3,7' then 'Good'
when '3' then 'Avg'
when '7' then 'Bad'
else null
end new_type
from table_b
where type in (3,7)
group by id) b
on (a.id = b.id)
when matched then
update set type = new_type;
It has been assumed that there are unique combination of id an type in table_b.
I am interpreting what you mean as saying that you want to output 'good' when TableB contains both 3 and 7, 'avg' when it contains only 3, and so on. Here is a way to get this result:
select a.id,
(case when sum(case when b.type = 3 then 1 else 0 end) > 1 and
sum(case when b.type = 7 then 1 else 0 end) > 0
then 'good'
when sum(case when b.type = 3 then 1 else 0 end) > 1
then 'avg'
when sum(case when b.type = 7 then 1 else 0 end)
then 'bad'
end) as logic
from tableA a left outer join
tableB b
on a.id = b.id
group by a.id;

sql combine two subqueries

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 )