【问题标题】:SQL Join ChallengeSQL 连接挑战
【发布时间】:2016-08-10 16:29:45
【问题描述】:

好的,所以我已经坚持了 2 天!我已经从语义的角度解决了它,但查询可能需要长达 10 分钟才能执行。我为此选择的数据库是 SQLite(出于我不想在此详细说明的原因),但我尝试在 SQL Server 2012 上运行相同的东西,它并没有对性能产生太大影响。

所以,问题是我有 2 张桌子

  • prices(product_id INT,for_date DATE,值 INT)
  • events(starts_on DATE,ends_on DATE NULLABLE)

价格表中有大约 500K 行,事件表中有大约 100 行。

现在我需要编写一个查询来执行以下操作。

伪代码是:

  • 对于每个事件:
    • 如果事件有一个ends_on 值,那么获取所有具有匹配for_date 的product_id,对于不匹配的产品然后获取小于ends_on 值但大于该事件的starts_on 的最后一个for_date。
    • ELSE 如果事件的 ends_on 日期为 NULL,则获取所有具有与 starts_on 匹配的 for_date 的 product_id,对于不匹配的产品,获取小于 starts_on 值的最后一个 for_date。李>

我在 SQL Server 2012 中编写的查询是

SELECT    
    sp.for_date, sp.value
FROM 
    prices sp
INNER JOIN 
    events ev ON (((ev.ends_on IS NOT NULL AND
                   (sp.for_date = (SELECT for_date
                                   FROM prices
                                   WHERE for_date <= ev.ends_on 
                                      AND for_date > ev.starts_on
                                   ORDER BY for_date DESC
                                      OFFSET 0 ROWS
                                      FETCH NEXT 1 ROWS ONLY))))
          OR
          ((ev.ends_on is null 
          and
             (sp.for_date = (SELECT for_date
                                FROM prices
                                WHERE 
                                    for_date <= ev.starts_on_j
                                    AND for_date > dateadd(day, -14,   ev.starts_on)
                                order by for_date desc
                                offset 0 rows
                                    fetch next 1 row only))))

                                    );

顺便说一句,我还尝试使用部分数据创建临时表并对它们进行了相同的操作。它只是卡住了。

奇怪的是,如果我分别运行 2 个“OR”条件,响应时间是完美的!

更新

样本数据集和预期结果

价格条目

Product ID, ForDt, Value
1, 25-01-2010, 123
1, 26-01-2010, 112
1, 29-01-2010, 334
1, 02-02-2010, 512
1, 03-02-2010, 765
1, 04-02-2010, 632
1, 05-02-2010, 311
1, 06-02-2010, 555
2, 03-02-2010, 854
2, 04-02-2010, 625
2, 05-02-2010, 919
3, 20-01-2010, 777
3, 06-02-2010, 877
3, 10-03-2010, 444
3, 11-03-2010, 888

事件条目(为了更容易理解,我还添加了事件 ID)

Event ID, StartsOn, EndsOn
22, 27-01-2010, NULL
33, 02-02-2010, 06-02-2010
44, 01-03-2010, 13-03-2010

预期结果集

Event ID, Product ID, ForDt, Value
22, 1, 26-01-2010, 112
33, 1, 06-02-2010, 311
44, 1, 06-02-2010, 311

33, 2, 05-02-2010, 919
44, 2, 05-02-2010, 919

22, 3, 20-01-2010, 777
33, 3, 06-02-2010, 877
44, 3, 11-03-2010, 888

【问题讨论】:

  • 如果可以显示查询和输出,将有助于了解查询在做什么
  • 还显示了索引以及上面两个表与一些样本数据的关系
  • EXPLAIN QUERY PLAN的输出。
  • 你想要的答案应该是SQLite还是SQL Server?
  • 我必须承认我很难理解这项任务。您正在从表价格中选择记录,但仅显示日期和价格。当您将记录与许多事件结合起来时,您最终会得到一个包含许多天数和许多价格的列表,其中许多可能是重复的,并且没有它们所指的产品和事件的信息。这似乎没有意义。

标签: sql sqlite join subquery


【解决方案1】:

好的,既然您已经将预期结果显示为事件和相关产品的列表,那么这个问题就很有意义了。您的查询只选择日期和值没有。

您正在寻找每次活动的最佳产品价格记录。这可以通过分析函数轻松完成,但 SQLite 不支持它们。所以我们必须写一个更复杂的查询。

让我们先看看ends_on null 的事件。以下是如何找到最优惠的产品价格(即在starts_on 之前的最后一个价格):

select e.event_id, p.product_id, max(for_date) as best_for_date
from events e
join prices p on p.for_date < e.starts_on
where e.ends_on is null
group by e.event_id, p.product_id;

我们扩展了这个查询,还可以找到带有 ends_on 的事件的最佳产品价格,然后再次访问产品表,以便我们获得包含值的完整记录:

select ep.event_id, p.product_id, p.for_date, p.value
from
(
  select e.event_id, p.product_id, max(for_date) as best_for_date
  from events e
  join prices p on (e.ends_on is null and p.for_date < e.starts_on)
                or (e.ends_on is not null and p.for_date between e.starts_on and e.ends_on)
  group by e.event_id, p.product_id
) ep
join prices p on p.product_id = ep.product_id and p.for_date = ep.best_for_date;

(顺便说一句:您在这里描述了一个非常特殊的情况。到目前为止,我所看到的数据库会将ends_on null 视为无限制或“仍处于活动状态”。因此,检索此类事件的价格不会是最后一个之前 starts_on,而是最新的在或之后 starts_on。)

【讨论】:

  • 好的,所以我认为这个查询主要是有效的,将在我的主要生产数据集的不同组合上进行尝试。现在,我已将 p.for_date
  • 谢谢托尔斯滕。在 select 子句中不使用 max 真的很愚蠢 :o)