Выбор родительских строк на основе нескольких комбинаций столбцов из дочерней таблицы

#sql #sqlite

#sql #sqlite

Вопрос:

Учитывая следующие таблицы:

родительский:

  ---- 
| id |
 ---- 
|  1 |
|  2 |
|  3 |
 ---- 
  

свойство:

  ---- ----------- ------ ------- 
| id | parent_id | name | value |
 ---- ----------- ------ ------- 
|  1 |         1 | aaa  |     5 |
|  2 |         1 | bbb  |     2 |
|  3 |         1 | ccc  |     9 |
|  4 |         2 | aaa  |     8 |
|  5 |         2 | ccc  |     9 |
|  6 |         3 | bbb  |    10 |
|  7 |         3 | ddd  |     1 |
|  8 |         3 | ccc  |     9 |
 ---- ----------- ------ ------- 
  

В общем случае запросы имеют следующий вид:
Выберите родительские идентификаторы, если:

 * Have a property called 'xxx' with value 'yyy'                                         <---|
* and/or                                                                                    |
* Have a property called 'xxx' with value NOT equal 'yyy' OR do not have 'xxx' proprty. <---|
* and/or                                                                                    |
* Repeat -----------------------------------------------------------------------------------|
  

Так, например, запрос может выглядеть следующим образом:
Выберите родительские идентификаторы, если:

  • Имеют свойство с именем ‘bbb’ и его значение ! = 2 ИЛИ не имеют свойства ‘bbb’.
  • (и)
  • Есть свойство с именем ‘ccc’ и его значение == 9.

В приведенном выше примере результатом запроса будет:

  • 2 (не имеет свойства ‘bbb’ и имеет ‘ccc’ == 9).
  • 3 (Имеет свойство ‘bbb’ со значением != 2 и имеет ‘ccc’ == 9).

В настоящее время я делаю что-то вроде этого:

 select * from parent where
    -- use "not in" and invert check for "NOT equal or no property queries"
    id not in (select parent_id from property where name = 'bbb' and value = 2)
    and id in (select parent_id from property where name = 'ccc' and value = 9)
  

Мне было интересно, есть ли лучший подход, поскольку это может вырасти до слишком большого числа select внутри where предложения.

Ответ №1:

Вы можете объединить таблицы и использовать условную агрегацию:

 select p.id
from parent p inner join property t
on t.parent_id = p.id
group by p.id
having sum(t.name = 'bbb' and t.value = 2) = 0
   and sum(t.name = 'ccc' and t.value = 9) > 0
  

Это не обязательно с точки зрения производительности лучше, чем ваше решение, но я думаю, что оно более читабельно и лучше масштабируется, поэтому вы можете добавить больше условий.

Существует также решение с EXISTS / NOT EXISTS :

 select p.* from parent p
where not exists (select 1 from  property t where t.parent_id = p.id and t.name = 'bbb' and t.value = 2)
  and exists (select 1 from  property t where t.parent_id = p.id and t.name = 'ccc' and t.value = 9)
  

Смотрите демонстрацию.
Результаты:

 > | id |
> | -: |
> |  2 |
> |  3 |
  

Ответ №2:

Ответ Forpas — corect. Однако это можно упростить и улучшить двумя способами.

Во-первых, join для запроса не требуется. Во-вторых, SQLite поддерживает нотацию кортежей для упрощения логики. Таким образом, логика может быть выражена как:

 select parent_id
from property p
group by parent_id
having sum( (name, value) = ('ccc', 9) ) > 0 and
       sum( (name, value) = ('bbb', 2) ) = 0;
  

Вот SQL-скрипка.