用于存储历史数据和每年报告数据的数据库结构

Database structure for storing historical data and reporting on data annually

我正在使用 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

用户只需要 select 字符串的类型和年份。 尺寸必须显示在 selected 年的报告中。

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

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

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

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

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

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

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

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 可能不会有什么坏处查询。