如何仅在 Oracle 中按连续数据分组
How to group by contiguous data only in Oracle
我正在尝试显示每个费率的笼子数量以及开始和结束日期。这将进入我们每天为每个笼子收费的发票,这些笼子可以有不同的费率。
这不是一个简单的 GROUP BY
和获取 MIN
和 MAX
日期,因为笼子的数量可以下降或上升然后再次回到相同的数字所以我只需要查看连续数据。
我搜索了解决方案并找到了 . I modified it a little bit to suit my needs and came up with this:
WITH cte(rate_name, cages, use_date) AS (
SELECT 'I1', 8, DATE'2017-11-04' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-05' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-07' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-10' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-11' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-12' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-13' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-14' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-01' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-02' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-03' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-04' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-05' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-06' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-07' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-08' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-09' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-10' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-11' FROM DUAL
)
SELECT
a.rate_name,
a.cages,
MIN(a.use_date) AS startdate,
MAX(a.use_date) AS enddate
FROM (
SELECT
cte.use_date,
cte.rate_name,
cte.cages,
ROW_NUMBER() OVER (ORDER BY cte.rate_name, cte.use_date) - ROW_NUMBER() OVER (PARTITION BY cte.rate_name, cte.cages ORDER BY cte.use_date) AS grp
FROM cte
) a
GROUP BY a.rate_name, a.cages, a.grp
ORDER BY a.rate_name ASC, 3;
我在 PL/SQL 中 运行 这个查询,它似乎非常适合我正在尝试做的事情。当我尝试在我们正在使用的软件工具中插入解决方案时,结果发现它不支持 ROW_NUMBER()
、OVER
和 PARTITION BY
.
有没有一种方法可以在不使用这些内置功能的情况下获得相同的结果?
我已经开始考虑手动实施 ROW_NUMBER()
并找到了 this 方法。当我测试它时它似乎可以工作,但我还没有真正插入它。我现在有点坚持实施 PARTITION BY
我只是感到有点迷茫,不知道我是否正在前进这里的方向是正确的。
编辑
我刚刚注意到查询 returns 的结果不正确。
对于 I1
和 7
个笼子,应该返回 2 行。第一行在 2017-11-07
开始和结束,而第二行在 2017-11-10
开始并在 2017-11-11
.
结束
嗯,这有点尴尬,但这是我首先想到的。我相信它可以被清理干净。我必须制作一个二级 CTE 才能让它在没有任何功能或任何东西的情况下工作。使用 connect by
或 lag
可能会更容易,但我猜你的软件工具也无法处理这些。
WITH cte(rate_name, cages, use_date) AS (
SELECT 'I1', 8, DATE'2017-11-04' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-05' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-07' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-10' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-11' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-12' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-13' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-14' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-01' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-02' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-03' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-04' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-05' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-06' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-07' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-08' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-09' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-10' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-11' FROM DUAL
),
recur as (
SELECT
c1.use_date,
c1.rate_name,
c1.cages,
case when c3.rate_name is null then c1.use_date else null end as start_date,
case when c2.rate_name is null then c1.use_date else null end as end_date
FROM cte c1
-- next day
left join cte c2 on c2.rate_name = c1.rate_name and c2.use_date = c1.use_date +1 and c2.cages = c1.cages
-- prev day
left join cte c3 on c3.rate_name = c1.rate_name and c3.use_date = c1.use_date -1 and c3.cages = c1.cages
)
select rate_name, cages, start_date,
(select min(e.end_date) from recur e
where e.rate_name = s.rate_name
and e.end_date >= s.start_date) as end_date
from recur s
where start_date is not null
order by rate_name, start_date;
我正在尝试显示每个费率的笼子数量以及开始和结束日期。这将进入我们每天为每个笼子收费的发票,这些笼子可以有不同的费率。
这不是一个简单的 GROUP BY
和获取 MIN
和 MAX
日期,因为笼子的数量可以下降或上升然后再次回到相同的数字所以我只需要查看连续数据。
我搜索了解决方案并找到了
WITH cte(rate_name, cages, use_date) AS (
SELECT 'I1', 8, DATE'2017-11-04' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-05' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-07' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-10' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-11' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-12' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-13' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-14' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-01' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-02' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-03' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-04' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-05' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-06' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-07' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-08' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-09' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-10' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-11' FROM DUAL
)
SELECT
a.rate_name,
a.cages,
MIN(a.use_date) AS startdate,
MAX(a.use_date) AS enddate
FROM (
SELECT
cte.use_date,
cte.rate_name,
cte.cages,
ROW_NUMBER() OVER (ORDER BY cte.rate_name, cte.use_date) - ROW_NUMBER() OVER (PARTITION BY cte.rate_name, cte.cages ORDER BY cte.use_date) AS grp
FROM cte
) a
GROUP BY a.rate_name, a.cages, a.grp
ORDER BY a.rate_name ASC, 3;
我在 PL/SQL 中 运行 这个查询,它似乎非常适合我正在尝试做的事情。当我尝试在我们正在使用的软件工具中插入解决方案时,结果发现它不支持 ROW_NUMBER()
、OVER
和 PARTITION BY
.
有没有一种方法可以在不使用这些内置功能的情况下获得相同的结果?
我已经开始考虑手动实施 ROW_NUMBER()
并找到了 this 方法。当我测试它时它似乎可以工作,但我还没有真正插入它。我现在有点坚持实施 PARTITION BY
我只是感到有点迷茫,不知道我是否正在前进这里的方向是正确的。
编辑
我刚刚注意到查询 returns 的结果不正确。
对于 I1
和 7
个笼子,应该返回 2 行。第一行在 2017-11-07
开始和结束,而第二行在 2017-11-10
开始并在 2017-11-11
.
嗯,这有点尴尬,但这是我首先想到的。我相信它可以被清理干净。我必须制作一个二级 CTE 才能让它在没有任何功能或任何东西的情况下工作。使用 connect by
或 lag
可能会更容易,但我猜你的软件工具也无法处理这些。
WITH cte(rate_name, cages, use_date) AS (
SELECT 'I1', 8, DATE'2017-11-04' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-05' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-07' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-10' FROM DUAL UNION ALL
SELECT 'I1', 7, DATE'2017-11-11' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-12' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-13' FROM DUAL UNION ALL
SELECT 'I1', 8, DATE'2017-11-14' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-01' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-02' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-03' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-04' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-05' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-06' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-07' FROM DUAL UNION ALL
SELECT 'I1 - BR', 1, DATE'2017-11-08' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-09' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-10' FROM DUAL UNION ALL
SELECT 'I1 - BR', 5, DATE'2017-11-11' FROM DUAL
),
recur as (
SELECT
c1.use_date,
c1.rate_name,
c1.cages,
case when c3.rate_name is null then c1.use_date else null end as start_date,
case when c2.rate_name is null then c1.use_date else null end as end_date
FROM cte c1
-- next day
left join cte c2 on c2.rate_name = c1.rate_name and c2.use_date = c1.use_date +1 and c2.cages = c1.cages
-- prev day
left join cte c3 on c3.rate_name = c1.rate_name and c3.use_date = c1.use_date -1 and c3.cages = c1.cages
)
select rate_name, cages, start_date,
(select min(e.end_date) from recur e
where e.rate_name = s.rate_name
and e.end_date >= s.start_date) as end_date
from recur s
where start_date is not null
order by rate_name, start_date;