在子查询中迭代

Posted

技术标签:

【中文标题】在子查询中迭代【英文标题】:Iterate inside subquery 【发布时间】:2015-02-24 11:18:08 【问题描述】:

我有一种情况,我需要根据 where 子句输入来迭代一个条件,想知道如何做到这一点。

背景是,在一个存储单元中,它由几个罐组成,每个罐都有自己的罐倾角测量状态,最后测量的日期不同,我怎样才能得到特定日期的最新倾角值?

例子:

Tank A having last measured date (EndDate) as 01 Feb 2015.
Tank B having last measured date (EndDate) as 31 Jan 2015.
Tank B having last measured date (EndDate) as 17 Feb 2015.
Tank C having last measured date (EndDate) as 18 Feb 2015.

表结构:

Tanks| DipDaytime| Volume| EndDate
A, 28 Jan 2015 8pm, 1000, 01 Feb 2015
B, 30 Jan 2015 5pm, 2000, 31 Jan 2015 
B, 01 Feb 2015 5pm, 2500, 17 Feb 2015
C, 01 Feb 2015 3pm, 3000, 18 Feb 2015 

预期的输出是:

For 31 Jan 2015:
A, 28 Jan 2015 8pm, 1000, 01 Feb 2015
B, 30 Jan 2015 5pm, 2000, 31 Jan 2015 

For 18 Feb 2015:
A, 28 Jan 2015 8pm, 1000, 01 Feb 2015
B, 01 Feb 2015 5pm, 2500, 17 Feb 2015 
C, 01 Feb 2015 3pm, 3000, 18 Feb 2015 

我能得出这样的结果:

SELECT ts.Tanks, ts.DipDaytime, ts.EndDate , ts.Volume
FROM   table ts
WHERE  ts.EndDate =     
(SELECT MAX(ts2.EndDate ) FROM table ts2
                      WHERE ts2.Tanks =  ts.Tanks
                      AND ts2.EndDate <= '17.02.2014')

问题是我需要更改 17.01.2014 每次我需要一个特定日期的不同结果时,我也无法将 17.01.2014 显示为查询结果,因为它不是表的一部分。

如何以动态方式获得它,我只需要提供一个日期范围,例如从 01.02.2014 到 28.02.2014,以获得完整的结果?还有一个显示报告日期的临时列?

最终结果将是:

For 31 Jan 2015:
Tank | DipDaytime| Volume| EndDate, ReportDate
A, 28 Jan 2015 8pm, 1000, 01 Feb 2015, 31 Jan 2015
B, 30 Jan 2015 5pm, 2000, 31 Jan 2015, 31 Jan 2015 

For 18 Feb 2015:
Tank | DipDaytime| Volume| EndDate, ReportDate
A, 28 Jan 2015 8pm, 1000, 01 Feb 2015, 18 Feb 2015
B, 01 Feb 2015 5pm, 2500, 17 Feb 2015, 18 Feb 2015 
C, 01 Feb 2015 3pm, 3000, 18 Feb 2015, 18 Feb 2015 

如果有人可以提供帮助,我们将不胜感激。谢谢。

【问题讨论】:

为什么坦克 A 出现在这两种情况下? @SagarJoon: 因为坦克 A 在 31.01 和 18.02 之间没有变化? 【参考方案1】:

您可以使用row_number() 过滤每个(Tank) 组的最新行。使用子查询技巧,您可以使用常量进行过滤和显示:

select  *
from    (
cross join
        (
        select  row_number() over (
                    partition by tank
                    order by EndDate desc) as rn
        ,       *
        from    Tanks
        cross join
                (
                select  '17.02.2014' as report_date
                from    dual
                ) pars
        where   EndDate <= report_date
        ) numbered_rows
where   rn = 1 -- Latest row per tank

使用常量的更好方法是将其作为参数传递给查询。在 Oracle 中,参数以分号开头,例如 :report_date

【讨论】:

【参考方案2】:

你可以试试这样的:

with report_dates as
(
  select to_date('31 Jan 2015', 'dd Mon yyyy') report_date from dual
  union all
  select to_date('18 Feb 2015', 'dd Mon yyyy') report_date from dual
)
select t.tanks, d.report_date,
       max(t.dipdaytime) keep (dense_rank last order by t.enddate) DipDaytime,
       max(t.volume) keep (dense_rank last order by t.enddate) volume,
       max(t.enddate) enddate
from table t, report_dates d
where t.dipdaytime <= d.report_date
group by t.tanks, d.report_date
order by d.report_date, t.tanks
;

这给出了:

    TANKS   VOLUME  REPORT_DATE DIPDAYTIME  ENDDATE
1   A   1000    31-janv.-2015   28-janv.-2015   01-févr.-2015
2   B   2000    31-janv.-2015   30-janv.-2015   31-janv.-2015
3   A   1000    18-févr.-2015   28-janv.-2015   01-févr.-2015
4   B   2500    18-févr.-2015   01-févr.-2015   17-févr.-2015
5   C   3000    18-févr.-2015   01-févr.-2015   18-févr.-2015

【讨论】:

【参考方案3】:

我倾向于对您的查询稍作修改——定义您想要的日期列表,然后在查询中多次使用该列表:

with rd as (
      select date '2015-01-31' as ReportDate from dual
      union all
      select date '2015-02-18' from dual
    )
select rd.ReportDate, t.*
from table t cross join
     rd
where t.dipdaytime = (select max(t2.dipdaytime)
                      from table t2
                      where t2.tank = t.tank and
                            t2.dipdaytime <= rd.ReportDate
                     );

这是使用相关子查询来查找每个罐在任何给定日期之前的最大 dipdaytime

【讨论】:

【参考方案4】:

此查询给出选定时间段的结果。您必须在第一个子查询 (dates) 中定义开始报告日期和最后日期。

with dates as (
  select to_date('2015-02-15') + level - 1 tdate 
    from dual
    connect by to_date('2015-02-15') + level - 1 <= '2015-02-18'),
tanks as (
  select * 
    from (
      select tdate, tanks, dipdaytime, volume, enddate
          row_number() over (partition by tanks, tdate order by enddate desc) rn
        from dates
        left join ts on ts.enddate <= dates.tdate)
    where rn = 1)
select tdate, tanks, dipdaytime, volume
  from tanks
  order by tdate, tanks


TDATE       TANKS      DIPDAYTIME      VOLUME
----------- ---------- ----------- ----------
2015-02-15  A          2015-01-28        1000
2015-02-15  B          2015-01-30        2000
2015-02-16  A          2015-01-28        1000
2015-02-16  B          2015-01-30        2000
2015-02-17  A          2015-01-28        1000
2015-02-17  B          2015-02-01        2500
2015-02-18  A          2015-01-28        1000
2015-02-18  B          2015-02-01        2500
2015-02-18  C          2015-02-01        3000

9 rows selected

下面的查询会在不同的列中选择每个罐的数据,因此您在报告期间的每个日期都有一行。

with dates as (
  select to_date('2015-02-15') + level - 1 tdate 
    from dual
    connect by to_date('2015-02-15') + level - 1 <= '2015-02-18'),
tanks as (
  select * 
    from (
      select tdate, tanks, dipdaytime, volume, enddate,
          row_number() over (partition by tanks, tdate order by enddate desc) rn
        from dates
        left join ts on ts.enddate <= dates.tdate)
    where rn = 1)
select dates.tdate,
    ta.dipdaytime a_ddt, ta.volume a_vol, ta.enddate a_end,
    tb.dipdaytime b_ddt, tb.volume b_vol, tb.enddate b_end,
    tc.dipdaytime c_ddt, tc.volume c_vol, tc.enddate c_end
  from dates
    left join tanks ta on ta.tdate = dates.tdate and ta.tanks = 'A'
    left join tanks tb on tb.tdate = dates.tdate and tb.tanks = 'B'
    left join tanks tc on tc.tdate = dates.tdate and tc.tanks = 'C'
  order by tdate

结果:

TDATE       A_DDT            A_VOL A_END       B_DDT            B_VOL B_END       C_DDT            C_VOL C_END
----------- ----------- ---------- ----------- ----------- ---------- ----------- ----------- ---------- -----------
2015-02-15  2015-01-28        1000 2015-02-01  2015-01-30        2000 2015-01-31                         
2015-02-16  2015-01-28        1000 2015-02-01  2015-01-30        2000 2015-01-31                         
2015-02-17  2015-01-28        1000 2015-02-01  2015-02-01        2500 2015-02-17                         
2015-02-18  2015-01-28        1000 2015-02-01  2015-02-01        2500 2015-02-17  2015-02-01        3000 2015-02-18

【讨论】:

以上是关于在子查询中迭代的主要内容,如果未能解决你的问题,请参考以下文章

在子查询 JOIN 中引用外部查询

在子查询和别名中排序

在子查询中引用外部查询

在子查询中使用聚合和窗口函数

mysql不支持在子查询中使用limit解决办法

错误:语法错误:应为“)”,但在子查询中得到语句结束语句