SQL - Select items where value equals multiple values - sql

Have a very hard time wording this question, but below is a table to illustrate my problem.
Id itemID categoryID
1 5 10
2 5 16
3 6 10
4 2 10
If I have a table setup like this, and I want to select "itemID" where categoryID equals 10 AND 16, the result should be itemID 5. A bit more context would be the user has a list of checkboxes that are the categoryID's, and if they select just categoryID 10, then itemID 5, 2 and 6 would appear. If they also select categoryID 16, then only itemID 5 would appear since it has category 10 and 16, where itemID 2 only has category 10.

This is an example of a "set-within-sets" subquery. I think the most general way to solve these is using aggregation and a having clause:
select itemID
from t
group by itemId
having sum(case when categoryID = 10 then 1 else 0 end) > 0 and
sum(case when categoryID = 16 then 1 else 0 end) > 0;
Each condition in the having clause is counting the number of rows that match one category. You can easily see how this would generalize for more categories, or to exclude a category. For instance, if you wants 10 and 16 but not 22:
select itemID
from t
group by itemId
having sum(case when categoryID = 10 then 1 else 0 end) > 0 and
sum(case when categoryID = 16 then 1 else 0 end) > 0 and
sum(case when categoryID = 22 then 1 else 0 end) = 0;

Join the table to itself:
select t1.itemID
from mytable t1
join mytable t2 on t2.itemID = t1.itemID
where t1.categoryID = 10
and t2.categoryID = 16;

Related

Oracle SQL: How to select only ID‘s which are member in specific groups?

I want to select only those ID‘s which are in specific groups.
For example:
ID GroupID
1 11
1 12
2 11
2 12
2 13
Here I want to select the ID's which are in the groups 11 and 12 but in no other groups.
So the result should show just the ID 1 and not 2.
Can someone provide a SQL for that?
I tried it with
SELECT ID FROM table
WHERE GroupID = 11 AND GroupID = 12 AND GroupID != 13;
But that didn't work.
You can use aggregation:
select id
from mytable
group by id
having min(groupID) = 11 and max(groupID) = 12
This having condition ensures that the given id belongs to groupIDs 11 and 12, and to no other group. This works because 11 and 12 are sequential numbers.
Other options: if you want ids that belong to group 11 or 12 (not necessarily both), and to no other group, then:
having sum(case when groupId in (11, 12) then 1 end) = count(*)
If numbers are not sequential, and you want ids in both groups (necessarily) and in no other group:
having
max(case when groupID = 11 then 1 end) = 1
and max(case when groupID = 12 then 1 end) = 1
and max(case when groupID in (11, 12) then 0 else 1 end) = 0
SELECT t.id FROM table t
where exists(
SELECT * FROM table
where group = 11
and t.id = id
)
and exists(
SELECT * FROM table
where group = 12
and t.id = id
)
and not exists(
SELECT * FROM table
where group = 13
and t.id = id
)
group by t.id
One method is conditional aggregation:
select id
from t
group by id
having sum(case when groupid = 1 then 1 else 0 end) > 0 and
sum(case when groupid = 2 then 1 else 0 end) > 0 and
sum(case when groupid in (1, 2) then 1 else 0 end) = 0 ;
You can use GROUP BY with HAVING and a conditional COUNT:
SELECT id
FROM table_name
GROUP BY ID
HAVING COUNT( CASE Group_ID WHEN 11 THEN 1 END ) > 0
AND COUNT( CASE Group_ID WHEN 12 THEN 1 END ) > 0
AND COUNT( CASE WHEN Group_ID NOT IN ( 11, 12 ) THEN 1 END ) = 0
Or you can use collections:
CREATE TYPE int_list IS TABLE OF NUMBER(8,0);
and:
SELECT id
FROM table_name
GROUP BY id
HAVING int_list( 11, 12 ) SUBMULTISET OF CAST( COLLECT( group_id ) AS int_list )
AND CARDINALITY( CAST( COLLECT( group_id ) AS int_list )
MULTISET EXCEPT int_list( 11, 12 ) ) = 0
(Using collections has the advantage that you can pass the collection of required values as a single bind parameter whereas using conditional aggregation is probably going to require dynamic SQL if you want to pass a variable number of items to the query.)
Both output:
| ID |
| -: |
| 1 |
db<>fiddle here
Use joins:
SELECT DISTINCT c11.ID
FROM (SELECT ID FROM WORK_TABLE WHERE GROUPID = 11) c11
INNER JOIN (SELECT ID FROM WORK_TABLE WHERE GROUPID = 12) c12
ON c12.ID = c11.ID
LEFT OUTER JOIN (SELECT ID FROM WORK_TABLE WHERE GROUPID NOT IN (11, 12)) co
ON co.ID = c11.ID
WHERE co.ID IS NULL;
The INNER JOIN between the first two subqueries ensures that rows exist for both GROUPID 11 and 12, and the LEFT OUTER JOIN and WHERE verify that there are no rows for any other GROUPIDs.
dbfiddle here

How to query within the same table

I need to query a single table based on the contents of the same table
currently i have used in within and, which is taking a lot of time to query & i know its not the smartest way
PID CID Status
1 1 1
1 2 0
1 3 1
1 4 1
1 5 1
2 1 1
2 2 1
2 3 1
2 4 0
2 5 0
from the above table i need results which satisfy the following combination
Select PID from Tablename where
(CID in (1) AND status 1)
AND
(CID in (2,3) AND status = 1)
AND
(CID in (4) AND status = 1)
AND
(CID in (5) AND status = 1)
So as per above requirement, i should get only PID 1
This should get all the PIDs which exist in all the select statements:
SELECT PID FROM TableName WHERE CID = 1 AND Status = 1
INTERSECT
SELECT PID FROM TableName WHERE CID IN (2, 3) AND Status = 1
INTERSECT
SELECT PID FROM TableName WHERE CID = 4 AND Status = 1
INTERSECT
SELECT PID FROM TableName WHERE CID = 5 AND Status = 1
You seem to want:
Select PID
from Tablename
where status = 1
group by PID
having sum(case when CID in (1) then 1 else 0 end) > 0 and
sum(case when CID in (2, 3) then 1 else 0 end) > 0 and
sum(case when CID in (4) then 1 else 0 end) > 0 and
sum(case when CID in (5) then 1 else 0 end) > 0;
Your query only tests values within a single row. The where condition can never evaluate to true.
Here is a db<>fiddle, showing that this returns PID = 1 for the data in your example.

SQL: Searching for the correct record group

Lets say that we have a Groupings table that has 2 columns: Group_ID and Item_ID. Both are ints. For a simplified example lets imagine that the Item_ID can be one of only 3 values: {1,2,3}. The Group_IDs then represent the different groups of permutations of this data. So for example:
Group_ID Item_ID
1 1
2 2
3 3
4 1
4 2
5 1
5 3
6 2
6 3
7 1
7 2
7 3
What SQL could I write that if I inputted a collection of Item_Ids it would return the Group_ID related to that collection?
So for example in the above with an input of (2,3) => 6
Edit: I want to use the solution query on a more complex table, where there are 16 different values for the Item_ID
use conditional aggregation
select group_id
from table t1
where Item_ID in(2,3)
and exists( select 1 from table t2 where t1.group_id=t2.group_id
having count(distinct t2.Item_ID)=2 )
group by group_id
having count(distinct item_id)=2
Another Sql to do this
SELECT Group_id
FROM YourTable
GROUP BY Group_id
HAVING COUNT(Item_ID) = 2
AND COUNT(CASE WHEN Item_ID = 2 THEN Item_ID END) > 0
AND COUNT(CASE WHEN Item_ID = 3 THEN Item_ID END) > 0
Assuming you have no duplicates, I would simply do:
select group_id
from t
group by group_id
having sum(case when Item_ID in (2, 3) then 1 else 0 end) = count(*) and
count(*) = 2;
This seems like the simplest approach.

Calculation filtered sets in SQL

I have the following table invoving sort of 3 sets and I'm going to calculate the count of sets in whcich there is no (TaskId = 4), How can I achieve that?
SetId TaskId
1 0
1 1
1 4
2 0
2 2
2 3
3 0
3 2
3 4
Use conditional aggregation:
SELECT SetId
FROM yourTable
GROUP BY SetId
HAVING SUM(CASE WHEN TaskId = 4 THEN 1 ELSE 0 END) = 0;
The basic idea here is to scan each SetId group of records and count the number of times which a TaskId value of 4 occurs. The HAVING clause retains only groups for which the 4 value never occurs.
Use a CASE expression to check whether the TaskId value is 4. And use SUM function with grouping SetId.
Query
select [SetId],
SUM(case [TaskId] when 4 then 0 else 1 end) as [sum]
from [your_table_name]
group by [SetId];
I think you are looking for something like
SELECT *
FROM mytable t1
WHERE t1.SetId NOT IN (SELECT t2.SetId FROM mytable t2 WHERE t2.TaskId = 4)
(select the full sets that have no TaskId=4)
or
SELECT distinct SetId
FROM mytable t1
WHERE t1.SetId NOT IN (SELECT t2.SetId FROM mytable t2 WHERE t2.TaskId = 4)
(select just the SetIds that have no TaskId=4)

Checking if the row has the max value in a group

I'm trying get to find out if a row has the max value in a group. Here's really simple example:
Data
VoteCount LocationId UserId
3 1 1
4 1 2
3 2 2
4 2 1
Pseudo-query
select
LocationId,
sum(case
when UserId = 1 /* and has max vote count*/
then 1 else 0
end) as IsUser1Winner,
sum(case
when UserId = 2 /* and has max vote count*/
then 1 else 0
end) as IsUser2Winner
from LocationVote
group by LocationID
It should return:
LocationId IsUser1Winner IsUser2Winner
1 0 1
2 1 1
I also couldn't find a way to generate dynamic column names here. What would be the simplest way to write this query?
You could also do this using a Case statement
WITH CTE as
(SELECT
MAX(VoteCount) max_votes
, LocationId
FROM LocationResult
group by LocationId
)
SELECT
A.LocationId
, Case When UserId=1
THEN 1
ELSE 0
END IsUser1Winner
, Case when UserId=2
THEn 1
ELSE 0
END IsUser2Winner
from LocationResult A
inner join
CTE B
on A.VoteCount = B.max_votes
and A.LocationId = B.LocationId
Try this:
select *
from table t
cross apply (
select max(votes) max_value
from table ref
where ref.group = t.group
)votes
where votes.max_value = t.votes
but if your table is huge and has no propriate indexes performance may be poor
Another way is to get max values by groups into table variable or temp table and then join it to original table.