【问题标题】:How do I merge two tables without naming all columns?如何在不命名所有列的情况下合并两个表?
【发布时间】:2012-01-20 16:53:41
【问题描述】:

假设我有这两个具有相同列的表。发挥您的想象力,让它们变得更大:

USER_COUNTERPARTY:
ID  |Name                        |Credit Rating    |Sovereign Risk    |Invoicing Type
----+----------------------------+-----------------+------------------+---------------
1   |Nat Bank of Transnistria    |7                |93                |Automatic
2   |Acme Ltd.                   |25               |12                |Automatic
3   |CowBInd LLP.                |49               |12                |Manual

TEMP:
ID  |Name                        |Credit Rating    |Sovereign Risk    |Invoicing Type
----+----------------------------+-----------------+------------------+---------------
2   |Acacacme Ltd.               |31               |12                |Manual
4   |Disenthralled Nimrod Corp.  |31               |52                |Automatic

我想将它们合并为一个,用第二个替换第一个中具有相同 ID 的任何内容,并插入不存在的任何内容。我可以使用这种说法:

MERGE INTO USER_COUNTERPARTY C
USING TEMP T
ON (C.COUNTER_ID = T.COUNTER_ID)
WHEN MATCHED THEN UPDATE SET
    C.COUNTER_NAME = T.COUNTER_NAME,
    C.COUNTER_CREDIT = T.COUNTER_CREDIT,
    C.COUNTER_SVRN_RISK = T.COUNTER_SVRN_RISK,
    C.COUNTER_INVOICE_TYPE = T.COUNTER_INVOICE_TYPE
WHEN NOT MATCHED THEN INSERT VALUES (
    T.COUNTER_ID,
    T.COUNTER_NAME,
    T.COUNTER_CREDIT,
    T.COUNTER_SVRN_RISK,
    T.COUNTER_INVOICE_TYPE);

这很好,但请注意,我必须为每一列命名。有什么方法可以合并这些表而不必命名所有列? Oracle documentation 坚持我在合并中的“插入”和“设置”之后使用列名,因此可能需要一些其他声明。结果应该是这样的:

ID  |Name                        |Credit Rating    |Sovereign Risk    |Invoicing Type
----+----------------------------+-----------------+------------------+---------------
1   |Nat Bank of Transnistria    |7                |93                |Automatic
2   |Acacacme Ltd.               |31               |12                |Manual
3   |CowBInd LLP.                |49               |12                |Manual
4   |Disenthralled Nimrod Corp.  |31               |52                |Automatic

如果有帮助,我将其粘贴在这里:

CREATE TABLE USER_COUNTERPARTY
( COUNTER_ID             INTEGER       NOT NULL PRIMARY KEY,
  COUNTER_NAME           VARCHAR(38),
  COUNTER_CREDIT         INTEGER,
  COUNTER_SVRN_RISK      INTEGER,
  COUNTER_INVOICE_TYPE   VARCHAR(10) );

INSERT ALL
INTO USER_COUNTERPARTY VALUES (1, ‘Nat Bank of Transnistria’, 7, 93, ‘Automatic’)
INTO USER_COUNTERPARTY VALUES (2, ‘Acme Ltd.’, 25, 12, ‘Manual’)
INTO USER_COUNTERPARTY VALUES (3, ‘CowBInd LLP.’, 49, 12, ‘Manual’)
SELECT * FROM DUAL;

CREATE TABLE TEMP AS SELECT * FROM USER_COUNTERPARTY;
DELETE FROM TEMP;

INSERT ALL
INTO TEMP VALUES (2, ‘Conoco Ltd.’, 25, 12, ‘Automatic’)
INTO TEMP VALUES (4, ‘Disenthralled Nimrod Corp.’, 63, 12, ‘Manual’)
SELECT * FROM DUAL;

【问题讨论】:

  • 可以查询数据字典(如USER_TAB_COLUMNS)生成列名列表。
  • 没错,你可以。但那又如何呢?我可能不得不将它们放入一个集合中,遍历它们,并使用动态插入。对于更新所有列,我认为删除和插入会更好。但我会记住数据字典。
  • “然后呢?”正如我所说:生成列名列表。例如SELECT ','||column_name FROM user_tab_columns WHERE table_name='MYTABLE' ORDER BY column_id; - 然后将结果复制并粘贴到合并语句中。当然,它并不漂亮,如果架构发生变化,必须重新完成,但它可以工作。当然我不是在谈论运行时代码生成:)

标签: sql oracle


【解决方案1】:

我认为您必须避免使用列名的唯一选择是两个单独的语句:

delete from USER_COUNTERPARTY UC
      where exists
              (select null
                 from TEMP T
                where T.COUNTER_ID = UC.COUNTER_ID);

insert into USER_COUNTERPARTY UC
  select *
    from TEMP T
   where not exists
           (select null
              from USER_COUNTERPARTY UC
             where T.COUNTER_ID = UC.COUNTER_ID);

【讨论】:

  • 是的,我想这就是这样做的方法。
  • 但是你怎么用'select null'而不是'select *'呢?这是为了效率吗?我也应该这样做吗?
  • selectexists 的子查询中只是用来指示是否返回一行,返回什么列并不重要。有的人会用select 1,有的人会用select *,其实没什么区别。
  • @JohnDoyle 我认为表别名的语法是错误的。对我来说,第一个查询需要根据stackoverflow.com/questions/11005209/… 更改为“从 USER_COUNTERPARTY UC 中删除 UC”,第二个需要在第一行删除别名,使其“插入 USER_COUNTERPARTY”。
  • 必须考虑的一个很大区别是,DELETE 语句可能存在 ON DELETE CASCADE 约束导致相关表可能“丢失”数据记录。 MERGE 不会发生这种情况。
【解决方案2】:

您可以尝试使用这样的包装联合语句:

SELECT (*) FROM
(SELECT * FROM Table1 WHERE ID NOT IN (SELECT ID FROM Table2)
 UNION ALL
 SELECT * FROM Table2)
ORDER BY 1

【讨论】:

    【解决方案3】:

    我遇到了所描述的问题,并且我解决它的方式技术含量非常低,但我想我会分享它以防它引发人们的其他想法。

    我获取了列名(我从 SQL developer 中的 DDL 表中提取它们,但也使用 tab_columns 表中的方法)并将它们插入到 Excel 电子表格中。然后我删除了 Varchar 等语句(使用文本到列 Excel 函数,然后只删除了 varchar、number 等语句结束的列),所以它只留下了字段名称。然后我在下一个 Excel 列中插入了一个公式,="dest."&A2&"=src."&A2&"," 并填写了所有 110 个字段,然后在一个新的 Excel 列中,使用 =A2&"," 并在一个新列,="src."&A2&",",再次填写所有字段。然后在一个 SQL 表中,我输入:

    merge into <schema>.<destination_table> dest
      using <schema>.<source_table> src
    on (dest.<link> = src.<link>)
    when matched then update set
    (<copy all of the first column, 
      not including the linking fields and removing the comma at the end>)
    when not matched then insert 
    (<copy and paste the second column from Excel, and remove the final comma>)
    values
    (<copy and paste the third column from Excel and remove the final comma>)
    

    我还有一个用于合并具有不同列名的表的版本,但这涉及在 Excel 工作表中映射字段的额外步骤。

    我发现我需要使用合并语句来完成我的工作 - 与存在的更新相比,我发现 Merge 可以节省大量时间。

    【讨论】:

      【解决方案4】:

      如果您有列的默认值(并且您希望使用这些默认值),则可以在插入语句中省略这些值,但否则,您必须指定要为其插入或更新值的每一列。

      SELECT 没有像 * 这样的简写。

      【讨论】:

        【解决方案5】:

        我遇到了同样的问题,我编写了一个获取所有表列的列表并构建动态 sql 查询以进行更新而不命名所有列的过程。

        PROCEDURE update_from_table(
          p_source VARCHAR2,  -- Table to copy all columns from
          p_target VARCHAR2,  -- Table to copy into 
          p_id_name VARCHAR2 -- Primary key name
        )
          AS
        v_sql VARCHAR2(4096) := 'UPDATE ' || p_target || ' t1 SET (';
        v_sql_src VARCHAR2(4096) := ') = (SELECT ';
        v_sql_end VARCHAR2(4096) := ' FROM '|| p_source ||' t2 WHERE t1.'||p_id_name||' = t2.'||p_id_name||') 
        WHERE EXISTS (
        SELECT 1
          FROM '|| p_source ||' t2
         WHERE t1.'||p_id_name||' = t2.'||p_id_name||' )';
        v_first BOOLEAN := TRUE;  
        BEGIN
        FOR col IN
        (select column_name from user_tab_columns
          where table_name = p_source
        )
        LOOP
          IF NOT v_first THEN
            v_sql:= v_sql || ', '; -- adding comma before every arg except first
            v_sql_src := v_sql_src || ', ';
          ELSE
            v_first := FALSE;
          END IF;
          v_sql:= v_sql || col.column_name ;
          v_sql_src:= v_sql_src || col.column_name ;
        END LOOP;
        
        v_sql := v_sql || v_sql_src || v_sql_end;
        EXECUTE IMMEDIATE v_sql;
        END update_from_table;
        

        然后我分两步合并:

        -- Insert not matching records
        INSERT INTO USER_COUNTERPARTY
                    SELECT *
                    FROM TEMP WHERE COUNTER_ID NOT IN (
                SELECT USER_COUNTERPARTY.COUNTER_ID 
                FROM USER_COUNTERPARTY 
                JOIN TEMP ON TEMP.COUNTER_ID = USER_COUNTERPARTY.COUNTER_ID);
        -- Update matching records
        update_from_table('TEMP', 'USER_COUNTERPARTY', 'COUNTER_ID');
        

        【讨论】:

          猜你喜欢
          • 2018-06-21
          • 1970-01-01
          • 2021-06-04
          • 1970-01-01
          • 1970-01-01
          • 2016-10-18
          • 2019-04-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多