2015-05-15 2 views
4

(Ниже приводится весьма упрощенное описание моей проблемы политика компании не позволяет мне описать реальную ситуацию в деталях.).Postgres запрос массива

таблиц БД, участвующих являются:

PRODUCTS: 
ID Name 
--------- 
1 Ferrari 
2 Lamborghini 
3 Volvo 


CATEGORIES: 
ID Name 
---------- 
10 Sports cars 
20 Safe cars 
30 Red cars 

PRODUCTS_CATEGORIES 
ProductID CategoryID 
----------------------- 
1   10 
1   30 
2   10 
3   20 

LOCATIONS: 
ID  Name 
------------ 
100  Sports car store 
200  Safe car store 
300  Red car store 
400  All cars r us 


LOCATIONS_CATEGORIES: 
LocationID CategoryID 
------------------------ 
100   10 
200   20 
300   30 
400   10 
400   20 
400   30 

Обратите внимание, что местоположения напрямую не связаны с продуктами, а только категории. Клиент должен иметь возможность видеть список местоположений, которые могут предоставить все категории продуктов, к которым принадлежат продукты, которые они хотят купить. Так, например:

Клиент хочет купить Ferrari. Это будет доступно из магазинов в категориях 10 или 30. Это дает нам магазины 100, 300 и 400, но не 200.

Однако, если клиент хочет купить Volvo и Lamborghini, это будет доступно из магазинов в категориях 10 и 20. Что только дает нам магазин 400.

Другой клиент хочет купить Ferrari и Volvo. Это они могли получить из магазина в любой категории 10 + 20 (спортивный и безопасный) или категории 30 + 20 (красный и безопасный).

Что мне нужно, это postgres-запрос, который принимает несколько продуктов и возвращает местоположения, где все они могут быть найдены. Я начал с массивов и оператора < @, но быстро потерялся. Ниже приведен пример SQL, который пытается получить магазины, где можно купить Ferrari и Lamborghini. Он работает неправильно, так как он требует, чтобы местоположения удовлетворяли все категории все выбранные автомобили принадлежат. Он возвращает только местоположение 400, но должен возвращать местоположения 400 и 100.

SELECT l.* FROM locations l 
WHERE 
(SELECT array_agg(DISTINCT(categoryid)) FROM products_categories WHERE productid IN (1,2)) 
<@ 
(SELECT array_agg(categoryid) FROM locations_categories WHERE locationid = l.id); 

Надеюсь, мое описание имеет смысл.

+0

Здесь не нужны массивы, вы можете сделать все это, умножив соединения таблиц. –

+0

Я продумал эти строки, но не смог собрать их вместе. Не могли бы вы помочь мне с более подробной информацией? –

+0

Несомненно, если вы поместите свои данные образца в http://sqlfiddle.com/, я отдам его. –

ответ

3

Вот запрос. Вы должны вставить список отобранных автомобилей Ids pc.ProductId in (1,3) и в итоге вы должны исправить состояние для выбранного количества автомобилей, поэтому, если вы выберете 1 и 3, вы должны написать HAVING COUNT(DISTINCT pc.ProductId) = 2, если вы выберете 3 машины, тогда должно быть 3. Это условие в HAVING вы условии, что все автомобили находятся в этих местах:

SELECT Id FROM Locations l 
JOIN Locations_Categories lc on l.Id=lc.LocationId 
JOIN Products_Categories pc on lc.CategoryId=pc.CategoryID 
where pc.ProductId in (1,3) 
GROUP BY l.id 
HAVING COUNT(DISTINCT pc.ProductId) = 2 

Sqlfiddle demo

Например, для одного автомобиля будет:

SELECT Id FROM Locations l 
JOIN Locations_Categories lc on l.Id=lc.LocationId 
JOIN Products_Categories pc on lc.CategoryId=pc.CategoryID 
where pc.ProductId in (1) 
GROUP BY l.id 
HAVING COUNT(DISTINCT pc.ProductId) = 1 

Only Ferrary demo Volvo and a Lamborghini demo

+0

Я только правильно понял это после публикации моей. Это и эквивалентный подход к тому, что я написал, и был опубликован ранее; все, что я сделал по-другому, сопоставил имена продуктов *, которые тривиальны, и сделал счет несколько иначе. Поэтому это следует принять в предпочтении моему ответу. –

1

ОТВЕТ НА ХОД: (Я добавлю ответы, как я получаю нужный результат)

Для вашего первого вопроса:

Клиент хочет купить Ferrari. Это будет доступно в магазинах в категории 10 или 30. Это дает нам хранит 100, 300 и 400, но не 200.

SELECT DISTINCT l.id, l.name 
FROM Products p 
LEFT JOIN Product_Categories p_c 
ON p.id = p_c.ProductId 
LEFT JOIN Categories c 
ON p_c.CategoryId = c.id 
LEFT JOIN Locations_Categories l_c 
ON c.id = l_c.CategoryId 
LEFT JOIN Locations l 
ON l_c.LocationId = l.id 
WHERE p.id = 1 

Второго вопрос:

Однако, если клиент хочет купить Volvo и Lamborghini, это будет доступно из магазинов в категориях 10 и 20. Какой только дает нам магазин 400.

SELECT DISTINCT l.id, l.name 
FROM Products p 
LEFT JOIN Product_Categories p_c 
ON p.id = p_c.ProductId 
LEFT JOIN Categories c 
ON p_c.CategoryId = c.id 
LEFT JOIN Locations_Categories l_c 
ON c.id = l_c.CategoryId 
LEFT JOIN Locations l 
ON l_c.LocationId = l.id 
WHERE l.id in (select id 
       from locations loc 
       join locations_categories locat1    
       on loc.id = locat1.LocationId 
       join locations_categories locat2 
       on loc.id = locat2.LocationId 
       where locat1.CategoryId = 10 
       AND locat2.categoryId = 20) 

РЕЗУЛЬТАТ НА ВТОРОЙ ВОПРОС USING ПЕРЕСЕЧЕНИЕ: пересекаются пересечет эталонные все магазины, где 1 продукт можно найти каждый раз:

SELECT DISTINCT l.id, l.name 
FROM Products p 
LEFT JOIN Product_Categories p_c 
ON p.id = p_c.ProductId 
LEFT JOIN Categories c 
ON p_c.CategoryId = c.id 
LEFT JOIN Locations_Categories l_c 
ON c.id = l_c.CategoryId 
LEFT JOIN Locations l 
ON l_c.LocationId = l.id 
WHERE p.id = 2 
INTERSECT 
SELECT DISTINCT l.id, l.name 
FROM Products p 
LEFT JOIN Product_Categories p_c 
ON p.id = p_c.ProductId 
LEFT JOIN Categories c 
ON p_c.CategoryId = c.id 
LEFT JOIN Locations_Categories l_c 
ON c.id = l_c.CategoryId 
LEFT JOIN Locations l 
ON l_c.LocationId = l.id 
WHERE p.id = 3 

Для каждого нового продукта вы добавляете новое заявление INTERSECT и создать новый вариант с желаемым идентификатором продукта. SQLFIDDLE: http://sqlfiddle.com/#!15/ce97d/15

+0

Я начинаю думать, что это не так, как вы этого хотите, я предполагаю, что вы хотите, чтобы 1 запрос сделал все? хотя этот уровень сложности может быть немного выше моей головы –

+0

Это как раз проблема. В идеале я хотел бы иметь возможность вводить любое количество продуктов в этот один запрос и получать места, где можно найти все продукты. Но, возможно, это невозможно. И в этом случае, возможно, лучше просто выполнить фильтрацию на прикладном уровне. Как вы думаете? –

+0

Итак, ваш вход всегда будет списком продуктов? не категории? –

1

Ну, это трудно полностью избежать массивов здесь bu Я думаю, что нашел решение с меньшими функциями массива.

Вместо того чтобы выбирать необходимые местоположения, я исключил недействительные.

WITH needed_categories AS (
    SELECT p."ID", array_agg(pc."CategoryID") AS at_least_one_should_match 
    FROM Products p 
    JOIN Products_Categories pc ON p."ID" = pc."ProductID" 
    WHERE p."ID" IN (1, 3) 
    GROUP BY p."ID" 
), 
not_valid_locations AS (
    SELECT DISTINCT lc."LocationID", unnest(nc.at_least_one_should_match) 
    FROM Locations_Categories lc 
    JOIN needed_categories nc ON NOT ARRAY[lc."CategoryID"] && nc.at_least_one_should_match 
    EXCEPT 
    SELECT * FROM Locations_Categories 
) 
SELECT * 
FROM Locations 
WHERE "ID" NOT IN (
    SELECT "LocationID" FROM not_valid_locations 
); 

Вот SQLFiddle: http://sqlfiddle.com/#!15/e138d/78

Это работает, но я все еще пытаюсь избежать двойного НомерСтарта сканирование Location_Categories. Тот факт, что автомобили могут принадлежать нескольким категориям, немного сложнее, я решил это использовать массивы, но я тоже пытаюсь избавиться от них.

+0

Избегание массивов действительно необходимо. Db не является огромным, и это не запрос, который будет выполняться часто. Таким образом, производительность не является критичной. Это умное решение. Мне не пришлось бы так делать это назад. –

+0

Ну, рад ухам обо всем этом, потому что я застреваю, пытаясь повысить производительность и избавиться от массивов :) Сложно ли это быть перенесено в ваш проект? –

+0

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

3

(В основном это конкретизирует ответ @ VAlex, хотя я не понимаю, что пока я не отвечал; пожалуйста, примите @ valex's не этот).

Это можно сделать, используя только объединения и агрегацию.

Создайте координаты соединения дерева соединений с продуктами, как обычно. Затем присоедините его к списку желаемых продуктов (строки значений с одним столбцом) и отфильтруйте соединение только для соответствия названиям продуктов. Теперь у вас есть одна строка с указанием местоположения продукта, где этот продукт можно найти.

Теперь по местоположению и месту возврата, где количество представленных продуктов равно числу, которое мы ищем (для ВСЕХ). Для ЛЮБОГО мы опускаем фильтр HAVING, потому что любая строка местоположения, возвращаемая соединением, является тем, что мы хотим.

Итак:

WITH wantedproducts(productname) AS (VALUES('Volvo'), ('Lamborghini')) 
SELECT l."ID" 
FROM locations l 
INNER JOIN locations_categories lc ON (l."ID" = lc."LocationID") 
INNER JOIN categories c ON (c."ID" = lc."CategoryID") 
INNER JOIN products_categories pc ON (pc."CategoryID" = c."ID") 
INNER JOIN products p ON (p."ID" = pc."ProductID") 
INNER JOIN wantedproducts wp ON (wp.productname = p."Name") 
GROUP BY l."ID" 
HAVING count(DISTINCT p."ID") = (SELECT count(*) FROM wantedproducts); 

является то, что вы хотите, в основном.

Для запросов «магазины с любыми запрошенными продуктами» оставьте предложение HAVING.

Вы также можете создать агрегат, если хотите показать магазины с любым совпадением, но отсортировать по количеству совпадений.

Вы также можете добавить string_agg(p."Name") в список значений SELECT, если вы хотите перечислить продукты, которые можно найти в этом магазине.

Если вы хотите, чтобы ваш вход будет массивом, а не значения-список, просто замените VALUES (...) с SELECT unnest($1) и передать свой массив в качестве параметра $1, или записать его буквально на месте $1.

+0

Вы должны использовать 'COUNT (DISTINCT L.Id)', как только один продукт может быть включен в разные категории, поэтому его можно считать более одного раза. – valex

+0

@valex Хорошая точка, но она должна быть «p.» ID «'; 'DISTINCT l." ID "' при группировке по 'l." ID "' бессмысленно. –

Смежные вопросы