簡體   English   中英

Postgres數組查詢

[英]Postgres array query

(以下是對我的問題的高度簡化的描述。公司政策不允許我詳細描述實際情況。)

涉及的數據庫表是:

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

請注意,這些位置並非直接連接到產品,而只是類別。 客戶應該能夠看到可以提供他們想要購買的產品所屬的所有產品類別的位置列表。 所以,例如:

一位顧客想買一輛法拉利。 這可以從類別10 30的商店獲得。這給我們商店100,300和400但不是200。

但是,如果客戶想購買沃爾沃和蘭博基尼,可以從10類 20類商店購買。這只能為我們提供400商店。

另一位客戶想購買法拉利和沃爾沃。 他們可以從10 + 20(運動和安全)或30 + 20(紅色和安全)類別的商店獲得。

我需要的是一個postgres查詢,它接受大量產品並返回可以找到所有產品的位置。 我開始使用數組和<@運算符但很快就迷路了。 下面是一些示例SQL,它試圖獲得可以購買法拉利和蘭博基尼的商店。 它,因為它需要的位置,以滿足所有選定的車屬於所有類別無法正常工作。 它僅返回位置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);

我希望我的描述有意義。

這是查詢。 您應該pc.ProductId in (1,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演示

例如,對於一輛汽車,它將是:

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

只有Ferrary演示 沃爾沃和蘭博基尼演示

(這基本上詳細闡述了@ valex的答案,雖然我沒有意識到,直到我發布;請接受@ valex不是這個)。


這可以僅使用連接和聚合來完成。

像往常一樣構建連接樹,將位置映射到產品。 然后將其與所需產品列表(單列值行)連接,並將連接過濾為僅匹配的產品名稱。 現在,無論在何處找到該產品,您都可以獲得一行產品的位置。

現在按位置和返回位置分組,其中存在的產品數量等於我們正在尋找的數量(對於所有)。 對於任何我們省略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子句。

如果您希望顯示具有任何匹配的商店但是根據匹配數進行排序,那么您也可以對聚合進行ORDER BY

如果要列出可在該商店中找到的產品,還可以將string_agg(p."Name")SELECT值列表中。

如果您希望輸入是數組而不是值列表,只需將SELECT unnest($1)替換為VALUES (...)並將數組作為參數$1傳遞,或者用字面代替$1

答案:(我將在獲得所需結果時添加答案)

對於你的第一個問題:

一位顧客想買一輛法拉利。 這可以從類別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

第二個問題:

但是,如果客戶想購買沃爾沃和蘭博基尼,可以從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)

使用INTERSECT的第二個問題的結果:intersect將交叉引用每次可以找到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語句並使用所需的產品ID SQLFIDDLE創建一個新的選擇: http ://sqlfiddle.com/#!15 / ce97d / 15

嗯,這里很難完全避免數組,但我認為我找到了一個陣列函數較少的解決方案。

我沒有選擇所需的位置,而是排除了無效的位置。

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雙seq掃描。 汽車可以屬於多個類別的事實有點棘手,我用陣列解決了這個問題,但我也試圖擺脫這些。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM