【问题标题】:Select upload history from Oracle table从 Oracle 表中选择上传历史
【发布时间】:2017-04-13 13:39:50
【问题描述】:

我有一个包含工厂列表(工厂 ID 和工厂名称)的表

Factory ID    Factory Name
1001          Factory1
1002          Factory2
1003          Factory3

用户将每月将一些数据上传到每个工厂的历史记录表中。并且同一月份的现有工厂数据也将被不同的用户覆盖。因此历史表如下所示。

Factory ID   Month UploadedValue UploadedBy UploadedOn
1001         01    250           User1      29.11.2016 07:28 PM
1002         01    102.12        User1      29.11.2016 07:28 PM
1001         01    400           User2      30.11.2016 12.00 PM

现在,我需要为每个工厂和每个月生成一份报告,这是最新上传的(上传者和上传时间),如果没有上传,则应显示为“未上传”。如下所示。

   Factory ID    Factory Name   Month   Last Upload by   Last Upload on
    1001          Factory1       01      User2            30.11.2016 12.00 PM
    1002          Factory2       01      User1            29.11.2016 07:28 PM
    1003          Factory3       01      Not Uploaded     Not Uploaded

请帮助获得这个。我尝试了分析功能。但没有运气。

【问题讨论】:

    标签: sql oracle window-functions dense-rank partition-by


    【解决方案1】:

    要获取每个工厂每个月的最新更新,您可以在“更新”表中group by factory_idmonth,然后选择max(updatedon)。对于其他列,您可以使用keep (dense_rank last...)。所有这些都是聚合函数(而不是分析函数) - 您不会做超出需要的工作。

    查询的另一部分是数据密集化。在较新的 Oracle 版本中,这可以通过“分区外连接”来完成(谷歌以阅读有关此主题的更多信息)。我在输入数据中添加了第二个“月份”以充分说明这个概念(也请参阅输出)。注意 - 我没有打扰 'Not Uploaded' 而不是 null - 如果真的需要,可以将所有值包装在 coalesce() 中以用该文本替换 null,但我看不出它添加了什么。 null 传达的信息完全相同。

    with
         factories ( factory_id, factory_name ) as (
           select 1001, 'Factory1' from dual union all
           select 1002, 'Factory2' from dual union all
           select 1003, 'Factory3' from dual
         ),
         updates ( factory_id, month, uploadedvalue, uploadedby, uploadedon ) as (
           select 1001, '01', 250   , 'User1', to_date('29.11.2016 07:28 PM', 'dd.mm.yyyy hh:mi AM') from dual union all
           select 1002, '01', 102.12, 'User1', to_date('29.11.2016 07:28 PM', 'dd.mm.yyyy hh:mi AM') from dual union all
           select 1001, '01', 400   , 'User2', to_date('30.11.2016 12.00 PM', 'dd.mm.yyyy hh:mi AM') from dual
         ),
         months ( month ) as (
           select '01' from dual union all
           select '02' from dual
         )
    select fm.factory_id, fm.factory_name, fm.month, s.uploadedvalue, s.uploadedby, 
           s.uploadedon
    from   (
    select factory_id, month, 
           min(uploadedvalue) keep (dense_rank last order by uploadedon) as uploadedvalue,
           min(uploadedby)    keep (dense_rank last order by uploadedon) as uploadedby,
           max(uploadedon) as uploadedon
    from   updates
    group by factory_id, month
    ) s
    partition by (month)
    right outer join (select * from factories cross join months) fm  
             on fm.factory_id = s.factory_id
            and fm.month      = s.month
    order by month, factory_id
    ;
    

    输出

    FACTORY_ID  FACTORY_NAME  MONTH  UPLOADEDVALUE  UPLOADEDBY  UPLOADEDON
    ----------  ------------  -----  -------------  ----------  -------------------
    1001        Factory1      01               400  User2       30.11.2016 12.00 PM
    1002        Factory2      01            102.12  User1       29.11.2016 07:28 PM
    1003        Factory3      01            
    1001        Factory1      02            
    1002        Factory2      02            
    1003        Factory3      02            
    

    【讨论】:

    • 这是由于交叉连接而将记录复制到月份表中的次数。
    • @Robinclave - 不,它没有复制任何东西。在您的实际示例中,您可能会拥有超过一个月的数据,这就是我试图说明的内容。当然,在您的 inputs 中,您只有一个月的数据,所以在我的输出中,第二个月的行都是空的,但是如果您添加第二个月的数据,您将看不到任何内容是“重复”。 (试试吧!)当然,如果在report 中您只需要一个月的数据,您可以在 ORDER BY 之前添加一个 WHERE 子句(并且您可以从输出中省略 MONTH)。
    【解决方案2】:

    您可以使用RANK() 函数。假设表格是FACFAC_HIST,代码可能如下

    SELECT fid, fname, month, last_uploaded_by, last_uploaded_on
      FROM ( select fh.fid fid
                  , fh.fname fname
                  , COALESCE(fd.month, 1) month
                  , COALESCE(RANK() FIRST OVER (PARTITION BY fd.fid, fd.month 
                                                    ORDER BY fd.last_uploaded_on DESC)
                             , -1) rnk
                  , COALESCE(fd.uploaded_by, 'not uploaded') last_uploaded_by
                  , COALESCE(fd.last_uploaded_on, 'not uploaded') last_uploaded_on
               FROM fac fh
               LEFT OUTER JOIN fac_hist fd
                 ON fh.fid = fd.fid
                    )
     WHERE rnk <= 1;
    

    【讨论】:

    • DENSE_RANK() 在这种情况下会很好,但它提供了不必要的功能(填补等级之间的空白),这会掩盖所请求的内容。 RANK() 在没有(次要)混淆的情况下完成了这项工作。另外,请开始使用标准的 JOIN 语法——它已经存在超过 25 年了,现在应该是标准的,尤其是它使您的代码更具可移植性。最后请习惯使用COALESCE()而不是NVL(),这也是标准,这样可以使您的代码更便携,但不会隐式转换,从而减少出错的机会。
    • 感谢本的投入。在我的公司,我们仍在使用这些语法,所以不了解标准化。会读到这些。同时,您能否帮助编辑此答案以符合您的建议?非常感谢。
    • 我看到 COALESCE 的语法与 NVL 完全一致!感谢更新
    • @RogueCoder - nvl() 较旧(很久以前由 Oracle 引入)并且没有像 coalesce() 那样优化。特别是当第二个参数很复杂并且需要大量处理时,这可能很重要。 coalesce() 仅在实际需要时计算第二个参数(当第一个参数为 null 时),而 nvl() 不使用此“短路评估”。此外,coalesce() 是 SQL 中的标准,nvl() 不是。使用前者的代码更便携。所以,使用前者的两个原因!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-13
    • 2011-08-03
    • 1970-01-01
    • 1970-01-01
    • 2021-12-11
    • 2017-05-11
    相关资源
    最近更新 更多