选择 EAV 方案中价格的所有产品

Posted

技术标签:

【中文标题】选择 EAV 方案中价格的所有产品【英文标题】:Select all products with prices in EAV scheme 【发布时间】:2016-06-07 19:33:08 【问题描述】:

这是一个通用的 SQL 问题。我有一个查询,它从 Products 中选择所有行以及来自其他表的额外信息。问题是它是 EAV 方案,最后一个关系以某种方式颠倒并加入中断。

要求是:

列出所有带有组的产品 如果值表中的“价格”可用,请添加此信息 明确:如果“价格”不可用,则应该有没有价格信息的产品行 产品不能重复 另外:DISTINCT 是不可能的

我有一个使用子查询过滤值的有效查询(如下),但我需要摆脱它。我只能使用连接。

SQL Fiddle : http://sqlfiddle.com/#!15/0576b/8

create table Products (
    id int primary key,
    groupId int,
    code varchar(100)
);

create table Groups (
    id int primary key,
    code varchar(100)
);

create table Values (
    id int primary key,
    productId int,
    typeId int,
    value varchar(100)
);

create table ValueTypes (
    id int primary key,
    name varchar(100)
);

insert into Products values (1, 1, 'P1');
insert into Products values (2, 2, 'P2');
insert into Groups values (1, 'C1');
insert into Groups values (2, 'C2');
insert into Values values (1, 1, 1, 'Aqua');
insert into Values values (2, 1, 2, '$5');
insert into ValueTypes values (1, 'Name');
insert into ValueTypes values (2, 'Price');

我的查询有效:

SELECT *
FROM Products p
INNER JOIN Groups g ON p.groupId = g.id
LEFT JOIN Values v ON v.productId = p.id AND v.typeId = (SELECT id FROM ValueTypes WHERE name = 'Price')

问题是,如何重写它以使用连接而不是子查询?

我试过了:

SELECT *
FROM Products p
INNER JOIN Groups g ON p.groupId = g.id
LEFT JOIN Values v ON v.productId = p.id
LEFT JOIN ValueTypes vt ON vt.id = v.typeId AND vt.name = 'Price'

但它返回重复的产品 P1。另一方面,INNER JOIN 省略了没有“价格”值的产品。

【问题讨论】:

这是 sql server 吗?数据加 1 PostgreSQL,但查询应该没关系。 【参考方案1】:

明确定义 JOIN 顺序

SELECT *
FROM Products p
INNER JOIN Groups g ON p.groupId = g.id
LEFT JOIN (  --product price
  SELECT productId, value  
  FROM Values v2
  JOIN ValueTypes vt ON vt.id = v2.typeId AND vt.name = 'Price'
) v ON v.productId = p.id;

编辑 还有 2 个 JOIN 版本。与上述版本相比,优化器产生不同的计划

SELECT *
FROM ValueTypes vt
INNER JOIN Products p ON vt.name = 'Price'
INNER JOIN Groups g ON p.groupId = g.id
LEFT JOIN Values v ON v.productId = p.id AND v.typeId = vt.id;

或略有不同的 v3

SELECT *
FROM (SELECT id FROM ValueTypes WHERE name = 'Price') vt
CROSS JOIN Products p 
INNER JOIN Groups g ON p.groupId = g.id
LEFT JOIN Values v ON v.productId = p.id AND v.typeId = vt.id

【讨论】:

你期望它比我的子查询更快吗?明天去检查;) 这似乎比我使用子查询的查询要慢一些。我的标称时间是 12,5 s +- 5%;您对子查询的 JOIN 给出 13s + 20%。但我想在某些情况下这种结构会更好。 我认为子查询在正确的地方。如果您仍然只需要 JOIN 查询,请查看更多选项,已编辑。 FROM 以 ValueTypes 开头的版本获胜 :)【参考方案2】:

你可以这样做:

SELECT p.id, p.code, g.id, g.code, 
       max(case when vt.name='Price' 
                then v.value
                else null end) as price
  FROM Products p
       LEFT JOIN Groups g ON p.groupId = g.id
       LEFT JOIN Values v ON v.productId = p.id 
       LEFT join ValueTypes vt ON v.typeId = vt.id
  group by p.id, p.code, g.id, g.code

在这里查看它的工作原理:http://sqlfiddle.com/#!15/0576b/36

【讨论】:

返回的行数没问题,但是 (1) 'Price' 不是数字,所以我只返回了 NULL; (2) 这个查询太慢了,大约慢了 180%(30 秒) 1 - 它不是价格我在查询中返回它是 v.value 根据您的要求 explicitly: if 'Price' is not available, there should be Product row without Price information 2- 它不应该那么慢,分析这样的事情我想要查看查询的解释计划,因此我可以建议一些索引,或者您可以显示具有定义索引的表结构。如果速度那么慢,则意味着您的表上没有正确的索引设置。 再次,v.value 不是数字 :) 由于分组,它很慢,我的查询使用相同的表和连接。与 Serg 的回答相同。 现在我明白你为什么说这个`v.value不是数字`聚合函数max不需要传递给它的值是一个数字,它只是一种技术当其中一个为空时取出重复的结果,这是您的情况。像这样的简单连接不应该像你说的那样太慢。正如我在之前的 cmets 中提到的,这将取决于您拥有的索引。但是没问题。您已经接受了另一个答案:) "max 不需要传递给它的值是一个数字" - 我不知道。但是,当我运行您编写的查询时,我只收到价格的空值。

以上是关于选择 EAV 方案中价格的所有产品的主要内容,如果未能解决你的问题,请参考以下文章

Symfony2 使用 EAV 返回值形成动态选择字段

EAV 是“坏的”,但类表继承的可维护性如何?

Laravel 从一张表中选择价格最低的独特产品

ubercart 中产品的多个价格?

如何加入表格并按最大日期选择 [重复]

使用 EAV 表加入/透视项目