Oracle SQL 查询从一行中的多个列中获取最新数据

Posted

技术标签:

【中文标题】Oracle SQL 查询从一行中的多个列中获取最新数据【英文标题】:Oracle SQL Query to get Latest Data from multiple columns in one row 【发布时间】:2020-04-16 07:35:57 【问题描述】:

我有这样的表 MY_TABLE,它存储来自许多表的更改数据(更新跟踪)。因此,每当其他表有一些更新时,我都会在基表上使用AFTER UPDATE TRIGGER 将新数据存储在 MY_TABLE 中。

ID  RECORD_DESC EMP_ID  FIRST_NAME  LAST_NAME   GENDER  SALARY
1   EMP         5       ABC         XYZ                 
2   EMP         5                               M       
3   EMP         5                   XYZ-NEW     F
4   SAL         5                                       1000
5   EMP         5                               M       
6   SAL         5       ABC-NEW                         750

现在我想查询 MY_TABLE 以获取所有列的最新更改的员工数据,结果应该是这样的行:

EMP_ID  FIRST_NAME  LAST_NAME   GENDER  SALARY
5       ABC-NEW     XYZ-NEW     M       750

到目前为止,我所做的是为每一列获取 MAX(ID),并从该 ID 再次查询表以获取该 ID 的列值。

但问题是这个查询会在 db 上承受相当大的负载,因为我有 25 个这样的列,并且表会随着时间的推移而变大。

那么,有人可以建议我更好的方法来编写下面的查询:

SELECT (SELECT FIRST_NAME FROM MY_TABLE WHERE ID = T2.FIRST_NAME_PK) AS FIRST_NAME
     , (SELECT LAST_NAME  FROM MY_TABLE WHERE ID = T2.LAST_NAME_PK ) AS LAST_NAME
     , (SELECT GENDER     FROM MY_TABLE WHERE ID = T2.GENDER_PK    ) AS GENDER
     , (SELECT SALARY     FROM MY_TABLE WHERE ID = T2.SALARY_PK    ) AS SALARY
  FROM (SELECT (SELECT MAX(ID) FROM MY_TABLE WHERE EMP_ID = T1.EMP_ID AND FIRST_NAME IS NOT NULL) FIRST_NAME_PK     -- ID = 6
             , (SELECT MAX(ID) FROM MY_TABLE WHERE EMP_ID = T1.EMP_ID AND LAST_NAME  IS NOT NULL) LAST_NAME_PK      -- ID = 3
             , (SELECT MAX(ID) FROM MY_TABLE WHERE EMP_ID = T1.EMP_ID AND GENDER     IS NOT NULL) GENDER_PK         -- ID = 5
             , (SELECT MAX(ID) FROM MY_TABLE WHERE EMP_ID = T1.EMP_ID AND SALARY     IS NOT NULL) SALARY_PK         -- ID = 6
          FROM (SELECT DISTINCT EMP_ID
                  FROM MY_TABLE
               ) T1
       ) T2;

【问题讨论】:

发布示例数据的创建、插入语句并显示您想要的输出。 请解释您为什么要这样做。您没有包含当前值的员工表吗?然后,您只需读取此表,这当然比从日志表中收集数据要快得多。 @ThorstenKettner,我只想获取在表上完成的任何更新,所以如果最初在创建员工记录时输入了所有字段。然后只更改拳头名称,然后我只想要名字而不是其他列值。我只想获取已更改的最新数据。 【参考方案1】:

试试这个

SELECT DISTINCT EMP_ID, 
, LAST_VALUE(FIRST_NAME) IGNORE NULLS OVER (PARTITION BY EMP_ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) 
, LAST_VALUE(LAST_NAME) IGNORE NULLS OVER (PARTITION BY EMP_ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) 
, LAST_VALUE(GENDER) IGNORE NULLS OVER (PARTITION BY EMP_ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) 
, LAST_VALUE(SALARY) IGNORE NULLS OVER (PARTITION BY EMP_ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) 
FROM MY_TABLE

【讨论】:

【参考方案2】:

您可以按如下方式使用KEEP 子句:

SELECT ID,
       MAX(FIRST_NAME) KEEP (DENSE_RANK FIRST ORDER BY CASE WHEN FIRST_NAME IS NULL THEN NULL ELSE ID END DESC NULLS LAST) AS FIRST_NAME,
       MAX(LAST_NAME) KEEP (DENSE_RANK FIRST ORDER BY CASE WHEN LAST_NAME IS NULL THEN NULL ELSE ID END DESC NULLS LAST) AS LAST_NAME,
       MAX(GENDER) KEEP (DENSE_RANK FIRST ORDER BY CASE WHEN GENDER IS NULL THEN NULL ELSE ID END DESC NULLS LAST) AS GENDER,
       MAX(SALARY) KEEP (DENSE_RANK FIRST ORDER BY CASE WHEN SALARY IS NULL THEN NULL ELSE ID END DESC NULLS LAST) AS SALARY
  FROM MY_TABLE
GROUP BY ID;

【讨论】:

【参考方案3】:

这个怎么样?查看代码中的 cmets。

SQL> with my_table (id, record_Desc, emp_id, first_name, last_name, gender, salary) as
  2    -- sample data
  3    (select 1, 'emp', 5, 'abc', 'xyz', null , null from dual union all
  4     select 2, 'emp', 5, null, null, 'm', null from dual union all
  5     select 3, 'emp', 5, null, 'xyz-new', 'f', null from dual union all
  6     select 4, 'emp', 5, null, null, null, 1000 from dual union all
  7     select 5, 'emp', 5, null, null, 'm', null from dual union all
  8     select 6, 'emp', 5, 'abc-new', null, null, 750 from dual
  9    ),
 10  temp as
 11    -- find last values
 12    (select a.id,
 13      a.record_desc,
 14      a.emp_id,
 15      last_value(a.first_name ignore nulls) over (partition by a.record_desc, a.emp_id order by a.id) first_name,
 16      last_value(a.last_name  ignore nulls) over (partition by a.record_desc, a.emp_id order by a.id) last_name,
 17      last_value(a.gender     ignore nulls) over (partition by a.record_desc, a.emp_id order by a.id) gender,
 18      last_value(a.salary     ignore nulls) over (partition by a.record_desc, a.emp_id order by a.id) salary
 19     from my_table a
 20    )
 21  -- extract only the last row per RECORD_DESC and EMP_ID
 22  select *
 23  from temp c
 24  where c.id = (select max(b.id) From my_table b
 25                where b.record_desc = c.record_Desc
 26                  and b.emp_id = c.emp_id
 27               );

        ID REC     EMP_ID FIRST_N LAST_NA G     SALARY
---------- --- ---------- ------- ------- - ----------
         6 emp          5 abc-new xyz-new m        750

SQL>

【讨论】:

【参考方案4】:

您的员工表包含当前数据。不过,您只想显示已更改的数据。

我要做的是显示员工数据,以防列在日志表中有更新。我们不必查找最新的更新,因为无论该列多久更新一次,employee 表都包含最后一个值。尽管必须读取整个日志表,但这是一个非常简单的操作。

select
  e.emp_id,
  case when log.some_first_name is not null then e.first_name end as first_name,
  case when log.some_last_name  is not null then e.last_name  end as last_name,
  case when log.some_gender     is not null then e.gender     end as gender,
  case when log.some_salary     is not null then e.salary     end as salary
from employees e
join
(
  select
    emp_id,
    min(first_name) as some_first_name,
    min(last_name)  as some_last_name,
    min(gender)     as some_gender,
    min(salary)     as some_salary
  from my_table
  group by emp_id
) log on log.emp_id = e.emp_id
order by e.emp_id;

一次又一次地运行此查询的替代方法是使用 last_updates 表,其中每个员工一行,并在每次插入现有日志表时填充它的触发器。如果你经常需要这个,那是我会选择的路线。

【讨论】:

以上是关于Oracle SQL 查询从一行中的多个列中获取最新数据的主要内容,如果未能解决你的问题,请参考以下文章

SQL查询从特定列中的每个字段中删除某些文本 - Oracle SQL

用于从具有多个条件的多个表中的多个列中获取数据的存储过程

如何从常规 SQL 查询中输出 XML?

列中多个值和范围的 SQL SELECT 查询

如何在 Oracle PL/SQL 过程的开始部分之后声明游标

oracle sql查询以获取没有空格的列值