问题描述
对于下面的表结构和数据,我试图获取在过去 15 个月内至少包含 1 个日期的行,其中包含由模式分隔的字符串组。
CREATE TABLE TEMP(nbr NUMBER,dt VARCHAR2(4000));
INSERT INTO TEMP VALUES(1,'22/05/2016');
INSERT INTO TEMP VALUES(2,'22/05/2020##22/01/2020##22/10/2019');
INSERT INTO TEMP VALUES(3,'25/05/2020##22/07/2019##22/11/2019');
INSERT INTO TEMP VALUES(4,'25/05/2015##22/01/2017##22/06/2018');
我写的查询是:
select nbr,dt from temp
WHERE MONTHS_BETWEEN(SYSDATE,TO_DATE(REGEXP_SUBSTR(dt,'[^##]+',1,LEVEL)))<15
CONNECT BY LEVEL<=REGEXP_COUNT(dt,'[^##]+');
对于第 2 行和第 3 行,属性 dt 的值使得至少 1 个子字符串的日期小于 15 个月。 预期结果是:
nbr dt
2 22/05/2020##22/01/2020##22/10/2019
3 25/05/2020##22/07/2019##22/11/2019
我得到的结果是:https://imgur.com/a/EHzRRY3
我确信这个查询遗漏了一些东西,因为我得到了所有这些重复的行。 这是我第一次尝试这种类型的东西。 有人可以向我指出我需要做些什么来纠正这个问题吗?
解决方法
您可以简单地将 EXISTS
过滤器与相关的分层子查询一起使用:
SELECT *
FROM temp t
WHERE EXISTS (
SELECT 1
FROM DUAL
WHERE TO_DATE( REGEXP_SUBSTR( t.dt,'[^#]+',1,LEVEL ),'DD/MM/YYYY' )
>= ADD_MONTHS( TRUNC(SYSDATE),-15 )
CONNECT BY
LEVEL <= REGEXP_COUNT( t.dt,'[^#]+' )
)
对于您的示例数据,输出:
NBR | DT --: | :--------------------------------- 2 | 22/05/2020##22/01/2020##22/10/2019 3 | 25/05/2020##22/07/2019##22/11/2019
dbfiddle here
,如果我没有提到需要适当的数据模型,并且数据应该在从电子表格中导入时转换为该数据模型,那我将是疏忽大意。这样你就不会像现在一样试图通过箍来获得你想要的东西。当然你可以得到你想要的东西,但是代码很难让其他人在你身后维护。作为练习,我会鼓励您这样做,即使您可能会得到适合您当前情况的解决方案。亲眼看看如何花时间创建正确规范化的数据模型并将电子表格数据移入其中,这将使您能够创建更高效、更易于维护的代码。
好的,也就是说,考虑一下。这里的 WITH 子句像临时表一样用于创建要从中进行选择的数据集。该查询使用 CONNECT BY 来遍历字符串的每个元素,其中元素后跟分隔符 '##' 或行尾(使用这种形式的正则表达式处理 NULL 列表元素)。请注意,我在选择列表中包含了 LEVEL,因此您可以查看哪个元素与条件匹配。如果您不关心这一点,请将其删除并在 select 语句中添加 DISTINCT 以获得您在原始帖子中期望的输出。
WITH tbl(item_nbr,purchase_date) AS (
SELECT 1,'22/05/2016' FROM dual UNION ALL
SELECT 2,'22/05/2020##22/01/2020##22/10/2019' FROM dual UNION ALL
SELECT 3,'25/05/2020##22/07/2019##22/11/2019' FROM dual UNION ALL
SELECT 4,'25/05/2015##22/01/2017##22/06/2018' FROM dual
)
SELECT --DISTINCT
item_nbr,LEVEL AS ELEMENT,purchase_date
FROM tbl
WHERE MONTHS_BETWEEN(SYSDATE,TO_DATE(REGEXP_SUBSTR(purchase_date,'(.*?)(##|$)',LEVEL,NULL,1),'DD/MM/YYYY' ) ) < 15
CONNECT BY LEVEL <= REGEXP_COUNT(purchase_date,'##') + 1
AND PRIOR item_nbr = item_nbr
AND PRIOR SYS_GUID() IS NOT NULL;
ITEM_NBR ELEMENT PURCHASE_DATE
---------- ---------- ----------------------------------
2 1 22/05/2020##22/01/2020##22/10/2019
2 2 22/05/2020##22/01/2020##22/10/2019
2 3 22/05/2020##22/01/2020##22/10/2019
3 1 25/05/2020##22/07/2019##22/11/2019
3 3 25/05/2020##22/07/2019##22/11/2019
5 rows selected.
,
我喜欢为此使用标准递归查询。这是一种可移植的语法,可以在略有变化的数据库中使用,因此值得学习。另一个好处是我们可以使用简单的字符串函数而不是正则表达式。
我们可以使用以下递归查询将字符串元素解析为一系列包含相应 date
的行:
with cte (nbr,dt,dt_new,dt_rest) as (
select nbr,null,dt || '##' from temp
union all
select nbr,to_date(
substr(dt_rest,instr(dt_rest,'##') - 1)
default null on conversion error,'dd/mm/yyyy'
),substr(dt_rest,'##') + 2)
from cte
where instr(dt_rest,'##') > 0
)
select * from cte
where dt_new is not null
order by nbr,dt_new;
从那时起,我们可以简单地汇总并查看每个组的最新日期:
with cte (nbr,dt_rest ) as (...)
select nbr,max(dt_new) as max_dt_new
from cte
group by nbr,dt
having months_between(sysdate,max(dt_new)) < 15
NBR | DT | MAX_DT_NEW --: | :--------------------------------- | :--------- 2 | 22/05/2020##22/01/2020##22/10/2019 | 22-MAY-20 3 | 25/05/2020##22/07/2019##22/11/2019 | 25-MAY-20