具有产品 ID 和该 ID 的多个值的表的 Sql 查询

Posted

技术标签:

【中文标题】具有产品 ID 和该 ID 的多个值的表的 Sql 查询【英文标题】:Sql query for table with product id and several values for that id 【发布时间】:2017-04-16 13:48:48 【问题描述】:

我为标题道歉,但我不知道如何用几句话更好地解释我的问题。 我有一张桌子,上面有汽车 ID、财产 ID 和财产价值。 类似的东西:

 CarID | propertyID | propertyValue
    1        1            black
    1        2            diesel
    1        3            automatic gear
    1        4            80 kW
    2        1            blue
    2        2            gasoline
    2        3            manual gear
    2        4            120 kw
    3        1            blue
    3        2            gasoline
    3        3            manual gear
    3        4            90 kw

我无法通过有效查询来获取具有属性值的汽车,例如黑色、自动齿轮和柴油。 我尝试了 intersect 和 union 但没有结果。

选择不同的 t.* 从表 t 值喜欢“蓝色”的地方 相交选择 t.* 从表 t 价值喜欢“手动齿轮” 联合选择 t.* 从表 t 我喜欢“90 kw”的值

请帮忙。 谢谢。

编辑:我必须再次道歉,但我忘了添加更多信息。我添加了除功率外所有属性都相同的情况,我想获得 90 千瓦的汽车。 数据库是 PostgreSQL。提前谢谢你。

【问题讨论】:

嗨@pep,您不应该在问题中也包含propertyID吗? 您使用的是什么数据库?您在标签中指定了很多内容。 @SandPiper Postgres 标签隐含地包含纯 SQL...除非他另有说明,否则 OP 正在使用 Postgres。 @OtoShavadze 我不知道你为什么删除了你的答案,但我投票决定取消删除它。多种方法都没有错。 @OtoShavadze 您的回答是解决问题的另一种方式,而不是蒂姆回答的一部分……也许您应该重新考虑取消删除它!无论如何,这不应该是一场只有最好的人才能生存的比赛,大量的答案可以使社区受益。 【参考方案1】:

实现您想要的结果的一种规范方法是按CarID 聚合,仅保留具有您想要的属性的记录,然后检查以确保属性的不同计数与您想要的列表中的所有属性相匹配。

WITH cte AS (
    SELECT CarID
    FROM table
    WHERE propertyValue IN ('black', 'automatic gear', 'diesel')
    GROUP BY CarID
    HAVING COUNT(DISTINCT propertyValue) = 3
)
SELECT * FROM cte

如果您想返回匹配汽车的所有记录及其所有列,则可以按如下方式使用 CTE:

SELECT *
FROM table t1
INNER JOIN cte t2
    ON t1.CarID = t2.CarID

演示在这里:

Rextester

【讨论】:

很好的答案!尽管propertyValues 在propertyID 中是唯一的,但这取决于假设。如果 propertyValue = 'black' 可以是 propertyID = 1(代表颜色)和 propertyID = 2(代表 WheelColor)的值怎么办? @GiorgosAltanis propertyID 与此查询无关,我不遵循您的逻辑。 我的意思是,现在的查询询问类似“查找具有任何值为黑色的属性和任何具有值为自动齿轮的属性的汽车”,而实际上应该是“查找具有color 属性值为黑色,gear 属性值为自动齿轮”。当然,问题本身忽略了这一点,因此答案也有道理。 @GiorgosAltanis 我现在看到了,很好。是的,也许我们应该检查键/值对,而不仅仅是值。但是 OP 的表格和最初的尝试没有显示需要/发生这种情况的迹象。 我也问过他,我认为问题中缺少一些东西(不可能是正确的......)但补救措施很简单:WHERE (PropertyId, Value) IN ((1,'black'), (3,'automatic gear'), (2,'diesel'))。你怎么看?【参考方案2】:

您在这里遇到困难的最大原因是您的表设置不当,并且没有充分利用关系数据库的潜力。而不是一个有字段的表

CarID | propertyID | propertyValue

你应该有

CarID | Color | FuelType | TransmissionType

那么你的查询就简单多了:

SELECT * FROM tablename 
WHERE  Color LIKE 'black' 
AND FuelType LIKE 'diesel' 
AND TransmissionType LIKE 'auto'

我知道这是一个与您现有设置不同的答案,但是如果您按照建议修改您的表格,您的生活将会变得更加轻松。

编辑

我根据您现有的表结构为您提出了一种不同的方法。使用 WITH 语句通过选择主键,然后使用子查询为每个单独的属性值连接同一个表,将表改造成设计更好的表。然后,从中选择:

WITH newtable AS (
SELECT ot.CarID, p1.propertyValue AS CarColor, p2.propertyValue AS FuelType, 
    p3.propertyValue AS TransType, p4.propertyValue AS EnginePower
FROM oldtable ot
LEFT JOIN (SELECT CarID, propertyValue FROM oldtable WHERE propertyID = 1) p1
    ON p1.CarID = ot.CarID 
LEFT JOIN (SELECT CarID, propertyValue FROM oldtable WHERE propertyID = 2) p2
    ON p2.CarID = ot.CarID
LEFT JOIN (SELECT CarID, propertyValue FROM oldtable WHERE propertyID = 3) p3
    ON p3.CarID = ot.CarID
LEFT JOIN (SELECT CarID, propertyValue FROM oldtable WHERE propertyID = 4) p4
    ON p4.CarID = ot.CarID)
SELECT * FROM newtable
WHERE CarColor LIKE 'blue' AND TransType LIKE 'manual' AND EnginePower LIKE '90 KW'
ORDER BY CarID

【讨论】:

我赞成,尽管这本身并不是问题的答案。这样的设计决策通常会带来巨大的痛苦,但几乎总是为时已晚! 昨晚我顿悟了如何解决这个问题,所以我修改了我的答案。 我知道数据库结构不好但原因很好。这个想法是允许客户端根据需要插入 propertyValues。今天是汽车的颜色,明天可能是导航。感谢您的意见。【参考方案3】:

一种方式

select * from (
    select your_table.*, count(*) over(partition by CarID) property_cnt 
    from your_table where propertyValue in ('black', 'diesel', 'automatic gear' )
) t where property_cnt = 3

【讨论】:

这是您可以使用的demo。不确定 OP 是否想要返回所有记录,尽管它肯定不会受到伤害。 @TimBiegeleisen - 你的意思是“所有记录”还是“所有列”? 哦,在这种情况下,你的意思是 op 只想要一行值 1 (CarID) ? 是的,这就是我读到的,虽然也许我错了。我已经更新了我的答案以涵盖这两种情况(使用与您使用的方法不同的方法......这里没有偷猎^ ^)。【参考方案4】:

为了完整起见,您确实可以使用intersect 查询获取结果,如下所示:

select t.CarID
from t
where propertyID = 1 and propertyValue = 'blue' 
intersect 
select t.CarID
from t
where propertyID = 3 and propertyValue = 'manual gear' 
intersect  
select t.CarID
from t
where propertyID = 4 and propertyValue = '90 kW'

您自己的查询的主要问题是您的 SQL 读取的是 from table t 而不是 from t。此外,您引入了union,而不是连续使用intersect

最后,我在我的查询中添加了propertyID,如果您不明白为什么,请阅读 Tim 的回答下的 cmets。

话虽如此,对于您的问题设置,我仍然更喜欢 Tim 的回答,并且几乎完全支持 SandPiper 提出的设计异议。

【讨论】:

【参考方案5】:

获取具有属性值的汽车的有效查询,例如黑色和自动齿轮和柴油

如果我们想要颜色 = 黑色、变速器 = 自动齿轮和燃料类型 = 柴油的汽车,其中,从样本数据推断颜色、变速器和燃料类型的属性 ID 为 1、2 和3个

然后我们可以对汽车的属性使用 json 聚合并检查我们的过滤条件是否是汽车属性的子集。

SELECT 
  "CarID"
, JSONB_OBJECT_AGG("propertyID", "propertyValue") properties
FROM cars
GROUP BY 1
HAVING JSONB_OBJECT_AGG("propertyID", "propertyValue") @> '"1":"black"'::JSONB

这在 posgresql 9.4+ 中有效。它返回与HAVING 子句中指定的键和值过滤器匹配的CarID。如果您只需要CarID,则可以丢弃properties

我们也可以像这样使用数组聚合和HAVING 比较来实现相同的目的。数组在 postgres 中已经存在很长时间了,而 contains 运算符 @> 在 postgres 中也存在很长时间了。这应该适用于所有最新的 postgresql 版本。

SELECT 
  "CarID"
, ARRAY_AGG(("propertyID"::INT, "propertyValue"::TEXT)) properties
FROM cars
GROUP BY 1
HAVING ARRAY_AGG(("propertyID", "propertyValue")) @> ARRAY[(1::INT,'black'::TEXT),(2,'diesel'::TEXT),(3,'automatic gear'::TEXT)];

【讨论】:

以上是关于具有产品 ID 和该 ID 的多个值的表的 Sql 查询的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 将枢轴附加到具有多个值的表

Laravel:通过另一个表从具有关联表的表中检索记录

如何在 LINQ sql 中将两个表与一个具有不同值的表连接起来?

SQL:只选择一行具有相同值的表

具有更多表的递归 SQL

如何获取 SQL 中具有 MAX 和 MIN 值的行的 ID