Sql Postgres数组查询
(以下是对我的问题的高度简化描述。公司政策不允许我详细描述实际情况。) 涉及的数据库表包括:Sql Postgres数组查询,sql,postgresql,Sql,Postgresql,(以下是对我的问题的高度简化描述。公司政策不允许我详细描述实际情况。) 涉及的数据库表包括: 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 --
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查询,它接受许多产品,并返回可以找到它们的位置。我从数组开始,答案正在进行中:(我将在得到所需结果时添加答案)
关于你的第一个问题:
一位顾客想买一辆法拉利。这可以在商店买到
第10类或第30类。这给了我们100家、300家和400家店铺,但没有
200
第二个问题:
然而,如果客户想要购买沃尔沃和兰博基尼,这一点很重要
可从10类和20类商店购买。哪只
给我们400号店
使用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创建一个新的select
SQLFIDDLE:在这里很难完全避免使用数组,但我想我找到了一个使用较少数组函数的解决方案 我没有选择需要的位置,而是排除了无效的位置
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:
这是可行的,但我仍在努力避免对位置\u类别进行双序列扫描。
汽车可能属于多个类别这一事实有点棘手,我使用数组解决了这个问题,但我也在尝试摆脱这些问题。以下是查询。您应该在(1,3)
中插入所选车辆ID的列表pc.ProductId,最后您应该将条件更正为所选车辆计数,因此如果您选择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
例如,对于一辆汽车,它将是:
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
(这基本上是对@valex的答案的详细阐述,尽管我在发布之前没有意识到这一点;请接受@valex不是这个答案)
这可以只使用连接和聚合来完成
按照常规,构建连接树,将位置映射到产品。然后将其与所需产品列表(一列值行)联接,并筛选联接以仅匹配产品名称。现在,您有一行显示了产品的位置,无论该产品位于何处
现在按地点和退货地点分组,在这些地点,现有产品的数量等于我们要寻找的数量(全部)。对于任何位置,我们省略了HAVING
过滤器,因为连接返回的任何位置行都是我们想要的
因此:
基本上就是你想要的
对于“存储任何想要的产品”查询,请删除HAVING
子句
如果要显示具有任何匹配项但基于匹配数排序的存储,则还可以使用“排序依据”
聚合
如果您想列出可在该商店找到的产品,还可以在选择值列表中添加字符串\u agg(p.“Name”)
如果希望输入是数组而不是值列表,只需将值(…)
替换为选择unest($1)
,并将数组作为参数$1
传递,或者将其逐字写入此处不需要的$1数组,所有这些都可以通过乘法连接表来实现。我按照这些思路做了一些思考,但并没有真正把它们结合起来。你能帮我提供更多的细节吗?当然,如果你把你的样本数据放到一个数据库中,我会试一试。真的很感谢你的帮助:我开始认为这不是你想要的方式,我猜你想要一个查询来完成一切?虽然这种复杂程度可能有点超出我的想象,但这正是问题所在。理想情况下,我希望能够输入任意数量的产品到这个查询中,并获得所有产品都可以找到的位置。但也许这是不可能的。在这种情况下,也许只在应用层进行过滤会更好。你怎么想?所以你的输入总是一个产品列表?不是类别?您是在动态创建sql吗?理想情况下,它应该尽可能是静态的,但动态创建也很好。避免使用数组并不是真正必要的。数据库不是很大,这不是一个经常执行的查询。因此,性能并不重要。这是一个聪明的解决办法。我不会想到像那样向后做。好吧,很高兴听到这些,因为我一直在努力提高性能和摆脱阵列:)这太复杂了,不能移植到您的项目中吗?我认为应该可以。我现在正试图把它移植过来,我会让你知道它是怎么回事。我只是在发布了我的之后才正确地理解了这一点。这是和我之前写过的内容相同的方法,并且是在更早的时候发布的;我所做的一切都不同,只是映射了产品名称,这很简单,而且计数方式也略有不同。所以这应该优先于我的答案被接受。你应该使用计数(不同的L.Id)
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
);
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
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
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);