【问题标题】:Join products table with prices table, for products with n valid prices将产品表与价格表连接起来,用于具有 n 个有效价格的产品
【发布时间】:2011-11-09 12:40:28
【问题描述】:

我有以下产品和价格表:

表格:产品

productID   
---------
1
2
3
4
5

表格:价格

priceID     productID   started     expires
-------------------------------------------
1           1           2011-11-05  NULL    
2           1           2011-11-05  2011-11-20
3           2           2011-11-05  NULL
4           3           2011-11-05  NULL
5           3           2011-11-06  2011-11-08

我想以这样的方式加入他们:

  1. 每个产品只有一个价格
  2. 价格有效price.started <= NOW() AND ( price.expires >= NOW() || price.expires IS NULL )
  3. 如果一种产品有多个有效价格,则应选择price.priceID 较高的价格
  4. 如果没有有效的价格,仍然显示产品信息

这四个标准定义了有效价格的最有效(为了更好的运气)。所以对于NOW() == 2011-11-09,最终结果应该是:

priceID     productID   started     expires
-------------------------------------------
2           1           2011-11-05  2011-11-20
3           2           2011-11-05  NULL
4           3           2011-11-05  NULL

我被困在了

  • 如果一种产品有多个有效价格,则应选择price.priceID 较高的价格

需求,它源于对任何给定产品在一段时间内的多个有效价格的古怪业务需求。我的sql fu极差,竟然:

SELECT
    product.*,
    price.*
FROM
    product
LEFT JOIN
    price
ON 
    price.productID = product.productID
    AND price.started <= NOW() 
    AND (
        price.expires IS NULL
        OR price.expires >= NOW()
    )

NOW() == 2011-11-09 的结果当然是:

productID   priceID     productID   started     expires
-------------------------------------------------------
1           1           1           2011-11-05  NULL    
1           2           1           2011-11-05  2011-11-20
2           3           2           2011-11-05  NULL
3           4           3           2011-11-05  NULL

澄清(基于 cmets 和答案):

  • 这些要求很奇怪,但很有效。重叠间隔是实际的业务需求,有时我需要显示所有价格(简单),有时只显示一个,最近的(我要问的)。
  • 无法更改数据模型。
  • 如果可能的话,我正在寻找整个连接。 (或关于如何将子查询集成到最终连接中的提示)
  • 我更喜欢基于标准 SQL (ANSI / ISO) 或 MySQL 的解决方案。

【问题讨论】:

  • 我想不出一个更具描述性的标题,如果您觉得更好,请随时编辑。
  • 同一价格的有效产品可以有多个价格吗?
  • @RedFilter 每个产品可以有任意数量的有效价格。我正在寻找一种基于四个标准来获得有效(为了更好的术语)的方法。
  • 应该有一个约束来禁止重叠间隔。这样可以避免这种混乱。
  • @wildplasser 重叠间隔是实际的业务需求。不幸的是,无法避免。

标签: mysql sql


【解决方案1】:

更新

以下(完整)查询应该可以解决问题:

SELECT
    product.*,
    price.*
FROM
    product, 
   (SELECT price.* FROM price 
        WHERE price.started <= NOW() 
        AND 
            (price.expires IS NULL
            OR price.expires >= NOW())
    GROUP BY price.productId
    BY price.priceId DESC LIMIT 1) AS `price`
WHERE price.productID = product.productID;

【讨论】:

  • 这只是子查询,不是整个查询。
  • @rich.okelly 好吧,价格确实合适,但我的实际问题是加入。关于子查询如何适合连接的任何建议?
  • @YannisRizos 道歉,我误解了全部要求!答案现已更新。
【解决方案2】:
SELECT
    pro.*
  , pri.*
FROM 
    product AS pro
  JOIN
    price AS pri
      ON pri.priceID =
         ( SELECT p.priceID
           FROM price p 
           WHERE p.productID = pro.productID
             AND p.started <= CURRENT_DATE() 
             AND ( p.expires IS NULL
                OR p.expires >= CURRENT_DATE()
                 )
           ORDER BY p.priceId DESC 
           LIMIT 1
         )

【讨论】:

  • 太棒了,这行得通!谢谢!用SELECT MAX(p.priceID) 替换SELECT p.priceID 并摆脱ORDER BY p.priceId DESC 似乎要快一点。
  • 子查询中的不同条件将需要不同的索引以获得最佳性能。
【解决方案3】:

如果你想避免子查询,你可以试试下面的语句:

SELECT
   product.*,
   price.*,
   MAX(price.priceID)
FROM
   product
LEFT JOIN
   price
ON 
   price.productID = product.productID
   AND price.started <= NOW() 
   AND (
      price.expires IS NULL
      OR price.expires >= NOW()
   )
GROUP BY price.productID

GROUP BY 将仅强制结果中具有最大 priceID 的不同产品。希望对您有所帮助!

【讨论】:

  • 在我看来,在您的查询中 MAX(price.priceID) 只是额外信息,而不是连接中的标准。这仍然会为每个产品返回多行。
【解决方案4】:
WITH pc AS (
    SELECT pc1.priceid, pc1.productid
    , pc1.started, pc1.expires
    FROM tmp.price pc1
    WHERE pc1.started <= now()
    AND (pc1.expires IS NULL OR pc1.expires > now() )
    AND NOT EXISTS ( SELECT *
        FROM tmp.price pcx
        WHERE pcx.productid = pc1.productid
        AND pcx.started <= now()
            AND (pcx.expires IS NULL OR pcx.expires > now() )
        AND pcx.priceid > pc1.priceid
        )
    )
SELECT pd.productid
    , pd.productname
    , pc.started
    , pc.expires
FROM pc
JOIN tmp.product pd ON pc.productid = pd.productid
    ;

CTE 是一个无名的视图。如果您的 DBMS 不支持 CTE,您可以将查询的第一 (CTE) 部分设置为命名视图;子语法是一样的。 YMMV。

【讨论】:

    【解决方案5】:
    SELECT
        p.*,
      ( SELECT 
            TOP 1 pr.price 
        FROM price pr 
        WHERE pr.productID = p.productID 
          AND pr.started <= NOW() 
          AND (  pr.expires IS NULL 
              OR pr.expires >= NOW()) 
        ORDER BY 
            pr.productID DESC
      ) AS price
    FROM product p
    

    【讨论】:

    • 如果我没记错的话,SELECT TOP 是 Transact-SQL 的一部分。 ANSI SQL 的任何解决方案?
    猜你喜欢
    • 1970-01-01
    • 2019-10-29
    • 2015-11-05
    • 2015-04-22
    • 2012-08-16
    • 2018-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多