如何在 Oracle 中根据 WHERE 子句 Update/insert 记录

How to Update/insert records based on WHERE clause in Oracle

我正在寻找从 pandas 数据帧到 Oracle table 的 upsert(update/insert) 查询。搜索后我得到了这个 merge 语句,它会在键存在时更新值,否则它将插入。


merge into table_A using dual on ( key_column = '123' )
when matched then 
update set date_column = TO_TIMESTAMP('2021-01-05 00:00:11.644', 'YYYY-MM-DD HH24:MI:SS.FF3'), OR = 'MZM'
when NOT matched then 
insert (key_column, date_column, OR) values( '456',TO_TIMESTAMP('2021-04-05 00:00:11.644', 'YYYY-MM-DD HH24:MI:SS.FF3'),'MZM')

我需要的解决方案

待更新

我想update根据条件max(date_column)记录key,记录date_column应该小于或等于30 days ] 我正在插入。

用于插入

我想为已经存在的 key 插入一个新行,但最后可用的记录。即该键的 date_column 不应在 30 天内。

我试过的

merge into table_A using dual on ( key_column = '123' )
when matched then 
update set date_column = TO_TIMESTAMP('2021-12-31 00:00:11.644', 'YYYY-MM-DD HH24:MI:SS.FF3'), ORA = 'MZM'
where (select trunc(TO_TIMESTAMP('2021-01-05 00:00:11.644', 'YYYY-MM-DD HH24:MI:SS.FF3')) - trunc(max(date_column))days from table_A where key = '123') <= 30 
when NOT matched then 
insert (key_column, date_column, ORA) values( '123',TO_TIMESTAMP('2021-12-31 00:00:11.644', 'YYYY-MM-DD HH24:MI:SS.FF3'),'MZM')
where (select trunc(TO_TIMESTAMP('2022-12-31 00:00:11.644', 'YYYY-MM-DD HH24:MI:SS.FF3')) - trunc(max(date_column))days from table_A where key = '123') > 30  ;

但它不起作用:(。

我无法从 pandas 执行此操作,因为数据量很大。

Table娱乐脚本

CREATE TABLE "FDSMLDBUSER"."FDS_upsert" ("key" VARCHAR2(255 BYTE) NOT NULL ENABLE, "date_column" TIMESTAMP (6) NOT NULL ENABLE, "ORA" VARCHAR2(100 BYTE))


insert into "FDSMLDBUSER"."FDS_upsert" ("key", "date_column", ora) values ('123',TO_TIMESTAMP('2021-12-31 00:00:11.644', 'YYYY-MM-DD HH24:MI:SS.FF3'),'MZM')

我可以在 oracle 中使用 upsert 关键字来做到这一点吗?

非常感谢你的帮助。

一般问题:

在table中过去30天的key能不能超过一行? 不,对于过去 30 天的每个密钥,我们应该只有一个记录,而且应该是最新的。如果任何键超过 30 天,那么我们需要为该键插入新行。

table 中可以有未来的日期吗? 还好没有

我使用 cx_oracle 的 executemany 方法将记录插入数据库,我将 pandas dataframe 转换为 dictionary 以在其中启用列名。请在下面找到代码:

connection = cx_Oracle.connect(f'{db_user}/{db_pwd}@{db_host}:{dbport}/{db_service_name}',encoding='UTF-16', nencoding='UTF-16')
cursor = connection.cursor()
parameters = secondary.to_dict(orient='records')
query = config.get('FDS_APAMA_RAWDATA', 'fds_raw_data')
cursor.prepare(query)
cursor.executemany(None,parameters)
for row in cursor:
    print(row[0])
con.commit() 

可以LEFT OUTER JOIN将新数据与现有数据进行对比,查找30天内是否存在一行,如果存在,则使用ROW_NUMBER解析函数查找最新匹配的行并使用 ROWID pseudo-column:

关联更新
MERGE INTO table_A dst
USING (
  SELECT d.*,
         a.ROWID AS rid,
         ROW_NUMBER() OVER (ORDER BY a.date_column DESC NULLS LAST) AS rn
  FROM   ( SELECT '123' AS key_column, 
                  TIMESTAMP '2023-01-05 00:00:11.644' AS date_column,
                  'MZM' AS ora
           FROM   DUAL ) d
         LEFT OUTER JOIN table_A a
         ON (    a.key_column = d.key_column
             AND a.date_column BETWEEN d.date_column - INTERVAL '30' DAY
                                   AND d.date_column + INTERVAL '30' DAY
         )
) src
ON ( src.rid = dst.ROWID AND src.rn = 1)
WHEN MATCHED THEN
UPDATE
  SET date_column = src.date_column,
      ora         = src.ora
WHEN NOT MATCHED THEN
  INSERT (key_column, date_column, ora)
  VALUES (src.key_column, src.date_column, src.ora);

其中,对于示例数据:

CREATE TABLE table_a (
  key_column VARCHAR2(255 BYTE) NOT NULL ENABLE,
  date_column TIMESTAMP (6) NOT NULL ENABLE,
  ORA VARCHAR2(100 BYTE)
);

INSERT INTO table_a (key_column, date_column, ora)
values ('123', TIMESTAMP '2022-12-31 00:00:11.644', 'MZM');

该行更新为:

KEY_COLUMN DATE_COLUMN ORA
123 2023-01-05 00:00:11.644000 MZM

db<>fiddle here

如果键存在最近 30 天内的一行,您要更新该行,否则您要插入一个新行。

on ( key_column = '123' )

不足以找到该行。因此,查找 table 以查找包含键和日期范围的行。例如:

merge into table_a 
using (select 123 as key, date '2022-01-05' as new_date from dual) src
  on (table_a.key_column = src.key and date_column >= src.new_date - interval '30' day)
when matched then
  update set date_column = src.new_date, ora = 'MZM'
when not matched then 
  insert (key_column, date_column, ora) values (src.key, src.new_date, 'MZM');
;

更新

如图所示,Oracle 禁止更新 MERGE 语句的 ON 子句中的列。我认为这是一个缺陷。但是好吧,至少他们提出了一个错误。

解决此问题的一个简单方法是在 PL/SQL 块中使用更新和插入而不是合并语句:如果行存在则更新该行。否则插入(当更新影响零行时)。

begin
  update table_a
  set date_column = :new_date, ora = 'MZM'
  where key = :key and date_column >= :new_date - interval '30' day;
  
  if sql%rowcount = 0 then
    insert into table_a (key_column, date_column, ora) values (:key, :new_date, 'MZM');
  end if;
end;