SQL query for fetching a single column with multiple values - sql

Consider the below table:
Table1
id | status
------------
1 | A
2 | B
3 | C
1 | B
4 | B
5 | C
4 | A
Desired output is 1 and 4 as they are having status as both 'A' and 'B'.
Can we write an query for this? I tried to query it using conditions like 'AND', 'UNION' and 'OR', but it did not return me the desired result.

If you want the ids with more than 1 statuses:
select id
from tablename
group by id
having count(distinct status) > 1

You can use aggregation:
select id
from t
where status in ('A', 'B')
group by id
having count(*) = 2;
If the table allows duplicates, then use count(distinct status) = 2.

Try this one, you can do without using having() as well
select
id
from
(
select
id,
count(distinct status) as cnt
from yourTable
group by
id
) val
where cnt > 1

Related

Get the min of one column but select multiple columns

I have a table as following:
ID NAME AMOUNT
______________________
1 A 3
1 B 4
2 C 18
4 I 2
4 P 9
And I want the min(Amount) for each ID but I still want to display its Name. So I want this:
ID NAME min(AMOUNT)
______________________
1 A 3
2 C 18
4 I 2
ID's can occur multiple times, Names too. I tried this:
SELECT ID, NAME, min(AMOUNT) FROM TABLE
GROUP BY ID
But of course its an error because I have to
GROUP BY ID, NAME
But then I get
ID NAME AMOUNT
______________________
1 A 3
1 B 4
2 C 18
4 I 2
4 P 9
And I understand why, it looks for the min(AMOUNT) for each combination of ID + NAME. So my question is basically, how can I select multiple column (ID, NAME, AMOUNT) and get the minimum for only one column, still displaying the others?
Im new to SQL but I cant seem to find an answer..
If you are using PostgreSQL, SQL Server, MySQL 8.0 and Oracle then try the following with window function row_number().
in case you have one id with similar amount then you can use dense_rank() instead of row_number()
Here is the demo.
select
id,
name,
amount
from
(
select
*,
row_number() over (partition by id order by amount) as rnk
from yourTable
) val
where rnk = 1
Output:
| id | name | amount |
| --- | ---- | ------ |
| 1 | A | 3 |
| 2 | C | 18 |
| 4 | I | 2 |
Second Option without using window function
select
val.id,
t.name,
val.amount
from myTable t
join
(
select
id,
min(amount) as amount
from myTable
group by
id
) val
on t.id = val.id
and t.amount = val.amount
You did not specify your db vendor. If it is luckily Postgres, the problem can be also solved without nested subquery using proprietary distinct on clause:
with t(id,name,amount) as (values
(1, 'A', 3),
(1, 'B', 4),
(1, 'W', 3),
(2, 'C', 18),
(4, 'I', 2),
(4, 'P', 9)
)
select distinct on (id, name_of_min) id
, first_value(name) over (partition by id order by amount) as name_of_min
, amount
from t
order by id, name_of_min
Just for widening knowledge. I don't recommend using proprietary features. first_value is standard function but to solve problem in simple query is still not enough. #zealous' answer is perfect.
In many databases, the most efficient method uses a correlated subquery:
select t.*
from t
where t.amount = (select min(t2.amount) from t t2 where t2.id = t.id);
In particular, this can take advantage of an index on (id, amount).

How to get count of id's with missing values that are present in observations without missing values using SQL

I have a table as shown below. The table has id observations with missing values and others with actual values. What I want to get is a count of id's only where value is missing. In this case it would be only id 4, so the count would be 1. How can I do this using SQL?
id | value
---+-------
1 | home
2 | out
3 | home
1 |
2 |
4 |
To get the count, you can do:
select count(distinct id)
from t
where not exists (select 1 from t t2 where t2.id = t.id and t2.value is not null);
Alternatively, you could use two levels of aggregation:
select count(*)
from (select id
from t
group by id
having count(value) = 0
) x;
Or, if your database supports it, except:
select count(*)
from (select id from t where value is null
except
select id from t where value is not null
) x;
except removes duplicates, so distinct is not necessary.
You can do aggregation :
select id
from table t
group by id
having count(value) = 0;
Try this-
SELECT id
FROM your_table
GROUP BY id
HAVING SUM(CASE WHEN value IS NULL OR value = '' THEN 1 ELSE 0 END) = COUNT(ID)

SQL: How to select pass rows from student table in case of re-sit exam in postgresql?

Say I have a table which has the data of students and their results.
ID Result
1 PASS
2 PASS
3 PASS
3 FAIL
4 PASS
4 FAIL
4 FAIL
5 FAIL
5 FAIL
5 FAIL
And I want to select one row for each student if they pass, I want pass rows to return but if they don't then return one fail row.
This is the expected result:
ID Result
1 PASS
2 PASS
3 PASS
4 PASS
5 FAIL
Note: Students can re-sit the exam as many times as they wish until they get a pass or give up from the exam.
Thank you.
Do a GROUP BY. Since PASS (always) is greater than FAIL, use MAX().
select ID, max(Result)
from tablename
group by ID
In this case a simple group by id to get the maximum result will do:
select
id,
max(result) result
from tablename
group by id
See the demo.
Results:
> id | result
> -: | :-----
> 1 | PASS
> 2 | PASS
> 3 | PASS
> 4 | PASS
> 5 | FAIL
Use EXISTS:
select distinct ID,
case when exists (select 1 from MyTable t2 where t1.ID = T2.ID and T2.Result = 'Pass') then 'Pass' else 'Fail' end as Result
from MyTable t1
use corelated subquery and union
select id,result from tbl
where result='pass'
union
select id,result from tbl
where not exists( select 1 from tbl t2 where t1.id=t2id
and result='pass')
or you could use row_number() analytic function
select id,result from
(select id,result,
row_number() over(partition by id order by case when result='pass' then 1 else 2 end) rn
) where rn=1
OK,a PG-style way to implement this requirement is to use array function, like below:
select
id,
case when array_agg(distinct result)::varchar[] #> array['PASS']::varchar[] then 'PASS' else 'FAIL' end as result
from
test
group by
id;
id | result
----+--------
1 | PASS
2 | PASS
3 | PASS
4 | PASS
5 | FAIL
you can achieve this with the following:
select id, max(result)
from tbl
group by id

SQL Server - group by ID if column contains a value

I have following table:
ID | NR | Status
1000 | 1 | A
1000 | 2 | A
1001 | 3 | A
1002 | 4 | A
1002 | 5 | N
1003 | 6 | N
I need to an output which groups these by ID's. The NR column can be ignored. If one of the records with those ID's contains Status A, That status will be given as result.
So my output would be:
ID | Status
1000 | A
1001 | A
1002 | A
1003 | N
Any suggestions/ideas?
Although min() is the simplest method, it is not easily generalizable. Another method is:
select id
(case when sum(case when status = 'A' then 1 else 0 end) > 0
then 'A'
else 'N' -- or whatever
end) as status
from t
group by id;
Or, if you have a table with one row per id, then I would use exists:
select ids.id,
(case when exists (select 1 from t where t.id = ids.id and t.status = 'A')
then 'A' else 'N'
end) as status
from ids;
This saves on the group by aggregation and can use an index on (id, status) for optimal performance.
Do a GROUP BY, use MIN() to pick minimum status value for each id, and A < N!
select id, min(status)
from tablename
group by id
You want exactly the records that match the predicate "If one of the records with those ID's contains Status A, that status will be given as result." ?
The query can be written simply as:
Select distinct ID, STATUS from [your working TABLE] where STATUS = 'A'.
Hope this can help.

SELECT First Group

Problem Definition
I have an SQL query that looks like:
SELECT *
FROM table
WHERE criteria = 1
ORDER BY group;
Result
I get:
group | value | criteria
------------------------
A | 0 | 1
A | 1 | 1
B | 2 | 1
B | 3 | 1
Expected Result
However, I would like to limit the results to only the first group (in this instance, A). ie,
group | value | criteria
------------------------
A | 0 | 1
A | 1 | 1
What I've tried
Group By
SELECT *
FROM table
WHERE criteria = 1
GROUP BY group;
I can aggregate the groups using a GROUP BY clause, but that would give me:
group | value
-------------
A | 0
B | 2
or some aggregate function of EACH group. However, I don't want to aggregate the rows!
Subquery
I can also specify the group by subquery:
SELECT *
FROM table
WHERE criteria = 1 AND
group = (
SELECT group
FROM table
WHERE criteria = 1
ORDER BY group ASC
LIMIT 1
);
This works, but as always, subqueries are messy. Particularly, this one requires specifying my WHERE clause for criteria twice. Surely there must be a cleaner way to do this.
You can try following query:-
SELECT *
FROM table
WHERE criteria = 1
AND group = (SELECT MIN(group) FROM table)
ORDER BY value;
If your database supports the WITH clause, try this. It's similar to using a subquery, but you only need to specify the criteria input once. It's also easier to understand what's going on.
with main_query as (
select *
from table
where criteria = 1
order by group, value
),
with min_group as (
select min(group) from main_query
)
select *
from main_query
where group in (select group from min_group);
-- this where clause should be fast since there will only be 1 record in min_group
Use DENSE_RANK()
DECLARE #yourTbl AS TABLE (
[group] NVARCHAR(50),
value INT,
criteria INT
)
INSERT INTO #yourTbl VALUES ( 'A', 0, 1 )
INSERT INTO #yourTbl VALUES ( 'A', 1, 1 )
INSERT INTO #yourTbl VALUES ( 'B', 2, 1 )
INSERT INTO #yourTbl VALUES ( 'B', 3, 1 )
;WITH cte AS
(
SELECT i.* ,
DENSE_RANK() OVER (ORDER BY i.[group]) AS gn
FROM #yourTbl AS i
WHERE i.criteria = 1
)
SELECT *
FROM cte
WHERE gn = 1
group | value | criteria
------------------------
A | 0 | 1
A | 1 | 1