仅当行A不存在于GROUP BY上时,如何选择行B.

时间:2014-06-10 20:54:23

标签: mysql sql phalcon

我是否经历了以下情况并且没有找到解决此问题的好方法。我正在进行API的优化,所以我正在寻找最快的解决方案。

以下描述并不完全是我正在做的事情,但我认为它代表了问题。

我们说我有一张产品表:

+----+----------+
| id |   name   |
+----+----------+
|  1 | product1 |
|  2 | product2 |
+----+----------+

我有一个每个产品的附件表,按语言分开:

+----+----------+------------+-----------------------+
| id | language | product_id |     attachment_url    |
+----+----------+------------+-----------------------+
|  1 |    bb    |      1     |     image1_bb.jpg     |
|  1 |    en    |      1     |     image1_en.jpg     |
|  1 |    pt    |      1     |     image1_pt.jpg     |
|  2 |    bb    |      1     |     image2_bb.jpg     |
|  2 |    pt    |      1     |     image2_pt.jpg     |
+----+----------+------------+-----------------------+

我打算按照请求中选择的语言获取正确的附件。如您所见,我可以为每个产品添加几个附件。我们使用Babel(bb)作为通用语言,因此每次我都没有使用正确的语言时,我应该获得babel版本。同样重要的是要考虑附件表的主键是id + language的组合。

因此,假设我尝试获取pt中的所有数据,我创建SQL查询的第一个选项是:

SELECT p.id, p.name, 
    GROUP_CONCAT( '{',a.id,',',a.attachment_url, '}' ) as attachments_list 
FROM products p 
LEFT JOIN attachments a 
    ON (a.product_id=p.id AND (a.language='pt' OR a.language='bb')) 

问题在于,通过此查询,我总是得到bb数据,而我只想在没有正确语言的附件时获取它。

我已经尝试过更改附件的子查询:

(SELECT * FROM attachments GROUP BY id ORDER BY id ASC, language DESC)

但是请求的时间加倍。

我也尝试在DISTINCT中使用GROUP_CONCAT,但只有在每行的整个结果相同时它才有效,所以它对我不起作用。

有谁知道我可以直接应用于查询的其他任何解决方案?

修改

结合@Vulcronos和@Barmar的答案使得最终解决方案至少比我最初建议的解决方案快2倍。

只是为其他正在寻找它的人添加一些上下文。我正在使用Phalcon。因为它,我把这些碎片放在一起很麻烦,因为Phalcon PHQL不支持子查询,也不支持我必须使用的其他许多东西。

对于我的方案,我必须提供大约1.2MB的JSON内容,超过2100个对象,使用自定义查询使得总请求时间比Phalcon本机关系管理方法快3倍(hasMany()hasManyToMany()等),比原始解决方案快了10倍(使用了很多find()方法)。

3 个答案:

答案 0 :(得分:1)

尝试进行两次连接而不是一次:

SELECT p.id, p.name, 
    GROUP_CONCAT( '{',COALESCE(a.id, b.id),',',COALESCE(a.attachment_url, b.attachment_url), '}' ) as attachments_list 
FROM products p 
LEFT JOIN attachments a 
    ON (a.product_id=p.id AND a.language='pt') 
LEFT JOIN attachments b
    ON (a.product_id=p.id AND a.language='bb') 

然后使用COALESCE返回b而不是a如果a不存在。如果以上操作不起作用,您也可以使用子选择。

答案 1 :(得分:1)

OR条件往往会使查询变慢,因为很难用索引对它们进行优化。尝试使用两种不同的语言单独加入。

SELECT p.id, p.name, 
    IFNULL(apt.attachment_url, abb.attachment_url) AS attachment_url
FROM products AS p
JOIN attachments AS abb ON abb.product_id = p.id
LEFT JOIN attachments AS apt ON alang.product_id = p.id AND apt.language = 'pt'
WHERE abb.language = 'bb'

这假设所有产品都有bb附件,而pt是可选的。

答案 2 :(得分:0)

我遗漏了产品的加入,因为它与此问题无关。它只需要在结果集中包含产品名称。

SELECT a.product_id, a.id, a.attachment_url FROM attachments a
WHERE a.language = ?
OR (a.language = 'bb' 
   AND NOT EXISTS
       (SELECT * FROM attachments
        WHERE language = ?
        AND id = a.id
        AND product_id = a.product_id));

注意:这样的问题通常有很多可能的解决方案。这不一定是效率最高的。