使用“相关”子查询进行高效连接

Posted

技术标签:

【中文标题】使用“相关”子查询进行高效连接【英文标题】:Efficient join with a "correlated" subquery 【发布时间】:2009-01-25 22:13:38 【问题描述】:

Oracle 中给定三个表 Dates(date aDate, doUse boolean), Days(rangeId int, day int, qty int) 和 Range(rangeId int, startDate date)

我想加入这些,以便 Range 与来自 aDate = startDate 的 Dates 加入其中,其中 doUse = 1 whith 每天以 Days 为单位。

给定一个范围,它可能会做这样的事情

SELECT rangeId, aDate, CASE WHEN doUse = 1 THEN qty ELSE 0 END AS qty
FROM (
    SELECT aDate, doUse, SUM(doUse) OVER (ORDER BY aDate) day
    FROM Dates 
    WHERE aDate >= :startDAte
) INNER JOIN (
    SELECT rangeId, day,qty
    FROM Days
    WHERE rangeId = :rangeId
) USING (day)
ORDER BY day ASC

我想要做的是查询 Range 中的所有范围,而不仅仅是一个。

问题是连接值“day”取决于要计算的范围 startDate,这给我制定查询带来了一些麻烦。

请记住,Dates 表非常大,因此我想避免从表中的第一个日期开始计算天值,而每个 Range Days 不应超过 100 天左右。

编辑:示例数据

Dates                            Days
aDate        doUse               rangeId     day     qty
2008-01-01   1                   1           1       1
2008-01-02   1                   1           2       10
2008-01-03   0                   1           3       8
2008-01-04   1                   2           1       2
2008-01-05   1                   2           2       5

Ranges
rangeId      startDate
1            2008-01-02
2            2008-01-03


Result
rangeId      aDate        qty
1            2008-01-02   1
1            2008-01-03   0
1            2008-01-04   10
1            2008-01-05   8
2            2008-01-03   0
2            2008-01-04   2
2            2008-01-05   5

【问题讨论】:

您能否填写一些示例数据,您希望得到什么结果? 【参考方案1】:

试试这个:

SELECT  rt.rangeId, aDate, CASE WHEN doUse = 1 THEN qty ELSE 0 END AS qty
FROM    (
    SELECT  *
    FROM    (
        SELECT  r.*, t.*, SUM(doUse) OVER (PARTITION BY rangeId ORDER BY aDate) AS span
        FROM    (
            SELECT  r.rangeId, startDate, MAX(day) AS dm
            FROM    Range r, Days d
            WHERE   d.rangeid = r.rangeid
            GROUP BY
                r.rangeId, startDate
            ) r, Dates t
        WHERE   t.adate >= startDate
        ORDER BY
            rangeId, t.adate
        )
    WHERE
        span <= dm
    ) rt, Days d
WHERE   d.rangeId = rt.rangeID
    AND d.day = GREATEST(rt.span, 1)

P。 S. 在我看来,将所有这些Dates 保留在数据库中的唯一目的是获取一个带有假期标记的连续日历。

您可以使用以下结构在 Oracle 中生成任意长度的日历:

SELECT :startDate + ROWNUM
FROM   dual
CONNECT BY
       1 = 1
WHERE  rownum < :length

Dates 中只保留假期。一个简单的连接会告诉您哪些Dates 是假期,哪些不是。

【讨论】:

为什么?为什么一遍又一遍地生成一些东西。日期表非常有用...您可以为假期设置列,我们有数百个假期日历,您可以为周末、工作日、第一天、最后一天、促销活动设置列,您可以将它们全部位图,这样您就可以拥有临时 q 的 最初设计日期表的动机是因为当时不知道如何在 oracle 中生成序列。但现在我认为它使管理更容易,我们使用的实际数据模型比我上面的示例更复杂,不同的媒体和部门有不同的“假期”。【参考方案2】:

好吧,也许我找到了办法。像这样的:

SELECT irangeId, aDate + sum(case when doUse = 1 then 0 else 1) over (partionBy rangeId order by aDate) as aDate, qty
FROM Days INNER JOIN (
    select irangeId, startDate + day - 1 as aDate, qty
    from Range inner join Days using (irangeid)
) USING (aDate)

现在我只需要一种方法来填写缺失的日期...

编辑:不,这样意味着我会错过最后日期的 doUse 值...

【讨论】:

以上是关于使用“相关”子查询进行高效连接的主要内容,如果未能解决你的问题,请参考以下文章

17/12/6 子查询

使用来自 SELECT 子查询的值的 UPDATE 查询,高效

sql子查询和连接查询的区别是啥呢?

SQL相关子查询与非相关子查询

SQL之复杂查询与视图

SQL之复杂查询与视图