Как выбрать записи, в которых все объединенные записи не соответствуют критериям - PullRequest
0 голосов
/ 29 октября 2018

У меня есть настройки со следующими таблицами (с использованием MySQL):

  • orders, которых много:
  • соединительный стол order_items, который имеет один из:
  • products таблица

Я написал запрос для выбора orders, где все их products имеют определенный type:

SELECT orders.* FROM orders 
INNER JOIN order_items ON order_items.order_id = orders.id   
INNER JOIN products ON products.id = order_items.product_id     
WHERE products.type = 'FooProduct'
AND (
  NOT EXISTS (
    SELECT null
    FROM products
    INNER JOIN order_items ON order_items.product_id = products.id
    WHERE order_items.order_id = orders.id
    AND products.type != 'FooProduct'
  )
 )

Я запускаю подобное пару раз: сначала для получения заказов, состоящих из всех FooProduct с, и снова для получения заказов со всеми BarProduct с.

Моим камнем преткновения было создание третьего запроса для получения всех других заказов, т. Е. Когда все типы их продуктов не являются исключительно FooProduct с или исключительно BarProduct с (то есть смесь двух или другого продукта). типов).

Итак, мой вопрос: как я могу получить все записи, где все типы продуктов не являются исключительно FooProduct с или исключительно BarProduct.


Вот небольшой пример данных, из которых я хотел бы вернуть заказы с идентификаторами 3 и 4:

- orders
id
 1
 2
 3
 4

-- order_items

id order_id product_id
 1        1          1
 2        1          1
 3        2          2
 4        2          2
 5        3          3
 6        3          4
 7        4          1
 8        4          2

-- products
id type
 1 'FooProduct'
 2 'BarProduct'
 3 'OtherProduct'
 4 'YetAnotherProduct'

Я пытался это сделать, ужасно, поэтому поместил в качестве подтекста следующее вместо существующего AND (даже с синтаксисом нет):

NOT HAVING COUNT(order_items.*) = (
  SELECT null
        FROM products
        INNER JOIN order_items ON  order_items.product_id = products.id
        WHERE order_items.order_id = orders.id
        AND products.type IN ('FooProduct', 'BarProduct')
)

Ответы [ 5 ]

0 голосов
/ 29 октября 2018

Я бы посоветовал использовать счетчик (отдельный) в объединенном подвыборе следующим образом:

SELECT orders.*
FROM orders 
inner join (
    SELECT orderid, max(products.type) as products_type
    FROM order_items
    INNER JOIN products ON products.id = order_items.product_id
    GROUP BY orderid
    -- distinct count of different products = 1 
    --    -> all order items are for the same product type
    HAVING COUNT(distinct products.type ) = 1 
    -- alternative is:
    -- min(products.type )=max(products.type )
) as tmp on tmp.orderid=orders.orderid 
WHERE 1=1
-- if you want only single type product orders for some specific product
and tmp.products_type = 'FooProduct'
0 голосов
/ 29 октября 2018

Это базовое решение, не очень эффективное, но простое:

SELECT * FROM orders WHERE id NOT IN (
    SELECT orders.id FROM orders 
    INNER JOIN order_items ON order_items.order_id = orders.id   
    INNER JOIN products ON products.id = order_items.product_id     
    WHERE products.type = 'FooProduct'
    AND (
      NOT EXISTS (
        SELECT null
        FROM products
        INNER JOIN order_items ON order_items.product_id = products.id
        WHERE order_items.order_id = orders.id
        AND products.type != 'FooProduct'
      )
 )
) AND id NOT IN (
    SELECT orders.id FROM orders 
    INNER JOIN order_items ON order_items.order_id = orders.id   
    INNER JOIN products ON products.id = order_items.product_id     
    WHERE products.type = 'BarProduct'
    AND (
      NOT EXISTS (
        SELECT null
        FROM products
        INNER JOIN order_items ON order_items.product_id = products.id
        WHERE order_items.order_id = orders.id
        AND products.type != 'BarProduct'
      )
 )
)
0 голосов
/ 29 октября 2018

Для этого можно использовать агрегацию и предложение having:

SELECT o.*
FROM orders o INNER JOIN
     order_items oi
     ON oi.order_id = o.id INNER JOIN
     products p
     ON p.id = oi.product_id   
GROUP BY o.id  -- OK assuming `id` is the primary key
HAVING SUM(p.type NOT IN ('FooProduct', 'BarProduct')) > 0;  -- at least one other product 

На самом деле, это не совсем верно. Это получает заказы, которые имеют некоторый другой продукт, но это не принимает заказы, которые являются смесью только foo и bar. Я думаю, что это получает другие:

HAVING SUM(p.type = 'FooProduct') < COUNT(*) AND
       SUM(p.type = 'BarProduct') < COUNT(*) 
0 голосов
/ 29 октября 2018

Это проблема реляционного деления.
Одним из решений для поиска заказов, в которых все продукты данного типа , является следующее:

SELECT *
FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
WHERE orders.id IN (
    SELECT order_items.order_id
    FROM order_items
    INNER JOIN products ON products.id = order_items.product_id
    GROUP BY order_items.order_id
    HAVING COUNT(CASE WHEN products.type = 'FooProduct' THEN 1 END) = COUNT(*)
)

Немного подкорректируйте вышеупомянутое, чтобы найти заказы, где все товары из списка заданных типов это:

HAVING COUNT(CASE WHEN products.type IN ('FooProduct', 'BarProduct') THEN 1 END) = COUNT(*)

И чтобы найти все заказы, где все продукты соответствуют всем типам из данного списка это:

HAVING COUNT(CASE WHEN products.type IN ('FooProduct', 'BarProduct') THEN 1 END) = COUNT(*)
AND    COUNT(DISTINCT products.type) = 2

DB Fiddle с тестами

0 голосов
/ 29 октября 2018

Вместо использования коррелированных подзапросов вы можете использовать Having и фильтрацию на основе функции условного агрегирования.

products.type IN ('FooProduct', 'BarProduct') вернет 0, если тип продукта не является ни одним из них. Мы можем использовать функцию Sum() для дальнейшей фильтрации.

Попробуйте вместо этого:

SELECT orders.order_id 
FROM orders 
INNER JOIN order_items ON order_items.order_id = orders.id   
INNER JOIN products ON products.id = order_items.product_id 
GROUP BY orders.order_id 
HAVING SUM(products.type IN ('FooProduct', 'BarProduct')) < COUNT(*)

Для случая, когда вы ищете заказы, которые имеют только FooProduct тип, вы можете использовать следующее вместо:

SELECT orders.order_id 
FROM orders 
INNER JOIN order_items ON order_items.order_id = orders.id   
INNER JOIN products ON products.id = order_items.product_id 
GROUP BY orders.order_id 
HAVING SUM(products.type <> 'FooProduct') = 0

Другой возможный подход:

SELECT orders.order_id 
FROM orders 
INNER JOIN order_items ON order_items.order_id = orders.id   
INNER JOIN products ON products.id = order_items.product_id 
GROUP BY orders.order_id 
HAVING SUM(products.type = 'FooProduct') = COUNT(*)
...