需要有关 SQL 中复杂 Join 语句的帮助

Posted

技术标签:

【中文标题】需要有关 SQL 中复杂 Join 语句的帮助【英文标题】:Need help with a complex Join statement in SQL 【发布时间】:2010-09-27 13:43:23 【问题描述】:

如何将一个日期数稀疏的表与另一个日期数详尽的表连接起来,以使稀疏日期之间的间隔采用前一个稀疏日期的值?

示例:

PRICE table (sparse dates):
date        itemid  price
2008-12-04  1       $1
2008-12-11  1       $3
2008-12-15  1       $7


VOLUME table (exhaustive dates):
date         itemid  volume_amt
2008-12-04   1       12345
2008-12-05   1       23456
2008-12-08   1       34567
2008-12-09   1       ...
2008-12-10   1
2008-12-11   1
2008-12-12   1
2008-12-15   1
2008-12-16   1
2008-12-17   1
2008-12-18   1

想要的结果:

date       price  volume_amt
2008-12-04 $1     12345
2008-12-05 $1     23456
2008-12-08 $1     34567
2008-12-09 $1     ...
2008-12-10 $1
2008-12-11 $3
2008-12-12 $3
2008-12-15 $7
2008-12-16 $7
2008-12-17 $7
2008-12-18 $7

更新:

一些人建议了一个相关的子查询来实现所需的结果。 (相关子查询 = 包含对外部查询的引用的子查询。)

这会起作用;但是,我应该注意到我使用的平台是 mysql,其相关子查询的优化很差。有什么方法可以在不使用相关子查询的情况下做到这一点?

【问题讨论】:

你可以编辑标题或删除并重新开始吗? 看起来是别人做的。是的,没注意那里。 需要输入的列名和所需的输出。现在这个问题的措辞相当模棱两可 添加了列名,所需的输出已经存在 【参考方案1】:

这并不像稀疏表的单个 LEFT OUTER JOIN 那样简单,因为您希望外部连接留下的 NULL 用最近的价格填充。

EXPLAIN SELECT v.`date`, v.volume_amt, p1.item_id, p1.price
FROM Volume v JOIN Price p1
  ON (v.`date` >= p1.`date` AND v.item_id = p1.item_id)
LEFT OUTER JOIN Price p2
  ON (v.`date` >= p2.`date` AND v.item_id = p2.item_id
    AND p1.`date` < p2.`date`)
WHERE p2.item_id IS NULL;

此查询将 Volume 与 Price 中较早的所有行匹配,然后使用另一个连接来确保我们只找到最近的价格。

我在 MySQL 5.0.51 上对此进行了测试。它既不使用相关子查询也不使用分组依据。

编辑:更新了查询以匹配 item_id 和日期。这似乎也有效。我在(date) 上创建了一个索引,在(date, item_id) 上创建了一个索引,EXPLAIN 计划是相同的。在这种情况下,(item_id, date) 上的索引可能会更好。这是 EXPLAIN 的输出:

+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------------------+
| id | select_type | table | type | possible_keys | key     | key_len | ref             | rows | Extra                                |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------------------+
|  1 | SIMPLE      | p1    | ALL  | item_id       | NULL    | NULL    | NULL            |    6 |                                      | 
|  1 | SIMPLE      | v     | ref  | item_id       | item_id | 22      | test.p1.item_id |    3 | Using where                          | 
|  1 | SIMPLE      | p2    | ref  | item_id       | item_id | 22      | test.v.item_id  |    1 | Using where; Using index; Not exists | 
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------------------+

但是我有一个非常小的数据集,优化可能依赖于更大的数据集。您应该进行试验,使用更大的数据集分析优化。

编辑:我之前粘贴了错误的 EXPLAIN 输出。上面的那一项已更正,并显示了对(item_id, date) 索引的更好使用。

【讨论】:

到目前为止看起来不错,如果我们假设可能有多个可能的 item_id 怎么办,只需将 v.item_id=p1.item_id 和 v.item_id=p2.item_id 放在 on 子句中,对吗?另外,如果您真的愿意,最好在 (item_id, date) 或 (date, item_id) 上添加索引?【参考方案2】:

假设每个日期/项目只有 1 个价格:

select v.date, v.itemid, p.price
from volume v
join price p on p.itemid = v.item_id
where p.date = (select max(p2.date) from price p2
                where p2.itemid = v.itemid
                and p2.date <= v.date);

【讨论】:

与我的答案相同,但我承认即使我的答案显示我发布得更早,您也能更快地做对。 其实我认为这包含一个错误:p.date条件属于join条件,而不是where子句。 在这种情况下不是,但如果您正在执行外部连接,它会。 我对 where 或 on 子句中的 p.date 比较的评论不正确;没什么区别。【参考方案3】:
SELECT v.date, p.price, v.volume
FROM volume v
LEFT JOIN Price p ON p.itemID=v.itemID
    AND p.[date] = (
                    SELECT MAX([date] )
                    FROM price p2 
                    WHERE p2.[date] <= v.[date] AND p2.itemid= v.itemid
                    GROUP BY p2.[date]
                   )

【讨论】:

不会起作用——只会在有新价格的日子返回交易量。期望的输出是当日价格或最近的前一天有新价格时应返回的交易量。【参考方案4】:
 SELECT  Volume.date, volume.itemid, price.price, volume.volume_amt
 FROM Volume 
 LEFT OUTER JOIN Price
 ON Volume.date = Price.date

大概吧。我的 SQL-fu 很弱

【讨论】:

不,这会让他在没有完全匹配的行中出现价格空白。 啊,明白了。我们正在做股票交易或其他事情,并且想要最后交易价格。 【参考方案5】:

此方法适用于 Oracle。不了解其他数据库,您也没有指定。如果这个确切的语法在你的数据库中不起作用,我猜有类似的技术。

dev> select * from price;

AS_OF               ID     AMOUNT
----------- ---------- ----------
04-Dec-2008          1          1
11-Dec-2008          1          2
15-Dec-2008          1          3

dev> select * from volume;

DAY                 ID     VOLUME
----------- ---------- ----------
05-Dec-2008          1          1
06-Dec-2008          1          2
07-Dec-2008          1          3
08-Dec-2008          1          4
09-Dec-2008          1          5
10-Dec-2008          1          6
11-Dec-2008          1          7
12-Dec-2008          1          8
13-Dec-2008          1          9
14-Dec-2008          1         10
15-Dec-2008          1         11
16-Dec-2008          1         12
17-Dec-2008          1         13
18-Dec-2008          1         14
19-Dec-2008          1         15
20-Dec-2008          1         16
21-Dec-2008          1         17
22-Dec-2008          1         18
23-Dec-2008          1         19

dev> select day, volume, amount from (
  2    select day, volume, (select max(as_of) from price p where p.id = v.id and as_of <= day) price_as_of
  3      from volume v
  4  )
  5  join price on as_of = price_as_of
  6  order by day;

DAY             VOLUME     AMOUNT
----------- ---------- ----------
05-Dec-2008          1          1
06-Dec-2008          2          1
07-Dec-2008          3          1
08-Dec-2008          4          1
09-Dec-2008          5          1
10-Dec-2008          6          1
11-Dec-2008          7          2
12-Dec-2008          8          2
13-Dec-2008          9          2
14-Dec-2008         10          2
15-Dec-2008         11          3
16-Dec-2008         12          3
17-Dec-2008         13          3
18-Dec-2008         14          3
19-Dec-2008         15          3
20-Dec-2008         16          3
21-Dec-2008         17          3
22-Dec-2008         18          3
23-Dec-2008         19          3

【讨论】:

以上是关于需要有关 SQL 中复杂 Join 语句的帮助的主要内容,如果未能解决你的问题,请参考以下文章

sql语句中的full join具体是怎么回事啊?

sql语句优化

sql语句优化

SQL语句调优

SQL语句中的join只能与on连用吗?有没有只用join的?举个例子行吗?

如何使用 ChatGPT 编写 SQL JOIN 查询