Search code examples
mysqlsqlrelational-division

SQL to return a merged set of results


I have the following SQL:

SELECT `table1`.`value`, `table2`.* 
FROM `table2` 
INNER JOIN `table1` ON `table2`.`product_id` = `table1`.`entity_id`
WHERE `table2`.`created_at` > '2012-04-23' and 
(`table1`.`value` = 264 OR `table1`.`value` = 260)
order by order_id

Which returns a result set like this (This is only a part of the returned results):

value  order_id   ...
260    1234
260    1235
260    1236
264    1236
260    1237
260    1238
260    1239
264    1239
264    1240
260    1241

What I want is a query that will take these results and only return orders where the order_id contains both value 260 and 264. Based on this example, the end result I am looking for is

260   1236
264   1236
260   1239
264   1239

My thought is this can be done with a subset but I am not exactly sure on the details to accomplish it.


Solution

  • This can be accomplished with relational division:

    select r.order_id from (
      select 
        dividend.*  
      from your_table_or_query as dividend  -- assumes no duplicates in `dividend`; use `distinct` if there are any
      inner join divisor
      on dividend.value = divisor.value
    ) as r
    group by r.order_id
    having count(*) = (select count(*) from divisor);
    

    result:

    +----------+
    | order_id |
    +----------+
    |     1236 |
    |     1239 |
    +----------+
    2 rows in set (0.00 sec)
    

    where your query is your_table_or_query and

    select 260 as value from dual union select 264 as value from dual
    

    is divisor.

    This will return the order ids 1236 and 1239; they can then be joined to original query to get all the rows with those order ids if that's what you want.


    Full query along with insert statements:

    create table divisor (value int);
    insert into divisor values (260), (264);
    
    create table your_table_or_query (value int, order_id int);
    insert into your_table_or_query values (260, 1234), (260, 1235), (260, 1236), (264, 1236), (260, 1237), (260, 1238), (260, 1239), (264, 1239), (264, 1240), (260, 1241);
    
    
    select y.* from (
      select r.order_id from (
        select
          dividend.*
        from your_table_or_query as dividend
        inner join divisor
          on dividend.value = divisor.value
      ) as r 
      group by r.order_id
      having count(*) = (select count(*) from divisor)
    ) as quotient
    inner join your_table_or_query y
      on quotient.order_id = y.order_id;
    

    Result:

    +-------+----------+
    | value | order_id |
    +-------+----------+
    |   260 |     1236 |
    |   264 |     1236 |
    |   260 |     1239 |
    |   264 |     1239 |
    +-------+----------+
    4 rows in set (0.00 sec)