【问题标题】:Joining tables and replacing null values with specified values连接表并用指定值替换空值
【发布时间】:2012-01-16 07:26:22
【问题描述】:

我正在尝试制作一个可打印的页面,其中包含指定制造商的所有销售,列出指定日期之间的所有产品。如果没有任何销售,则应显示 0。

桌子

// Manufacturer table
// mid, manufacturer
// Products table
// pid, product, ref_manufacturer_id
// Orders table
// oid, orderPrice, orderDateTime, ref_product_id

以及有效的查询(没有日期限制)

SELECT prod.product, COALESCE(COUNT(pord.oid),0) AS orderCount,
COALESCE(SUM(pord.orderPrice),0) AS orderSum
FROM product_manufacturer AS manu
JOIN product_list AS prod ON prod.ref_manufacturer_id = manu.mid
LEFT JOIN product_orders AS pord ON pord.ref_product_id = prod.pid
WHERE manu.mid = :manu_id
GROUP BY prod.product;

但是一旦我添加到 WHERE 语法中

WHERE manu.mid = :manu_id AND DATE(pord.orderDateTime) BETWEEN :orders_start AND :orders_end

我正在使用 PHP PDO 进行连接并验证 manu_id 是否为 int 并且 orders_start/end 已转换为 MySQL 日期格式。

但我想找出的问题是,是什么导致了问题,当我添加日期限制时,每个未订购的产品都不会显示在输出中?

关于创建表的 SQL

CREATE TABLE product_list (
  pid bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  product varchar(255) NOT NULL,
  ref_manufacturer_id bigint(20) unsigned NOT NULL,
  PRIMARY KEY (pid),
  KEY ref_manufacturer_id (ref_manufacturer_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

CREATE TABLE product_manufacturer (
  mid bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  manufacturer varchar(255) NOT NULL,
  PRIMARY KEY (mid),
  UNIQUE KEY manufacturer (manufacturer)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

CREATE TABLE product_orders (
  oid bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  orderPrice float(10,2) NOT NULL,
  orderDatetime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  red_product_id bigint(20) unsigned NOT NULL,
  PRIMARY KEY (oid),
  KEY red_product_id (red_product_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

【问题讨论】:

    标签: php mysql sql pdo


    【解决方案1】:

    您需要将 orderDateTime 条件移动到连接子句而不是 where 子句,例如:

    SELECT prod.product, COALESCE(COUNT(pord.oid),0) AS orderCount,
           COALESCE(SUM(pord.orderPrice),0) AS orderSum
    FROM product_manufacturer AS manu
    JOIN product_list AS prod ON prod.ref_manufacturer_id = manu.mid
    LEFT JOIN product_orders AS pord 
              ON pord.ref_product_id = prod.pid
              AND DATE(pord.orderDateTime) BETWEEN :orders_start AND :orders_end
    WHERE manu.mid = :manu_id
    GROUP BY prod.product;
    

    它在WHERE 子句中不起作用的原因是从外部连接返回的NULL 值。当您在产品的product_orders 中没有行时,外部联接会为日期字段orderDateTime 返回NULL,并且该行将被过滤掉,因为NULL 不等于任何内容。

    【讨论】:

    • 比 Michał Powaga 设置的要简单得多。谢谢你。
    • 您可以(或应该)将DATE(pord.orderDateTime) BETWEEN :orders_start AND :orders_end 替换为(pord.orderDateTime >= :orders_start) AND (pord.orderDateTime < orders_end + INTERVAL 1 DAY),这样查询就不必对pord 表进行全面扫描。
    • (我猜这两个参数是日期)。
    • @ypercube,我为什么要(如果是的话)这样做??
    • 因此查询不必对pord 表进行全面扫描。如果您有一个大订单表,那么查询将不得不为 每一 行调用 DATE() 函数并检查条件。如果您使用等效项,则不会调用 DATE() 并且(如果有合适的索引),查询将使用索引并且只扫描索引或读取所需的行。
    【解决方案2】:

    试试:

    SELECT p.product, 
        COALESCE(o.orderCount, 0) as orderCount, 
        COALESCE(o.orderSum,0) AS orderSum
    FROM product_manufacturer AS m
    JOIN product_list AS p ON p.ref_manufacturer_id = m.mid
    LEFT JOIN (
        SELECT ref_product_id as pid, COUNT(oid) AS orderCount, SUM(orderPrice) AS orderSum
        FROM product_orders
        WHERE DATE(orderDateTime) BETWEEN :orders_start AND :orders_end
        GROUP BY ref_product_id
    ) AS o ON p.pid = o.pid
    WHERE m.mid = :manu_id
    

    编辑:在 ypercube 评论后更正。

    【讨论】:

    • 子查询中只需要product_orders,不需要product_list
    • DATE(p.orderDateTime) 应该是:DATE(o.orderDateTime)。同样GROUP BY p.pid -> GROUP BY o.ref_product_id
    • @ypercube 在您对“orderDateTime”发表评论后,我注意到group by,但您更快:-)。现在我认为一切都很好:-)。
    【解决方案3】:

    在 where 子句上试试这个。

    WHERE manu.mid = :manu_id AND (DATE(pord.orderDateTime) BETWEEN :orders_start AND :orders_end)
    

    它可能将第二个 AND 函数作为另一个 where 子句读取,该语句应该返回 true。只是预感而已。让我知道这是否有效。

    【讨论】:

    • 不起作用,仍然继续仅显示在这些日期之间有订单的产品。并忽略那些没有的。
    • 可能是因为我们的 where 子句定义日期应该在订单开始和订单结束之内。它真的应该忽略那些其他事实。请说明你想做什么。
    • 我想显示特定制造商的所有产品以及指定日期之间的产品订单,其中制造商是指定的。
    【解决方案4】:

    我不知道您的特定系统是如何工作的,但在订购该产品之前,可能没有设置orderDateTime(即NULL 或其他内容)。你可以试试:

    WHERE manu.mid = :manu_id AND ((DATE(pord.orderDateTime) BETWEEN :orders_start AND :orders_end) OR pord.orderDateTime=NULL)

    如果不是这样,您能否举一个 orderDateTime 值的示例,说明您希望它不会显示的东西?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-22
      • 2013-01-02
      • 2015-06-26
      • 2018-12-24
      • 2022-01-24
      • 2019-04-19
      相关资源
      最近更新 更多