【问题标题】:A better query for LEFT JOIN on two tables and getting only matching results在两个表上更好地查询 LEFT JOIN 并仅获得匹配的结果
【发布时间】:2018-04-14 23:42:05
【问题描述】:

我有一个order 表,它与两个连接表相关联:order_buyerorder_seller

表结构如下:

CREATE TABLE IF NOT EXISTS `order` (
    id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,

    -- some fields...

    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,

    PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS order_seller (
    id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    order_id INT(10) UNSIGNED NOT NULL,
    seller_id INT(10) UNSIGNED NOT NULL,

    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,

    PRIMARY KEY (id),
    CONSTRAINT fk_order_seller_order_id FOREIGN KEY (order_id) REFERENCES `order` (id) ON DELETE CASCADE,
    CONSTRAINT fk_order_seller_seller_id FOREIGN KEY (seller_id) REFERENCES user (id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS order_buyer (
    id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    order_id INT(10) UNSIGNED NOT NULL,
    buyer_id INT(10) UNSIGNED NOT NULL,

    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,

    PRIMARY KEY (id),
    CONSTRAINT fk_order_buyer_order_id FOREIGN KEY (order_id) REFERENCES `order` (id) ON DELETE CASCADE,
    CONSTRAINT fk_order_buyer_buyer_id FOREIGN KEY (buyer_id) REFERENCES user (id) ON DELETE CASCADE
);

如果我是查看我的订单历史记录的买家,我希望获得所有 order_buyer.buyer_id 等于我的用户 ID 的订单。卖家也是如此。

但是我的查询是这样的,我不知道是买方还是卖方提出了查询;我只有一个用户ID。所以我需要其中一个来匹配,如果一个匹配,那么我想收集那个订单。

以下查询不起作用,因为它给了我每一个订单:

SELECT `order`.*
FROM `order`
LEFT JOIN order_seller
    ON order_seller.seller_id = 1
    AND order_seller.order_id = order.id
LEFT JOIN order_buyer
    ON order_buyer.buyer_id = 1
    AND order_buyer.order_id = order.id
WHERE order.status = "PENDING"

下面的查询通过从连接中删除AND 并使其成为结果集上的WHERE IN 子句来解决上述问题:

SELECT `order`.*
FROM `order`
LEFT JOIN order_seller
    ON order_seller.seller_id = 1
LEFT JOIN order_buyer
    ON order_buyer.buyer_id = 1
WHERE order.status = "PENDING"
    AND order.id IN (order_buyer.order_id, order_seller.order_id)

我的问题是,是否有更好的查询来实现相同的目标?这对我来说似乎有点肮脏。感觉不自然。

【问题讨论】:

  • qq:为什么有group by却没有聚合功能?其次;你能把样本数据和你的预期结果放在一起吗?没有样本就很难分析数据。
  • 对不起,group by 是我正在做的其他事情的产物。删除了那个。将使用测试数据进行更新。

标签: mysql sql database


【解决方案1】:

你可以把这两个查询结合起来:

SELECT `order`.*
FROM `order`
LEFT JOIN order_seller
    ON order_seller.seller_id = 1
    AND order_seller.order_id = order.id
LEFT JOIN order_buyer
    ON order_buyer.buyer_id = 1
    AND order_buyer.order_id = order.id
WHERE order.status = "PENDING"
    AND order.id IN (order_buyer.order_id, order_seller.order_id)

它应该比您的第二个查询更快,但返回相同的结果。

您也可以将最后一个条件更改为

COALESCE(order_buyer.order_id, order_seller.order_id) IS NOT NULL

这与 Gordon 的第一个查询基本相同。

但是 - 这里的问题是引擎需要先读取所有挂单,然后才能按用户 ID 过滤它们。 EXISTS 子查询也会有同样的问题。

我会使用 UNION ALL 查询:

SELECT `order`.*
FROM `order`
JOIN order_seller ON order_seller.order_id = order.id
WHERE order.status = "PENDING" AND order_seller.seller_id = 1
UNION ALL
SELECT `order`.*
FROM `order`
JOIN order_buyer ON order_buyer.order_id = order.id
WHERE order.status = "PENDING" AND order_buyer.buyer_id = 1

为避免过多的代码重复,您可以使用 UNION ALL 子查询:

SELECT o.*
FROM (
    SELECT order_id FROM order_seller WHERE seller_id = 1
    UNION ALL
    SELECT order_id FROM order_buyer WHERE buyer_id = 1
) x
JOIN `order` o ON o.id = x.order_id
WHERE o.status = "PENDING"

您至少需要一个order(status) 的索引。由于主键(order.id)是其中的隐含部分,因此索引可以同时用于 JOIN-ON 和 WHERE 子句。对于其他表,order_seller(seller_id)order_buyer(buyer_id) 上的索引可能没问题。但是order_seller(seller_id, order_id)order_buyer(buyer_id, order_id) 上的复合索引会更好。

【讨论】:

  • 选择这个答案,因为它更深入并显示了多种方法的权衡。
  • 必须将 UNION ALL 更改为 UNION,否则会出现重复的用户既是买家又是卖家的情况(购买了他们自己的商品)。
【解决方案2】:

类似的东西呢

select order.*
  from order
 where order.status = 'PENDING'
   and order.id in (
     select s.order_id from order_seller s where s.seller_id = 1
     union all
     select b.order_id from order_buyer b where b.buyer_id = 1)

或使用存在更改它

select o.*
  from order o
 where o.status = 'PENDING'
   and (
     exists (select 1 
               from order_seller s 
              where s.seller_id = 1 and s.order_id = o.order_id)
     or
     exists (select 1 
               from order_buyer b 
              where b.buyer_id = 1 and b.order_id = o.order_id))

【讨论】:

    【解决方案3】:

    您可以通过添加where 子句来使用第一个版本:

    SELECT o.*
    FROM `order` o LEFT JOIN
         order_seller os
         ON os.seller_id = 1 AND
            os.order_id = o.id LEFT JOIN
         order_buyer ob
         ON ob.buyer_id = 1 AND
            ob.order_id = o.id
    WHERE o.status = 'PENDING' AND
          (os.order_id IS NOT NULL OR ob.order_id IS NOT NULL);
    

    但是,我会使用exists 编写查询:

    SELECT o.*
    FROM `order` o
    WHERE EXISTS (SELECT 1
                  FROM order_seller os
                  WHERE os.seller_id = 1 AND os.order_id = o.id
                 ) OR
          EXISTS (SELECT 1
                  FROM order_buyer ob
                  WHERE ob.buyer_id = 1 AND ob.order_id = o.id
                 )
    WHERE o.status = 'PENDING' ;
    

    为了提高性能,您需要在(order_id, buyer_id)(order_id, seller_id) 上的每个表上建立索引。

    【讨论】:

    • 很好,我喜欢 EXISTS 方法。另外,那些id不是已经用我的外键约束索引了吗?请检查包含架构的更新问题。感谢您的帮助!
    • @Lansana 。 . . MySQL(与其他数据库不同)为外键约束创建索引。这些索引可能没问题。
    • 很好,我正在使用 MySQL。再次感谢。
    • @Lansana 不-您没有这些索引。 FK 定义将创建单列索引(order_id)(seller_id)。这与复合(多列)索引(order_id, seller_id) 不同。
    • @PaulSpiegel 。 . .但是单列索引可能也会做得很好。
    猜你喜欢
    • 2016-09-10
    • 2013-03-26
    • 2017-11-16
    • 2013-11-18
    • 1970-01-01
    • 1970-01-01
    • 2015-02-24
    • 2017-03-28
    • 2015-01-19
    相关资源
    最近更新 更多