【问题标题】:Database structure for storing historical data and reporting on data annually用于存储历史数据和每年报告数据的数据库结构
【发布时间】:2020-04-03 10:30:56
【问题描述】:

我正在使用 Oracle 数据库。我有一个假设字符串的系统。不像数据类型字符串,而是像小绳子一样的字符串。

多年来,字符串的开头和结尾可能会发生变化。必须保留这些更改的历史记录。每年进行一次报告。用户想查看 2016 年、2017 年、2018 年等字符串的开头和结尾是什么。 一个字符串一年可以改变几次,或者每 2/3 年只改变一次。用户只对特定年份的最新更改感兴趣。 2016 年或 2017 年等的有效开始和结束时间是什么?

CREATE TABLE STRINGLIST
(
  ID            NUMBER,
  STRINGID      NUMBER,
  STRING        VARCHAR2(7 BYTE),
  STRING_START  NUMBER,
  STRING_END    NUMBER,
  CREATE_DATE   DATE,
  CREATED_BY    VARCHAR2(10 BYTE),
  EXPIRE_DATE   DATE,
  EXPIRED_BY    VARCHAR2(10 BYTE)
)

Insert into PMS2020.STRINGLIST
   (ID, STRINGID, STRING, STRING_START, STRING_END, 
    CREATE_DATE, CREATED_BY, EXPIRE_DATE, EXPIRED_BY)
 Values
   (1, 1, 'string1', 0, 5, 
    TO_DATE('02/02/2015 12:31:00', 'MM/DD/YYYY HH24:MI:SS'), 'user1', TO_DATE('04/02/2016 12:31:00', 'MM/DD/YYYY HH24:MI:SS'), 'user1');
Insert into PMS2020.STRINGLIST
   (ID, STRINGID, STRING, STRING_START, STRING_END, 
    CREATE_DATE, CREATED_BY, EXPIRE_DATE, EXPIRED_BY)
 Values
   (2, 1, 'string1', 0.1, 5, 
    TO_DATE('04/02/2016 12:31:00', 'MM/DD/YYYY HH24:MI:SS'), 'user1', TO_DATE('02/02/2016 12:31:00', 'MM/DD/YYYY HH24:MI:SS'), 'user1');
Insert into PMS2020.STRINGLIST
   (ID, STRINGID, STRING, STRING_START, STRING_END, 
    CREATE_DATE, CREATED_BY, EXPIRE_DATE, EXPIRED_BY)
 Values
   (3, 1, 'string1', 0.12, 5.2, 
    TO_DATE('04/02/2016 12:31:00', 'MM/DD/YYYY HH24:MI:SS'), 'user1', TO_DATE('04/02/2018 12:31:00', 'MM/DD/YYYY HH24:MI:SS'), 'user1');
Insert into PMS2020.STRINGLIST
   (ID, STRINGID, STRING, STRING_START, STRING_END, 
    CREATE_DATE, CREATED_BY, EXPIRE_DATE, EXPIRED_BY)
 Values
   (4, 1, 'string1', 0.5, 6, 
    TO_DATE('04/02/2018 12:32:00', 'MM/DD/YYYY HH24:MI:SS'), 'user1', TO_DATE('04/02/2019 12:32:00', 'MM/DD/YYYY HH24:MI:SS'), 'user1');
Insert into PMS2020.STRINGLIST
   (ID, STRINGID, STRING, STRING_START, STRING_END, 
    CREATE_DATE, CREATED_BY)
 Values
   (5, 1, 'string1', 0.75, 6.5, 
    TO_DATE('04/02/2019 12:32:00', 'MM/DD/YYYY HH24:MI:SS'), 'user1');
COMMIT;

很容易得到当前的字符串大小:

select *
from stringlist
where stringid = 1
and expiry_date is null;

获取最新字符串大小的查询会是什么样子:

2015 0.1 - 5

2016 年 0.12 - 5.2

2017 0.12 - 5.2(2017 年未进行任何更改,因此 2016 年的值仍然有效)

2018 年 0.5 - 6

2019 年 0.75 - 6.5

用户只需要选择字符串类型和年份。 并且尺寸必须显示在所选年份的报告中。

一个非常简单的方法是添加一个 string_year。所以每年每个字符串都会有一个记录。但这会给您带来 2016 年和 2017 年的重复,因为 2017 年没有任何变化。

这显然是对系统的一个非常简单的解释。 “字符串”也有很多不同的属性等。但这对于这个问题并不重要。

您将如何处理这样的事情?你的数据库设计是什么?

【问题讨论】:

  • 您是否要求比您提供的数据库设计更好?
  • 更好的数据库设计或选择语句如何工作?那是不是很复杂?这意味着这只是一个简单的例子。实际系统很复杂,有很多表和依赖项。报表查询已经很复杂,有连接等。
  • 如果 EXPIRE_DATE 总是与下一行的 START_DATE 相同,我将删除 EXPIRE_DATE 和 EXPIRED_BY 列。否则,您的桌子看起来不错。
  • 哦,在您数据的第 2 行中,到期日期早于开始日期。这是一个错字吗,还是您的真实数据可能出现这种情况?
  • 错字。对不起。在现实世界的数据中不可能

标签: oracle database-design


【解决方案1】:

如果您一次只寻找一年,您可以这样做:

select max(string_start) keep (dense_rank last order by create_date) as string_start,
       max(string_end)   keep (dense_rank last order by create_date) as string_end
from stringlist
where create_date < last_day(to_date(to_char(2017), 'YYYY')) + 1;

STRING_START STRING_END
------------ ----------
         .12        5.2

SQL Fiddle

或:

select string_start, string_end
from (
  select string_start, string_end, row_number() over (order by create_date desc) as rn
  from stringlist
  where create_date < last_day(to_date(to_char(2017), 'YYYY')) + 1
)
where rn = 1;

STRING_START STRING_END
------------ ----------
         .12        5.2

SQL Fiddle

在这两种情况下,您都排除了下一年开始之前的所有数据,然后为剩下的行查找最新创建日期的值。

【讨论】:

    【解决方案2】:

    以下查询可用于查看字符串活动年份的所有字符串数据:

    WITH string_years (stringid, report_year) AS
    (
      SELECT s.stringid, y.y
      FROM (SELECT sl.stringid,
                   EXTRACT(YEAR FROM min(create_date)) AS MIN_YEAR,
                   EXTRACT(YEAR FROM max(create_date)) AS MAX_YEAR
            FROM stringlist sl
            GROUP BY sl.stringid) s
      INNER JOIN (SELECT MIN_YEAR+LEVEL-1 AS Y
                  FROM (SELECT EXTRACT(YEAR FROM min(create_date)) AS MIN_YEAR,
                               EXTRACT(YEAR FROM max(create_date)) AS MAX_YEAR
                        FROM stringlist) s
                  CONNECT BY LEVEL <= (s.max_year-s.min_year)+1) y ON y.y BETWEEN s.min_year AND s.max_year
    )
    SELECT sy.stringid, sy.report_year, 
           NVL(i.first_change, LAG(i.first_change) OVER (PARTITION BY sy.stringid ORDER BY sy.report_year)) AS START_INFO,
           NVL(i.last_change, LAG(i.last_change) OVER(PARTITION BY sy.stringid ORDER BY sy.report_year)) AS END_INFO
    FROM string_years sy
    LEFT JOIN (SELECT sl.stringid, 
                      EXTRACT(YEAR FROM sl.create_date) AS REPORT_YEAR, 
                      MAX(sl.string_start) KEEP (DENSE_RANK FIRST ORDER BY sl.create_date) AS FIRST_CHANGE,
                      MAX(sl.string_end) KEEP (DENSE_RANK LAST ORDER BY sl.create_date) AS LAST_CHANGE
                FROM stringlist sl
                GROUP BY sl.stringid, EXTRACT(YEAR FROM sl.create_date)) i ON sy.stringid = i.stringid AND sy.report_year = i.report_year
    

    陪伴SQLFiddle

    string_years CTE 列出了字符串处于活动状态的所有年份,即使它没有事件。活跃年份是字符串的第一个事件和最后一个事件之间的所有年份。然后我们将其加入到我们的年度摘要中,按 STRINGID 和 YEAR 对聚合函数进行分组。最后,如果一行为空,我们使用 LAG 函数获取上一年的值。

    如果您打算将此查询限制为特定字符串或字符串类型,则在 STRING_YEARS 之前创建另一个 CTE 以在整个查询的其余部分中代替 stringlist 使用可能不会有什么坏处。

    【讨论】:

      猜你喜欢
      • 2011-04-01
      • 2023-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-19
      相关资源
      最近更新 更多