Oracle SQL 循环遍历日期范围

Posted

技术标签:

【中文标题】Oracle SQL 循环遍历日期范围【英文标题】:Oracle SQL Loop through Date Range 【发布时间】:2015-02-19 17:01:06 【问题描述】:

我正在尝试编写执行以下操作的查询:

从访问表中获取 Start_Date 和 End_Date 对于包含在日期范围内的每个日期,搜索遭遇表 查看某位提供者在该日期是否看过患者 生成一个表格,显示每个日期以及谁在该日期看到了患者 日期。 注意事项: 患者可能在那个日期没有见过,这将导致 NULL 或类似的结果 可能不止一位提供者在某个日期看过患者,这会导致该日期出现多条线路。

简化示例: 开始日期:15 年 1 月 1 日 结束日期:15 年 1 月 4 日

期望的输出:

╔══════╦══════════╦═══════╦═══════╗
║  ID  ║   DATE   ║ NAME  ║ TYPE  ║
╠══════╬══════════╬═══════╬═══════╣
║ 2222 ║ 1-Jan-15 ║ Smith ║ Note  ║
║ 2222 ║ 2-Jan-15 ║ Jones ║ Note  ║
║ 2222 ║ 2-Jan-15 ║ Smith ║ Order ║
║ 2222 ║ 3-Jan-15 ║ NULL  ║       ║
║ 2222 ║ 4-Jan-15 ║ Jones ║ Note  ║
╚══════╩══════════╩═══════╩═══════╝

这是我到目前为止所得到的。这会生成这些特定提供者何时看到患者的列表,但对于那些没有看到患者的日期,它不会导致 NULL。

SELECT V.VISIT_ID, ET.ENCOUNTER_TRANSACTION_DATE, P.NAME_LAST, ETT.ENC_TRANS_TYPE_NAME
FROM VISIT V
RIGHT OUTER JOIN ENCOUNTER_TRANSACTION ET ON V.VISIT_ID = ET.VISIT_ID AND V.INSTITUTION_ID = ET.INSTITUTION_ID
INNER JOIN ENCOUNTER_TRANSACTION_TYPE ETT ON ET.ENCOUNTER_TYPE_ID = ETT.ENCOUNTER_TRANSACTION_TYPE_ID
INNER JOIN LOCAL_PROVIDER LP ON ET.ORDERING_PROVIDER_ID = LP.LOCAL_PROVIDER_ID
INNER JOIN PERSON_IDENTIFIER I ON I.IDENTIFIER = LP.PROVIDER_NUMBER AND I.IDENTIFIER_SYS_ID = LP.PROVIDER_NUMBER_SYS_ID  
INNER JOIN PERSON P ON P.PERSON_ID = I.PERSON_ID
WHERE
  V.INSTITUTION_ID = 1 AND
  V.END_DATE IS NOT NULL AND
  V.VOIDED_YN = 'N' AND
  V.CARE_SETTING_CODE = 'I' AND
  V.PATIENT_TEAM_ID IN (16, 17, 18) AND
  V.START_DATE >= (TRUNC(ADD_MONTHS(CURRENT_DATE, -1),'mon')) AND
  V.START_DATE <= (LAST_DAY(ADD_MONTHS(CURRENT_DATE, -1))) AND
  I.IDENTIFIER IN (
    '1234', --Smith
    '4321', --Jones
  )
ORDER BY V.VISIT_ID ASC, ET.ENCOUNTER_TRANSACTION_DATE ASC;

当前输出(缺少 03-JAN-15 的 NULL 行):

╔══════╦══════════╦═══════╦═══════╗
║  ID  ║   DATE   ║ NAME  ║ TYPE  ║
╠══════╬══════════╬═══════╬═══════╣
║ 2222 ║ 1-Jan-15 ║ Smith ║ Note  ║
║ 2222 ║ 2-Jan-15 ║ Jones ║ Note  ║
║ 2222 ║ 2-Jan-15 ║ Smith ║ Order ║
║ 2222 ║ 4-Jan-15 ║ Jones ║ Note  ║
╚══════╩══════════╩═══════╩═══════╝

【问题讨论】:

您提供了预期的输出,但忽略了提供输入数据,这使得我们很难测试我们的解决方案是否按预期工作。特别是如果您拥有与看起来一样多的桌子!说了这么多,您可以创建一个列出日期的子查询(例如select start_date + level from dual connect by level &lt;= 10)并将您的主查询分区外连接到它,以便为每一天生成行。 RIGHT JOIN 是无用的,因为 WHERE 条件将它变成了 INNER JOIN。因此,请使用 David Faber 的解决方案并将其加入到您现有的联接中 【参考方案1】:

以下是获取日期范围的方法:

 SELECT DATE'2015-01-01' + LEVEL - 1
   FROM dual
CONNECT BY DATE'2015-01-01' + LEVEL - 1 < DATE'2015-02-01';

以上将获取 2015 年 1 月 1 日至 2015 年 1 月 31 日范围内的所有日期。

使用上述方法,您可以做的是插入您的开始日期和结束日期并创建一个 CTE,然后在日期上使用外部联接:

WITH dr AS (
    SELECT DATE'2015-01-01' + LEVEL - 1 AS transaction_date
      FROM dual
   CONNECT BY DATE'2015-01-01' + LEVEL - 1 < DATE'2015-01-04'
)
SELECT V.VISIT_ID, dr.transaction_date
     , P.NAME_LAST, ETT.ENC_TRANS_TYPE_NAME
...
       ENCOUNTER_TRANSACTION ET RIGHT JOIN dr
    ON ET.ENCOUNTER_TRANSACTION_DATE = dr.transaction_date

更新我有一些时间,我想我知道如何将上述内容集成到您的查询中。我真的没有 SQL Fiddle 的示例数据(无论如何你有很多表)。您可以从这里开始,这应该得到所有适当的访问以及访问日期范围内的每个日期(假设没有访问超过 30 天 - 相应地进行调整):

WITH dr AS (
    SELECT LEVEL AS dd FROM dual
   CONNECT BY LEVEL <= 30 -- I'm assuming a max date range of 30; increase as you see fit
)
SELECT v.visit_id, v.start_date - 1 + dr.dd AS encounter_transaction_date
     , p.name_last, ett.enc_trans_type_name
  FROM visit v CROSS JOIN dr
 WHERE v.start_date - 1 + dr.dd < TRUNC(v.end_date) + 1
   AND v.institution_id = 1
   AND v.end_date IS NOT NULL
   AND v.voided_yn = 'N'
   AND v.care_setting_code = 'I'
   AND v.patient_team_id IN (16,17,18)
   AND v.start_date >= TRUNC(ADD_MONTHS(CURRENT_DATE, -1), 'MONTH')
   AND v.end_date < TRUNC(CURRENT_DATE, 'MONTH');

那么我认为你的外连接应该是从那里开始的左连接(至少,如果我理解正确的话,我可能不会:

WITH dr AS (
    SELECT LEVEL AS dd FROM dual
   CONNECT BY LEVEL <= 30 -- I'm assuming a max date range of 30; increase as you see fit
)
SELECT v.visit_id, v.start_date - 1 + dr.dd AS encounter_transaction_date
  FROM visit v CROSS JOIN dr
  LEFT JOIN encounter_transaction et
    ON v.visit_id = et.visit_id
   AND v.institution_id = et.institution_id
   AND TRUNC(v.start_date - 1 + dr.dd) = et.encounter_transaction_date
  LEFT JOIN encounter_transaction_type ETT
    ON et.encounter_type_id = ett.encounter_transaction_type_id
  LEFT JOIN local_provider lp
    ON et.ordering_provider_id = lp.local_provider_id
  LEFT JOIN person_identifier i
    ON i.identifier = lp.provider_number
   AND i.identifier_sys_id = lp.provider_number_sys_id
   AND i.identifier IN (
       '1234', --Smith
       '4321' --Jones ** you had an extra comma here!
)
  LEFT JOIN person p
    ON p.person_id = i.person_id
 WHERE v.start_date - 1 + dr.dd < TRUNC(v.end_date) + 1
   AND v.institution_id = 1
   AND v.end_date IS NOT NULL
   AND v.voided_yn = 'N'
   AND v.care_setting_code = 'I'
   AND v.patient_team_id IN (16,17,18)
   AND v.start_date >= TRUNC(ADD_MONTHS(CURRENT_DATE, -1), 'MONTH')
   AND v.start_date < TRUNC(CURRENT_DATE, 'MONTH');

【讨论】:

感谢您的建议,我仍在努力完全消化它。我现在正在尝试这个并得到一个 ORA-00904: V.END_DATE: CONNECT BY 之后的无效标识符。我对 CONNECT BY 不是很熟悉,所以我还在尝试调试。 似乎不允许 CONNECT BY 使用来自父选择的数据。 您的最新更新效果很好!感谢您的快速回复。

以上是关于Oracle SQL 循环遍历日期范围的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 2008 中的 While 循环遍历日期范围,然后插入

如何循环遍历日期范围?

以每月步骤循环遍历日期范围

Windows 批处理文件 - 循环遍历日期范围数组,然后拆分每个项目

在 SQL 中编写函数以遍历 UDF 中的日期范围

循环遍历 Oracle PL/SQL 中的表