Here is a solution that will work for the following special cases as well (compared to the other answer posted already, by OldProgrammer). The first special case arises when a warehouse may appear with one of the required pairs exactly twice, while the other pair is not present. The example in my sample data is warehouse W05
, the last two rows in the sample data. The other special case (not included in my sample data) is when both pairs are present for a warehouse, but at least of them appears two or more times. (Then the count is 3 or more instead of 2, if we don't count distinct pairs.)
Unlike your earlier question, where the solution was simple - just add a distinct
directive to the count
- in this case we need a trick, since Oracle SQL does not support counting distinct tuples. Why that is, only Oracle (and perhaps the maintainers of the SQL Standard) knows; relational databases are supposed to work with tuples as the most basic building block.
So - to work around this limitation, we need to "count distinct" something that reflects the tuples, but is a single expression. It would be easy to simply concatenate the two columns, but that may lead to wrong results in some cases; indeed, 'ab' concatenated to 'cde' is the same as 'abc' concatenated to 'de' even though the tuples are different.
The standard solution to that is to concatenate a character between the columns, a character that is not likely to appear in either column. The best choice for that is one of the ASCII control characters. Historically character 35, GS
("group separator"), is used as the field separator within records. (There are also "unit separator", "record separator" etc. among the ASCII control characters).
The solution then looks like this:
select warehouse_id
from wh_at
where (attribute_id, value) in ((101, 'Red'), (201, 'XXL'))
group by warehouse_id
having count(distinct attribute_id || chr(35) || value) = 2
;
This produces the output
WAREHOUSE_ID
------------
W01
from the input data (for testing):
create table wh_at (id, warehouse_id, attribute_id, value) as
select 1, 'W01', 101, 'Red' from dual union all
select 2, 'W01', 201, 'XXL' from dual union all
select 3, 'W25', 101, 'Blue' from dual union all
select 4, 'W25', 201, 'M' from dual union all
select 5, 'W02', 106, 'XL' from dual union all
select 6, 'W02', 209, null from dual union all
select 7, 'W03', 156, 'Red' from dual union all
select 8, 'W03', 201, 'XXL' from dual union all
select 9, 'W04', 101, 'Red' from dual union all
select 10, 'W04', 201, 'S' from dual union all
select 11, 'W04', 303, 'M' from dual union all
select 12, 'W05', 101, 'Red' from dual union all
select 13, 'W05', 101, 'Red' from dual
;