42p10 error there is no unique or exclusion constraint matching the on conflict specification

I'm getting the following error when doing the following type of insert: Query: INSERT INTO accounts (type, person_id) VALUES ('PersonAccount', 1) ON CONFLICT (type, person_id) WHERE type = '

I’m getting the following error when doing the following type of insert:

Query:

INSERT INTO accounts (type, person_id) VALUES ('PersonAccount', 1) ON
CONFLICT (type, person_id) WHERE type = 'PersonAccount' DO UPDATE SET
updated_at = EXCLUDED.updated_at RETURNING *

Error:

SQL execution failed (Reason: ERROR: there is no unique or exclusion
constraint matching the ON CONFLICT specification)

I also have an unique INDEX:

CREATE UNIQUE INDEX uniq_person_accounts ON accounts USING btree (type,
person_id) WHERE ((type)::text = 'PersonAccount'::text);

The thing is that sometimes it works, but not every time. I randomly get
that exception, which is really strange. It seems that it can’t access that
INDEX or it doesn’t know it exists.

Any suggestion?

I’m using PostgreSQL 9.5.5.

Example while executing the code that tries to find or create an account:

INSERT INTO accounts (type, person_id, created_at, updated_at) VALUES ('PersonAccount', 69559, '2017-02-03 12:09:27.259', '2017-02-03 12:09:27.259') ON CONFLICT (type, person_id) WHERE type = 'PersonAccount' DO UPDATE SET updated_at = EXCLUDED.updated_at RETURNING *
 SQL execution failed (Reason: ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification)

In this case, I’m sure that the account does not exist. Furthermore, it never outputs the error when the person has already an account. The problem is that, in some cases, it also works if there is no account yet. The query is exactly the same.

asked Feb 3, 2017 at 10:43

Tiago Babo's user avatar

Tiago BaboTiago Babo

9941 gold badge6 silver badges9 bronze badges

12

Per the docs,

All table_name unique indexes that, without regard to order, contain exactly the
conflict_target-specified columns/expressions are inferred (chosen) as arbiter
indexes. If an index_predicate is specified, it must, as a further requirement
for inference, satisfy arbiter indexes.

The docs go on to say,

[index_predicate are u]sed to allow inference of partial unique indexes

In an understated way, the docs are saying that when using a partial index and
upserting with ON CONFLICT, the index_predicate must be specified. It is not
inferred for you. I learned this
here, and the following example demonstrates this.

CREATE TABLE test.accounts (
    id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    type text,
    person_id int);
CREATE UNIQUE INDEX accounts_note_idx on accounts (type, person_id) WHERE ((type)::text = 'PersonAccount'::text);
INSERT INTO  test.accounts (type, person_id) VALUES ('PersonAccount', 10);

so that we have:

unutbu=# select * from test.accounts;
+----+---------------+-----------+
| id |     type      | person_id |
+----+---------------+-----------+
|  1 | PersonAccount |        10 |
+----+---------------+-----------+
(1 row)

Without index_predicate we get an error:

INSERT INTO  test.accounts (type, person_id) VALUES ('PersonAccount', 10) ON CONFLICT (type, person_id) DO NOTHING;
-- ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification

But if instead you include the index_predicate, WHERE ((type)::text = 'PersonAccount'::text):

INSERT INTO  test.accounts (type, person_id) VALUES ('PersonAccount', 10)
ON CONFLICT (type, person_id)
WHERE ((type)::text = 'PersonAccount'::text) DO NOTHING;

then there is no error and DO NOTHING is honored.

answered Jan 13, 2019 at 13:57

unutbu's user avatar

unutbuunutbu

818k177 gold badges1755 silver badges1649 bronze badges

1

A simple solution of this error

First of all let’s see the cause of error with a simple example. Here is the table mapping products to categories.

create table if not exists product_categories (
    product_id uuid references products(product_id) not null,
    category_id uuid references categories(category_id) not null,
    whitelist boolean default false
);

If we use this query:

INSERT INTO product_categories (product_id, category_id, whitelist)
VALUES ('123...', '456...', TRUE)
ON CONFLICT (product_id, category_id)
DO UPDATE SET whitelist=EXCLUDED.whitelist;

This will give you error No unique or exclusion constraint matching the ON CONFLICT because there is no unique constraint on product_id and category_id. There could be multiple rows having the same combination of product and category id (so there can never be a conflict on them).

Solution:

Use unique constraint on both product_id and category_id like this:

create table if not exists product_categories (
    product_id uuid references products(product_id) not null,
    category_id uuid references categories(category_id) not null,
    whitelist boolean default false,
    primary key(product_id, category_id) -- This will solve the problem
    -- unique(product_id, category_id) -- OR this if you already have a primary key
);

Now you can use ON CONFLICT (product_id, category_id) for both columns without any error.

In short: Whatever column(s) you use with on conflict, they should have unique constraint.

answered Apr 6, 2021 at 21:56

Ali Sajjad's user avatar

Ali SajjadAli Sajjad

2,94325 silver badges35 bronze badges

The easy way to fix it is by setting the conflicting column as UNIQUE

answered Mar 3, 2021 at 14:58

idirall22's user avatar

idirall22idirall22

1961 silver badge10 bronze badges

I did not have a chance to play with UPSERT, but I think you have a case from
docs:

Note that this means a non-partial unique index (a unique index
without a predicate) will be inferred (and thus used by ON CONFLICT)
if such an index satisfying every other criteria is available. If an
attempt at inference is unsuccessful, an error is raised.

answered Feb 3, 2017 at 13:43

Vao Tsun's user avatar

Vao TsunVao Tsun

45.6k10 gold badges94 silver badges125 bronze badges

1

I solved the same issue by creating one UNIQUE INDEX for ALL columns you want to include in the ON CONFLICT clause, not one UNIQUE INDEX for each of the columns.

CREATE TABLE table_name (
  element_id UUID NOT NULL DEFAULT gen_random_uuid(),
  timestamp TIMESTAMP NOT NULL DEFAULT now():::TIMESTAMP,
  col1 UUID NOT NULL,
  col2 STRING NOT NULL ,
  col3 STRING NOT NULL ,
  CONSTRAINT "primary" PRIMARY KEY (element_id ASC),
  UNIQUE (col1 asc, col2 asc, col3 asc)
);

Which will allow to query like

INSERT INTO table_name (timestamp, col1, col2, col3) VALUES ('timestamp', 'uuid', 'string', 'string')
ON CONFLICT (col1, col2, col3)
DO UPDATE timestamp = EXCLUDED.timestamp, col1 = EXCLUDED.col1, col2 = excluded.col2, col3 = col3.excluded;

answered Jul 23, 2022 at 13:33

Ruben1's user avatar

Содержание

  1. POSTGRESQL: ОШИБКА: нет ограничения уникальности или исключения, соответствующего спецификации ON CONFLICT.
  2. ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
  3. Strange behavior with conflict_target #3258
  4. Comments
  5. Environment
  6. Current behavior
  7. Case 1
  8. Case 2
  9. Case 3
  10. Case 4 ( conflict_target: :email )
  11. Case 5 ( conflict_target: :readers_email_index )
  12. Expected behavior
  13. Re: ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification

POSTGRESQL: ОШИБКА: нет ограничения уникальности или исключения, соответствующего спецификации ON CONFLICT.

I я пытаюсь выполнить операцию upsert для таблицы с именем «message_payload», используя приведенный ниже запрос, но получаю сообщение об ошибке:

ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification SQL state: 42P10

не могли бы вы опубликовать спецификацию таблицы public.error_message ?

о pg_admin. Тогда вам также нужно показать вкладку constraints . ^^

Пожалуйста, покажите нам оператор create table , включая все индексы и ограничения, в виде форматированного текста, а не скриншота, показывающего имена некоторых столбцов.

Таблица была создана с использованием весенних данных jpa с использованием объектов гибернации. Поэтому я не создавал никаких дополнительных индексов.

Скорее всего, вам не хватает уникального ограничения для этого конкретного поля.

Кто-то может запутаться, поэтому, хотя этот ответ правильный, использование слов «уникальное ограничение» отправило меня в кроличью нору, чтобы обнаружить, что вы тоже можете сделать это: ON CONFLICT ON CONSTRAINT constriant_name . , но j_msgid_idx здесь НЕ является ограничением. Это показатель, чтобы вызвать. ограничение.

Источник

ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification

From: Alexander Farber
To: pgsql-general

Subject: ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification Date: 2018-05-05 14:49:10 Message-ID: CAADeyWgSLC-crOa+cFg_EOfVtrmjTXSFFWfEdKuOnKRBUg=qEg@mail.gmail.com Views: Raw Message | Whole Thread | Download mbox | Resend email Thread: Lists: pgsql-general

I am struggling with an UPSERT in PostgreSQL 10.3 and have prepared a
simple test case showing my 2 problems (at
http://sqlfiddle.com/#!17/7e929/13 and also below) —

There is a two-player word game:

CREATE TABLE players (
uid SERIAL PRIMARY KEY,
name text NOT NULL
);

CREATE TABLE games (
gid SERIAL PRIMARY KEY,
player1 integer NOT NULL REFERENCES players ON DELETE CASCADE,
player2 integer NOT NULL REFERENCES players ON DELETE CASCADE,
hand1 char[7] NOT NULL,
hand2 char[7] NOT NULL
);

INSERT INTO players (name) VALUES (‘Alice’), (‘Bob’), (‘Carol’);

I am trying to set up a daily cronjob, which would calculate player
statistics and store them into a table for faster access from web scripts:

CREATE TABLE stats (
uid integer NOT NULL REFERENCES players ON DELETE CASCADE,
single_q_left INTEGER NOT NULL DEFAULT 0
);

Here I have just one statistic: when a player has only the «difficult»
letter «Q» left in her hand.

Below I am trying to calculate such situations per user and store them into
the stats table:

INSERT INTO stats(uid, single_q_left)
SELECT player1, COUNT(*)
FROM games
WHERE hand1 = ‘
GROUP BY player1
ON CONFLICT(uid) DO UPDATE SET
single_q_left = EXCLUDED.single_q_left;

Unfortunately, this gives me the error
«here is no unique or exclusion constraint matching the ON CONFLICT
specification»
and I can not understand it despite rereading
https://www.postgresql.org/docs/9.5/static/sql-insert.html

And my second problem is: the above query only calculates «half the
picture», when a player is stored in the player1 column.

How to add «the second half», when the player had a single Q left, while
she was player2?

Should I use SELECT UNION or maybe CASE WHEN . END?

Источник

Strange behavior with conflict_target #3258

Environment

  • Elixir version (elixir -v): Elixir 1.9.1 (compiled with Erlang/OTP 20)
  • Database and version (PostgreSQL 9.4, MongoDB 3.2, etc.): PostgresSQL 11.5
  • Ecto version (mix deps): ecto: 3.4.0
  • Database adapter and version (mix deps): ecto_sql: 3.4.1
  • Operating system: MacOS Catalina

Current behavior

There is a few different issues happening, I tried it out with the #ecto Slack channel so I’m separating them by cases.

It is worth noting that, for most of the cases below, I create my index in migration like this:

create unique_index(:readers, «lower(slug)», name: :readers_email_index) (I will explain why I manually name it later)

Case 1

My upsert code looks like this:

I am getting this error when I try an upsert:

ERROR 42P10 (invalid_column_reference) there is no unique or exclusion constraint matching the ON CONFLICT specification

When I check my index via psql, this is what I get: :readers_email_index

Case 2

Trying out with different syntax for conflict_target .

This is now returning:

ERROR 42704 (undefined_object) constraint «readers_email_index» for table «readers» does not exist

(As checked in psql again, the index definitely does exists.)

Case 3

Tried to remove the on_conflict altogether:

Ecto is now failing correctly:

Case 4 ( conflict_target: :email )

I also tried to run with conflict_target: :email .

ERROR 42P10 (invalid_column_reference) there is no unique or exclusion constraint matching the ON CONFLICT specification

Case 5 ( conflict_target: :readers_email_index )

unknown field :readers_email_index in conflict_target

It is worth noting that, I initially had my index created like this:

And this works fine, conflict_target is fine, unique_constraint is fine. However I wanted to add lower() to get some DB guarantee, so I did and updated it to look like this:

This now generates me a index name that looks something like :readers_lower_email_index or something and IIRC, conflict_target started failing here. I figured that since the previous index name worked perfectly, I will use that one instead, so I rollbacked my database, created with the previous syntax, got the previous index name ( readers_email_index ), and then updated the whole migration to look like this:

create unique_index(:readers, «lower(slug)», name: :readers_email_index)

For now since this is not working I’ve just resorted not to use lower at all, and decided to manually lowercase data in my application level.

Expected behavior

I expect the conflict_target option to actually work.

The text was updated successfully, but these errors were encountered:

Источник

Re: ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification

From: Adrian Klaver
To: Alexander Farber , pgsql-general

Subject: Re: ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification Date: 2018-05-05 17:03:52 Message-ID: 404675f8-7b5c-32de-1e62-cfb4d824b9f4@aklaver.com Views: Raw Message | Whole Thread | Download mbox | Resend email Thread: Lists: pgsql-general

On 05/05/2018 07:49 AM, Alexander Farber wrote:
> Hello,
>
> I am struggling with an UPSERT in PostgreSQL 10.3 and have prepared a
> simple test case showing my 2 problems (at
> http://sqlfiddle.com/#!17/7e929/13 and also below) —
>
> There is a two-player word game:
>
> CREATE TABLE players (
> uid SERIAL PRIMARY KEY,
> name text NOT NULL
> );
>
> CREATE TABLE games (
> gid SERIAL PRIMARY KEY,
> player1 integer NOT NULL REFERENCES players ON DELETE CASCADE,
> player2 integer NOT NULL REFERENCES players ON DELETE CASCADE,
> hand1 char[7] NOT NULL,
> hand2 char[7] NOT NULL
> );
>
> INSERT INTO players (name) VALUES (‘Alice’), (‘Bob’), (‘Carol’);
>
> INSERT INTO games (player1, player2, hand1, hand2) VALUES
> (1, 2, ‘‘, ‘‘),
> (1, 3, ‘<>‘, ‘‘),
> (3, 2, ‘‘, ‘‘),
> (1, 2, ‘‘, ‘‘),
> (2, 3, ‘‘, ‘‘),
> (2, 3, ‘‘, ‘‘),
> (1, 2, ‘‘, ‘‘);
>
> I am trying to set up a daily cronjob, which would calculate player
> statistics and store them into a table for faster access from web scripts:
>
> CREATE TABLE stats (
> uid integer NOT NULL REFERENCES players ON DELETE CASCADE,
> single_q_left INTEGER NOT NULL DEFAULT 0
> );
>
> Here I have just one statistic: when a player has only the «difficult»
> letter «Q» left in her hand.
>
> Below I am trying to calculate such situations per user and store them
> into the stats table:
>
> INSERT INTO stats(uid, single_q_left)
> SELECT player1, COUNT(*)
> FROM games
> WHERE hand1 = ‘
> GROUP BY player1
> ON CONFLICT(uid) DO UPDATE SET
> single_q_left = EXCLUDED.single_q_left;
>
> Unfortunately, this gives me the error
> «here is no unique or exclusion constraint matching the ON CONFLICT
> specification»
> and I can not understand it despite rereading
> https://www.postgresql.org/docs/9.5/static/sql-insert.html

The uid column in the stats table has neither a unique or exclusion
constraint on it.

>
> And my second problem is: the above query only calculates «half the
> picture», when a player is stored in the player1 column.
>
> How to add «the second half», when the player had a single Q left, while
> she was player2?
>
> Should I use SELECT UNION or maybe CASE WHEN . END?
>
> Thank you
> Alex
>

Источник

I currently have a table which looks like this:

CREATE TABLE "PDPC".collection
(
    col_no bigint NOT NULL DEFAULT nextval('"PDPC".collection_col_no_seq'::regclass),
    q1 character varying(10000) COLLATE pg_catalog."default",
    q2 character varying(10000) COLLATE pg_catalog."default",
    q3 character varying(10000) COLLATE pg_catalog."default",
    q4 character varying(10000) COLLATE pg_catalog."default",
    dg_fkey bigint,
    CONSTRAINT collection_pkey PRIMARY KEY (col_no),
    CONSTRAINT collection_dg_fkey_fkey FOREIGN KEY (dg_fkey)
        REFERENCES "PDPC".datagroup (dg_no) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
)
WITH (
    OIDS = FALSE
)
TABLESPACE pg_default;

ALTER TABLE "PDPC".collection
    OWNER to postgres;

I am trying to execute an UPSERT statement in PHP using postgresql, but i received

Fatal error: Uncaught PDOException: SQLSTATE[42P10]: Invalid column reference: 7 ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification in C:Apache24htdocsconsideration.php:77 Stack trace: #0 C:Apache24htdocsconsideration.php(77): PDOStatement->execute() #1 {main} thrown in C:Apache24htdocsconsideration.php on line 77

My web page currently has a form that takes in a user input of answers to four questions, and these questions will go into "PDPC".collection table. I want to run INSERT or UPDATE according to the dm_fkey, which is the foreign key I have set for this table.

This is the UPSERT statement that I used.

INSERT INTO "PDPC".collection (q1, q2, q3, q4, dg_fkey)
      VALUES (:q1, :q2, :q3, :q4, :dg_no)
      ON CONFLICT(dg_fkey) DO UPDATE 
      SET q1=:q1, q2=:q2, q3=:q3, q4=:q4

#python #postgresql #sqlalchemy #amazon-rds #postgresql-9.3

Вопрос:

(извините, на SO есть много похожих вопросов, но ни один из них я не смог найти достаточно хорошо)

Попытка перейти в таблицу RDS Postgres через временную таблицу…

 import sqlalchemy as sa

# assume db_engine is already set up

with db_engine.connect() as conn:

            conn.execute(sa.text("DROP TABLE IF EXISTS temp_table"))
            build_temp_table = f"""
                CREATE TABLE temp_table (
                unique_id        VARCHAR(40) NOT NULL,
                date             TIMESTAMP,
                amount           NUMERIC,
                UNIQUE (unique_id)
                );
                """
            conn.execute(sa.text(build_temp_table))

            upsert_sql_string = """
                INSERT INTO production_table(unique_id, date, amount)
                SELECT unique_id, date, amount FROM temp_table
                ON CONFLICT (unique_id)
                DO UPDATE SET 
                            date = excluded.date,
                            amount  = excluded.amount
                """
            conn.execute(sa.text(upsert_sql_string))
 

Примечание: production_table настроен одинаково для temp_table

Другие методы, которые я пробовал, включают:

  • Указание unique_id в качестве ПЕРВИЧНОГО КЛЮЧА или UNIQUE в определении таблицы
  • Запуск ALTER TABLE temp_table ADD PRIMARY KEY (unique_id) после создания temp_table

Независимо от того, что я делаю, я получаю ошибку:

psycopg2.errors.InvalidColumnReference: there is no unique or exclusion constraint matching the ON CONFLICT specification

Спасибо

Комментарии:

1. PK Операционная UNIQUE должна быть включена production_table , вот где будет конфликт.

2. да, я забыл упомянуть, что production_table он настроен идентично temp_table

3. Либо это не так, либо есть search_path настройка, указывающая на другую версию production_table

>
> when I manually mocking the data into both tables are fine and when I run
> the procedure, I get errorcode: 42P10 MSG: there is no unique or exclusion
> constraint matching on the CONFLICT specification
>
> the procedure is
>

> INSERT INTO ecisdrdm.bnft_curr_fact AS prod (bnft_fact_id,
> bene_cntry_of_brth_id, bene_cntry_of_rsdc_id,
> bene_cntry_of_ctznshp_id, frm_id, svc_ctr_id, actn_dt_in_id,
> actn_tm_in_id, src_sys_id,
> bnft_hist_actn_id, bene_id, bene_end_dt_id, petnr_app_id, atty_id,
> uscis_emp_id, application_id,
> rmtr_id, prpr_id, mig_filename)
> SELECT stg.bnft_fact_id, stg.bene_cntry_of_brth_id,
> stg.bene_cntry_of_rsdc_id,
> stg.bene_cntry_of_ctznshp_id, stg.frm_id, stg.svc_ctr_id,
> stg.actn_dt_in_id, stg.actn_tm_in_id, stg.src_sys_id,
> stg.bnft_hist_actn_id, stg.bene_id, stg.bene_end_dt_id, stg.petnr_app_id,
> stg.atty_id, stg.uscis_emp_id, stg.application_id,
> stg.rmtr_id, stg.prpr_id, stg.mig_filename
> FROM ecisdrdm.stg_bnft_curr_fact stg
> ON CONFLICT («bnft_fact_id») DO UPDATE
> SET (bnft_fact_id, bene_cntry_of_brth_id, bene_cntry_of_rsdc_id,
>

The documentation and the error message explain the issue.

«there is no unique or exclusion constraint matching on the CONFLICT
specification»

«The optional ON CONFLICT clause specifies an alternative action to raising
a unique violation or exclusion constraint violation error.»
-https://www.postgresql.org/docs/current/sql-insert.html

You have an index, but it is not unique. With partitioning, you cannot
create a unique index on a column that is not contained by your partition
key. So, you need to re-write to skip the use of ON CONFLICT I expect.

I я пытаюсь выполнить операцию upsert для таблицы с именем «message_payload», используя приведенный ниже запрос, но получаю сообщение об ошибке:

ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
SQL state: 42P10

Запрос:

    INSERT INTO public.error_message(
    id, created_by, created_dt, modified_by, modified_dt, attempt, message_headers, 
    message_main_topic, message_payload, message_status, message_status_code)
        VALUES (51, null, null, null, null, 1, '{
        "jsonContent": {
            "content-length": "1635",
            "message_status_code": "417",
            "cookie": "JSESSIONID=279AF4C174E6192BDAB11A067768BBD5",
            "postman-token": "f0f33e86-498f-452a-aaf6-18eb84dc5907",
            "kafka_timestampType": "CREATE_TIME",
            "message_id": "21",
            "kafka_receivedMessageKey": "null",
            "kafka_receivedTopic": "error-topic",
            "accept": "*/*",
            "kafka_offset": "33",
            "kafka_consumer": "org.apache.kafka.clients.consumer.KafkaConsumer@5091bb5f",
            "host": "localhost:8082",
            "content-type": "application/json",
            "connection": "keep-alive",
            "cache-control": "no-cache",
            "kafka_receivedPartitionId": "0",
            "kafka_receivedTimestamp": "1552305428711",
            "accept-encoding": "gzip, deflate",
            "message_main_topic": "ldarsQCustomStatistics.1",
            "user-agent": "PostmanRuntime/7.6.1"
        }
    }', 'ldarsQCustomStatistics.1', '{
        "jsonContent": {
            "messageTime": 16772223422,
            "messageRev": 9,
            "businessId": "DB",
            "messageId": "55"
        }
    }', 1, 201)
    ON CONFLICT ((message_payload->'jsonContent'->>'message_id'))
    DO UPDATE SET attempt = error_message.attempt + 1, message_headers = EXCLUDED.message_headers, 
                  message_status_code = EXCLUDED.message_status_code, message_status = EXCLUDED.message_status, 
                  created_by = EXCLUDED.created_by, created_dt = EXCLUDED.created_dt, 
                  modified_by = EXCLUDED.modified_by, modified_dt = EXCLUDED.modified_dt, 
                  message_main_topic = EXCLUDED.message_main_topic, message_payload = EXCLUDED.message_payload, 
                  id = DEFAULT

To migrate an Oracle database to Amazon Aurora with PostgreSQL compatibility, you usually need to perform both automated and manual tasks. The automated tasks involve schema conversion using AWS Schema Conversion Tool (AWS SCT) and data migration using AWS Database Migration Service (AWS DMS). The manual tasks involve post-AWS SCT migration touch-ups for certain database objects that can’t be migrated automatically.

AWS provides a rich set of documentation that makes homogeneous and heterogeneous database migrations easy. For more information about the Amazon Database Migration Playbooks series, which specifies many basic and complex code conversion strategies, see AWS Database Migration Service resources. Each playbook is a step-by-step guide to help make heterogeneous database migrations faster and easier and achieve database freedom.

AWS SCT automatically converts the source database schema and a majority of the custom code to a format compatible with the target database. In database migration of Oracle to PostgreSQL, AWS SCT automates the conversion of Oracle PL/SQL code to equivalent PL/pgSQL code in PostgreSQL. The custom code that the tool converts includes views, stored procedures, and functions. When a code fragment can’t be automatically converted to the target language, AWS SCT clearly documents all locations that require manual input from the application developer.

AWS SCT can automatically convert a few Oracle MERGE statements, but there are some complex scenarios where the database developer has to manually convert the Oracle MERGE code to PostgreSQL. This post covers how to migrate Oracle MERGE and the challenges involved during a conversion.

All the code snippets shown in this post are tested in Oracle – 12.2 and PostgreSQL – 11.

Understanding the Oracle MERGE statement

The MERGE statement was introduced in Oracle 9i, and provides a way to specify single SQL statements that can conditionally perform INSERT, UPDATE, or DELETE operations on the target table—a task that otherwise requires multiple logical statements. The MERGE statement selects records from the source table and automatically performs multiple DML operations on the target table by specifying a logical structure. Its main advantage is to help avoid the use of multiple inserts, updates, or deletes. MERGE is a deterministic statement, which means that after a row is processed by the MERGE statement, it can’t be processed again using the same MERGE statement.

To demonstrate scenarios of Oracle MERGE, we create the following test tables in Oracle database. PRODUCT is the target table and PRODUCT_DELTA is the source whose rows are merged into the PRODUCT table based on the merge condition:

--Create target table
CREATE TABLE PRODUCT (
    product_name   VARCHAR2(50),
    product_type   VARCHAR2(10),
    unit_price     NUMBER, 
    modified_date  DATE
);

ALTER TABLE PRODUCT 
    ADD CONSTRAINT PRODUCT_PKEY PRIMARY KEY(product_name, product_type);

--Create source table
CREATE TABLE PRODUCT_DELTA (
    product_name   VARCHAR2(50),
    product_type   VARCHAR2(10),
    unit_price     NUMBER, 
    status         CHAR(1)
);

ALTER TABLE PRODUCT_DELTA
    ADD CONSTRAINT PRODUCT_DELTA_PKEY PRIMARY KEY(product_name, product_type);

The following INSERT statements insert sample data into the PRODUCT_DELTA and PRODUCT tables:

--Insert into PRODUCT table
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date) VALUES('PR1',  'A', 10, '01-JAN-2020');
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date) VALUES('PR2',  'C', 10, '01-JAN-2020');
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date) VALUES('PR3',  'B', 10, '01-JAN-2020');
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date) VALUES('PR4',  'B', 10, '01-JAN-2020');
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date) VALUES('PR10', 'A', 10, '01-JAN-2020');

-- insert into PRODUCT_DELTA table
INSERT INTO PRODUCT_DELTA(product_name, product_type, unit_price, status) VALUES('PR1', 'A', 20, 'Y');
INSERT INTO PRODUCT_DELTA(product_name, product_type, unit_price, status) VALUES('PR2', 'C', 20, 'N');
INSERT INTO PRODUCT_DELTA(product_name, product_type, unit_price, status) VALUES('PR5', 'F', 20, 'N');
INSERT INTO PRODUCT_DELTA(product_name, product_type, unit_price, status) VALUES('PR6', 'B', 20, 'N');

In the following code, data from the PRODUCT_DELTA table is merged into the PRODUCT table:

MERGE INTO PRODUCT prd
USING PRODUCT_DELTA src
ON (prd.product_name = src.product_name 
    AND prd.product_type = src.product_type 
   )

WHEN MATCHED THEN
     UPDATE SET
         prd.unit_price = src.unit_price,
         modified_date  = SYSDATE

WHEN NOT MATCHED THEN
     INSERT(product_name, product_type, unit_price, modified_date)
     VALUES(src.product_name, src.product_type, src.unit_price, SYSDATE);

For each row in the target PRODUCT table, Oracle evaluates the search condition known as the merge condition. If the condition is matched, the result becomes true and Oracle updates the row in the target table with the corresponding data from the source PRODUCT_DELTA table. If the condition isn’t matched for any rows, the result is false and Oracle inserts the corresponding row from the source PRODUCT_DELTA table into the target table. The following diagram illustrates this workflow.

We can verify the target table and check if the data in the PRODUCT table matches the preceding diagram. See the following code:

SQL> SELECT * FROM product;

PRODUCT_NAME	PRODUCT_TYPE	UNIT_PRICE MODIFIED_DATE
--------------- --------------- 	---------- ---------------
PR1			A				   20 16-FEB-20
PR2			C				   20 16-FEB-20
PR3			B				   10 01-JAN-20
PR4			B				   10 01-JAN-20
PR10		A				   10 01-JAN-20
PR5			F				   20 16-FEB-20
PR6			B				   20 16-FEB-20

7 rows selected.

Equivalent of Oracle MERGE in PostgreSQL

PostgreSQL doesn’t have a direct MERGE-like construct. However, in PostgreSQL 9.5, the ON CONFLICT clause was added to INSERT, which is the recommended option for many of the Oracle MERGE statements conversion in PostgreSQL. This feature of PostgreSQL is also known as UPSERT—UPDATE or INSERT—and we use UPSERT and ON CONFLICT interchangeably in many places in this post.

We now convert the Oracle MERGE example from earlier to PostgreSQL using ON CONFLICT. The following statements create the PRODUCT and PRODUCT_DELTA tables in PostgreSQL:

--Create target table
CREATE TABLE PRODUCT (
    product_name   CHARACTER VARYING(50),
    product_type   CHARACTER VARYING (10),
    unit_price     DOUBLE PRECISION, 
    modified_date  TIMESTAMP
);

ALTER TABLE PRODUCT 
    ADD CONSTRAINT PRODUCT_PKEY PRIMARY KEY(product_name, product_type);

--Create source table
CREATE TABLE PRODUCT_DELTA (
    product_name   CHARACTER VARYING (50),
    product_type   CHARACTER VARYING (10),
    unit_price     DOUBLE PRECISION, 
    status         CHAR(1)
);

ALTER TABLE PRODUCT_DELTA
    ADD CONSTRAINT PRODUCT_DELTA_PKEY PRIMARY KEY(product_name, product_type);

You can similarly insert the same dataset into the PRODUCT_DELTA and PRODUCT tables in PostgreSQL as you did in the Oracle database tables:

--Insert into PRODUCT table
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date) VALUES('PR1',  'A', 10, '2020-01-01');
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date) VALUES('PR2',  'C', 10, '2020-01-01');
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date) VALUES('PR3',  'B', 10, '2020-01-01');
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date) VALUES('PR4',  'B', 10, '2020-01-01');
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date) VALUES('PR10', 'A', 10, '2020-01-01');

-- insert into PRODUCT_DELTA table
INSERT INTO PRODUCT_DELTA(product_name, product_type, unit_price, status) VALUES('PR1', 'A', 20, 'Y');
INSERT INTO PRODUCT_DELTA(product_name, product_type, unit_price, status) VALUES('PR2', 'C', 20, 'N');
INSERT INTO PRODUCT_DELTA(product_name, product_type, unit_price, status) VALUES('PR5', 'F', 20, 'N');
INSERT INTO PRODUCT_DELTA(product_name, product_type, unit_price, status) VALUES('PR6', 'B', 20, 'N');

The following PostgreSQL ON CONFLICT code block is equivalent to the Oracle MERGE, and does the following tasks:

  • Inserts rows to the target table from the source table if the rows don’t exist in the target table.
  • Updates non-key columns in the target table when the source table has some rows with the same keys as the rows in the target table. However, these rows have different values for the non-key columns.
    INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date)
      SELECT   src.product_name,
             src.product_type,
             src.unit_price,
             now() modified_date
      FROM ( SELECT
             		product_name,
               	product_type,
               	unit_price
            	FROM PRODUCT_DELTA
      ) src
    
    ON CONFLICT(product_name, product_type)
    
    DO UPDATE
           SET
             unit_price   = excluded.unit_price,
             modified_date = excluded.modified_date;

You can verify the data matches in the Oracle and PostgreSQL PRODUCT table after the code is converted and run in PostgreSQL:

pg11=> select * from product;
 product_name | product_type | unit_price |       modified_date
--------------+--------------+------------+----------------------------
 PR3          | B            |         10 | 2020-01-01 00:00:00
 PR4          | B            |         10 | 2020-01-01 00:00:00
 PR10         | A            |         10 | 2020-01-01 00:00:00
 PR1          | A            |         20 | 2020-02-16 10:54:41.177131
 PR2          | C            |         20 | 2020-02-16 10:54:41.177131
 PR5          | F            |         20 | 2020-02-16 10:54:41.177131
 PR6          | B            |         20 | 2020-02-16 10:54:41.177131
(7 rows)

Considerations

You typically need to perform the following while migrating MERGE statements from Oracle to PostgreSQL:

  1. Remove FROM DUAL if it exists in your Oracle MERGE code.
  2. Remove any table prefix from the UPDATE statement SET clause.
  3. Don’t include a table prefix for the merge condition columns in the ON CONFLICT clause.
  4. Verify that the target table has a primary key, unique key, or unique index on the merge condition columns.

The following diagram illustrates the PostgreSQL INSERT INTO .. ON CONFLICT .. DO UPDATE, which is the equivalent of the Oracle MERGE statement for common use cases.

Using ON CONFLICT in PostgreSQL

PostgreSQL ON CONFLICT enables developers to write less code and do more work in SQL, and provides additional guaranteed insert-or-update atomicity. With ON CONFLICT, the record is inserted if not present and updated if the record already exists. One of those two outcomes are guaranteed, regardless of concurrent activity. This is the main reason you gain performance using ON CONFLICT in PostgreSQL while having little or no further thought to concurrency.

Common MERGE use cases

During database migration from Oracle to PostgreSQL, you typically need to understand how PostgreSQL UPSERT with ON CONFLICT differs from Oracle MERGE. In some Oracle MERGE use cases, migrating to PostgreSQL can become challenging. This post explains the following common use cases and recommends best practices:

  • Oracle MERGE doesn’t force a unique index to be defined on the column or columns of the MERGE statement’s ON But in PostgreSQL, the target table must have a unique key or unique index on the columns that are part of the ON CONFLICT clause and guarantees an atomic INSERT or UPDATE outcome.
  • The MERGE condition contains a function-based comparison.
  • The MERGE condition in Oracle describes the minimum requirement by allowing an equi or non-equi joins condition. But in PostgreSQL, the ON expression needs to be evaluated to see if it properly compares to a unique record on the target table.
  • Oracle MERGE can contain all or any of INSERT, UPDATE, and DELETE, but PostgreSQL doesn’t support DELETE with ON CONFLICT.

This post explains all the restraints with ON CONFLICT in detail and shows you how to handle it correctly and efficiently during a database migration process.

Use case 1: The destination table doesn’t have a primary key, unique key, or unique index

A primary key, unique key, or unique index isn’t mandatory for a MERGE statement in Oracle. It just compares rows from the source and target table based on the merge condition specified and runs successfully. However, in PostgreSQL, ON CONFLICT produces the error there is no unique or exclusion constraint matching the ON CONFLICT specification when the target table doesn’t have a primary key, unique key, or unique index for the columns specified in ON CONFLICT.

Therefore, to use the ON CONFLICT clause in PostgreSQL, you must verify if the target table has a primary key, unique key, or unique index created for the condition columns.

For our use case, you have the following condition in your Oracle MERGE statement, which you’re going to migrate to PostgreSQL, and your PostgreSQL destination table doesn’t have a unique index for the condition columns:

--Oracle Merge condition in a MERGE query

ON (prd.product_name = src.product_name 
     AND prd.product_type = src.product_type 
    )

Rows that match the preceding merge criteria are updated. Ideally, you must always UPDATE rows based on a unique condition and defining a primary key, unique key, and unique index on the columns makes the job easier. When the table columns that are part of a condition aren’t unique, you might update more than one row, and the result is indeterminate. Therefore, the preceding condition doesn’t guarantee uniqueness, and you might get unpredictable results when the table has duplicate rows for the same condition.

However, PostgreSQL identifies this situation as not a best practice and therefore enforces a unique constraint or index on the columns in ON CONFLICT.

You can create either a primary key, unique key, or unique index on the target table for the condition columns, which helps identify a row in the table uniquely.

The following code creates a primary key for product_name and product_type in the target table in PostgreSQL:

--Creating Primary Key in PostgreSQL for Merge columns

ALTER TABLE PRODUCT
ADD CONSTRAINT PRODUCT_PKEY PRIMARY KEY(product_name, product_type);

The following code creates a unique key for product_name and product_type in the target table in PostgreSQL:

--Creating Unique Key in PostgreSQL for Merge columns

ALTER TABLE PRODUCT
ADD CONSTRAINT PRODUCT_UNQ UNIQUE(product_name, product_type);

The following code creates a unique key for product_name and product_type in the target table in PostgreSQL:

--Creating Unique Key in PostgreSQL for Merge columns

ALTER TABLE PRODUCT
ADD CONSTRAINT PRODUCT_UNQ UNIQUE(product_name, product_type);

The following code creates a unique index for product_name and product_type in the target table in PostgreSQL:

--Creating Unique Index in PostgreSQL for Merge columns

CREATE UNIQUE INDEX IDX_UNQ_PRD_1
ON PRODUCT(product_name, product_type);

After you create the primary key, unique key, or unique index on the destination table, you can follow the ON CONFLICT example explained earlier to migrate your Oracle MERGE code.

Use case 2: The MERGE condition contains a function-based comparison

In an Oracle MERGE statement, the ON clause specifies the condition upon which the MERGE operation either updates or inserts. Oracle provides a much more flexible way of writing merge conditions by allowing any operator or function in the merge condition. However, in PostgreSQL, the target table must have a unique index for the columns or conditions specified in the ON CONFLICT clause.

For example, if you have the following merge condition in a MERGE query in Oracle, you need to create a unique index in PostgreSQL for the exact matching merge condition specified in the Oracle MERGE join condition:

ON (prd.product_name = src.product_name
    AND nvl(prd.product_type,'NA') = nvl(src.product_type, 'NA') 
   )

In PostgreSQL, creating a unique index on product_name and product_type for this use case doesn’t work, because this isn’t the equivalent condition for the Oracle merge condition. You get the there is no unique or exclusion constraint matching the ON CONFLICT specification error if you create the following index:

--Unique Index on columns, but does not include function
CREATE UNIQUE INDEX PRODUCT_UPS 
ON PRODUCT(product_name, product_type);

These types of Oracle MERGE statements are bit tricky to migrate in PostgreSQL. Following the best practices in PostgreSQL, you must check if an exact functional unique index exists in PostgreSQL or not. If not, you must create a functional unique index in PostgreSQL and can convert code to the equivalent ON CONFLICT:

--Function based unique Index creation
CREATE UNIQUE INDEX PRODUCT_UPS 
ON PRODUCT(product_name, coalesce(product_type, 'NA'));

The following code demonstrates how you can migrate Oracle MERGE to PostgreSQL when you have a complex MERGE join condition:

MERGE INTO PRODUCT prd
USING PRODUCT_DELTA src
ON (		prd.product_name            = src.product_name
     AND 	nvl(prd.product_type, 'NA') = nvl(src.product_type, 'NA') 
   )

WHEN MATCHED THEN
    UPDATE SET
             prd.unit_price = src.unit_price,
             prd.modified_date = SYSDATE

WHEN NOT MATCHED THEN
INSERT(product_name, product_type, unit_price, modified_date)
VALUES(src.product_name, src.product_type, src.unit_price, SYSDATE);

The following code creates a unique functional index in PostgreSQL and illustrates how you can migrate Oracle MERGE statements to PostgreSQL when the merge condition isn’t an equi-join or contains a functional comparison:

--Function based unique Index creation
CREATE UNIQUE INDEX PRODUCT_UPS 
ON PRODUCT(product_name, coalesce(product_type, 'NA'));

--Converted to ON CONFLICT
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date)
SELECT del.product_name, del.product_type, del.unit_price, now()
  FROM PRODUCT_DELTA del

 ON CONFLICT(product_name, coalesce(product_type, 'NA'))

DO UPDATE
       SET unit_price = excluded.unit_price,
           modified_date = now();

Use case 3: The MERGE condition isn’t an equi-join

Oracle MERGE uses the ON clause to specify the condition, which can take any available operator. Based on the result of the condition, the MERGE operation either updates or inserts rows into the target table. You may sometimes encounter use cases in Oracle that need additional considerations when migrating to PostgreSQL.

For example, we use the following Oracle MERGE use case and migrate to PostgreSQL:

MERGE INTO PRODUCT prd
USING (SELECT p_name  product_name,
               p_type  product_type,
               p_price unit_price
          FROM DUAL
       ) src
ON (prd.product_name = src.product_name
     AND 	nvl(prd.product_type,'NA') = nvl(src.product_type, 'NA')
     AND   prd.unit_price > src.unit_price
   )
WHEN MATCHED THEN
    UPDATE SET
             prd.modified_date = '31-DEC-9999'
WHEN NOT MATCHED THEN
INSERT(product_name,product_type,unit_price, modified_date)
VALUES(src.product_name,src.product_type,src.unit_price, SYSDATE);

The following PostgreSQL code is the equivalent migrated code for the preceding Oracle MERGE:

INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date)
SELECT   src.product_name,
         src.product_type,
         src.unit_price,
         now() modified_date
  FROM (SELECT p_name  product_name,
               p_type  product_type,
               p_price unit_price
  ) src
ON CONFLICT(product_name, product_type)
DO UPDATE
       SET
         modified_date = '9999-12-31'
    WHERE PRODUCT.unit_price > excluded.unit_price;

Use case 4: PostgreSQL doesn’t support DELETE with ON CONFLICT

You can add an optional DELETE WHERE clause to the MATCHED clause to clean up after a merge operation. The DELETE clause deletes only the rows from the target table for which both the ON clause and DELETE WHERE clause conditions evaluates to TRUE.

When using MERGE for the DELETE operation, you must remember the following:

  • DELETE checks the match condition on the target table, not the source.
  • First the UPDATE SET … WHERE operation is performed, then the DELETE WHERE clause condition takes the updated value for its evaluation, not the original value from the table.
  • DELETE works only on rows updated during MERGE. Any rows in the target table that aren’t processed during MERGE aren’t deleted, even if they match the DELETE condition.

Migrating MERGE statements containing INSERT, UPDATE, and DELETE

Let’s examine a use case to understand how you can migrate a complex Oracle MERGE statement to PostgreSQL, which contains INSERT, UPDATE, and DELETE clauses in a single operation:

  • Insert rows to the PRODUCT table from the PRODUCT_DELTA table if the rows don’t exist in the PRODUCT
  • Update non-key columns in the PRODUCT table when the PRODUCT_DELTA table has some rows with the same keys as the rows in the PRODUCT table. However, these rows have different values for the non-key columns.
  • Delete matching rows from the PRODUCT table when status = ‘Y’ in the PRODUCT_DELTA

See the following code:

Oracle sample code:
MERGE INTO PRODUCT prd
USING PRODUCT_DELTA src
ON (prd.product_name = src.product_name
     AND nvl(prd.product_type, 'NA') = nvl(src.product_type, 'NA') 
   )

WHEN MATCHED THEN
    UPDATE SET
             prd.unit_price    = src.unit_price,
             prd.modified_date = SYSDATE

    DELETE WHERE (src.status = 'Y')

WHEN NOT MATCHED THEN
     INSERT(product_name, product_type, unit_price, modified_date)
     VALUES(src.product_name, src.product_type, src.unit_price, SYSDATE);

We can check the rows in the PRODUCT table:

SQL> SELECT * FROM product;

PRODUCT_NAME	PRODUCT_TYPE	UNIT_PRICE MODIFIED_DATE
--------------- --------------- 	---------- ---------------
PR5			F				   20 16-FEB-20
PR6			B				   20 16-FEB-20
PR2			C				   20 16-FEB-20
PR3			B				   10 01-JAN-20
PR4			B				   10 01-JAN-20
PR10		A				   10 01-JAN-20

6 rows selected.

Because you can’t delete rows using the ON CONFLICT clause in PostgreSQL, it becomes very tricky to migrate such code to PostgreSQL. As database developers, you can write code in many ways, but it’s essential to know which is the best option for a particular problem statement. In this section, we explore some workarounds for this use case and identify the best option.

Creating UPSERT with ON CONFLICT + DELETE with CTE

You can only insert or update based on the matching columns through the ON CONFLICT clause. To achieve DELETE in the same statement as ON CONFLICT in PostgreSQL, you can use CTE (common table expression) to perform DELETE and ON CONFLICT for UPSERT. But if the statements aren’t organized correctly, it might produce erroneous output. Therefore, it’s important during a conversion to refactor code correctly and test properly. A little negligence during code migration could lead to functional abnormalities and data corruption.

The following code runs correctly but has several semantic error and race conditions:

--Using ON CONFLICT with CTE
WITH del AS (
    DELETE FROM PRODUCT prd
     WHERE EXISTS ( SELECT 1 
FROM PRODUCT_DELTA p2
                     WHERE prd.product_name = p2.product_name
                       AND prd.product_type = p2.product_type
                       AND p2.status = 'Y'
                  )
    RETURNING prd.*
)

INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date)
SELECT del.product_name, del.product_type, del.unit_price, now()
  FROM PRODUCT_DELTA del
ON CONFLICT (product_name, coalesce(product_type, 'NA'))

DO UPDATE
  SET unit_price   = excluded.unit_price,
      modified_date = now();

Compare the output of the PRODUCT table in Oracle and you can identify the data mismatches:

pg11=> select * from product;
 product_name | product_type | unit_price |       modified_date
--------------+--------------+------------+----------------------------
 PR3          | B            |         10 | 2020-01-01 00:00:00
 PR4          | B            |         10 | 2020-01-01 00:00:00
 PR10         | A            |         10 | 2020-01-01 00:00:00
 PR1          | A            |         20 | 2020-08-16 18:39:07.438928
 PR2          | C            |         20 | 2020-08-16 18:39:07.438928
 PR5          | F            |         20 | 2020-08-16 18:39:07.438928
 PR6          | B            |         20 | 2020-08-16 18:39:07.438928
(7 rows)

This approach has the following issues:

  • The preceding query isn’t prevented from being used concurrently. To avoid race conditions on a busy database, a LOCK TABLE command is required, which is even more reason to speed up the INSERT/UPDATE transaction.
  • The organization of DML commands in the query doesn’t really rival with Oracle MERGE . This means in an Oracle MERGE statement; the run order is first UPDATE, followed by DELETE, and finally INSERT. Also, DELETE works only on rows updated during MERGE.

Breaking Oracle MERGE into individual DML statements

You can break Oracle MERGE to individual DML statements in PL/pgSQL in the same order MERGE is performed. A good way to implement this idea is with a manual lock command:

LOCK TABLE <destination table> IN SHARE ROW EXCLUSIVE MODE;

That lock level isn’t automatically acquired by any PostgreSQL command, so the only way it helps you is when you’re running concurrent transactions. When you know that no UPSERTs overlap through concurrent transactions or sessions, you can omit that lock. See the following code:

--To be safe, the affected table needs to be locked
LOCK TABLE PRODUCT IN SHARE ROW EXCLUSIVE MODE;
LOCK TABLE PRODUCT_DELTA IN SHARE ROW EXCLUSIVE MODE;


--Updating PRODUCT table
UPDATE PRODUCT prd
  SET unit_price = src.unit_price,
      modified_date = now()
  FROM PRODUCT_DELTA src
  WHERE (     prd.product_name = src.product_name 
          AND prd.product_type = src.product_type 
        );


--Deleting from PRODUCT table
DELETE FROM PRODUCT prd
WHERE exists (select 1 from PRODUCT_DELTA del where prd.product_name = del.product_name 
AND prd.product_type = del.product_type 
AND del.status = 'Y');


--Insert to PRODUCT table
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date)
SELECT del.product_name, del.product_type, del.unit_price, now()
  FROM PRODUCT_DELTA del
 WHERE NOT EXISTS ( SELECT 1 FROM PRODUCT u 
                    WHERE u.product_name = del.product_name
                      AND u.product_type = del.product_type
				     )
AND del.status != 'Y';

For more information about locks, see Explicit Locking.

Using one statement to INSERT, UDPATE, and DELETE using CTE

Although the preceding method is logical and easy to follow, the reason for moving to a single statement is to improve performance and reduce the chance of race conditions. See the following code:

--To be safe, the affected table needs to be locked
LOCK TABLE PRODUCT IN SHARE ROW EXCLUSIVE MODE;
LOCK TABLE PRODUCT_DELTA IN SHARE ROW EXCLUSIVE MODE;


--Single sql to INSERT, UDPATE and DELETE using CTE
WITH upsert AS (
  UPDATE PRODUCT prd
  SET unit_price   = src.unit_price,
      modified_date = now()
  FROM PRODUCT_DELTA src
  WHERE EXISTS (SELECT 1 FROM PRODUCT_DELTA del
                   WHERE prd.product_name = del.product_name 
                     AND prd.product_type = del.product_type
               )
  RETURNING prd.*
),
del AS (
    DELETE FROM PRODUCT prd
     WHERE EXISTS ( SELECT 1 FROM PRODUCT_DELTA del
                     WHERE prd.product_name = del.product_name
                       AND prd.product_type = del.product_type
                       AND del.status = 'Y'
                  )
    RETURNING prd.*
),
processed as (
   SELECT * FROM del
   UNION ALL
   SELECT * FROM upsert
)
INSERT INTO PRODUCT(product_name, product_type, unit_price, modified_date)
SELECT del.product_name, del.product_type, del.unit_price, now()
  FROM PRODUCT_DELTA del
 WHERE NOT EXISTS ( SELECT 1 FROM processed u
                    WHERE u.product_name = del.product_name
                      AND u.product_type = del.product_type
                  );

Conclusion

This post covered the most common types of MERGE statements used in Oracle and explained how you can convert them to equivalent PostgreSQL code. This post may be helpful if you’re experiencing issues with MERGE statements while migrating from an Oracle database to a PostgreSQL database.

If you have other issues not covered by these solutions, please leave a comment.


About the Authors

Venkatramana Chintha is an Associate Consultant with the Professional services team at AWS.
He works with AWS Technology and Consulting partners to provide guidance and technical assistance
on homogeneous and heterogeneous database migrations and Re-hosting migrations

Sashikanta Pattanayak is an Associate Consultant with the Professional services team at AWS.
He works with customers to build scalable, highly available and secure solutions in the AWS cloud.
He specializes in homogeneous and heterogeneous database migrations.

Environment

  • Elixir version (elixir -v): Elixir 1.9.1 (compiled with Erlang/OTP 20)
  • Database and version (PostgreSQL 9.4, MongoDB 3.2, etc.): PostgresSQL 11.5
  • Ecto version (mix deps): ecto: 3.4.0
  • Database adapter and version (mix deps): ecto_sql: 3.4.1
  • Operating system: MacOS Catalina

Current behavior

There is a few different issues happening, I tried it out with the #ecto Slack channel so I’m separating them by cases.

It is worth noting that, for most of the cases below, I create my index in migration like this:

create unique_index(:readers, "lower(slug)", name: :readers_email_index) (I will explain why I manually name it later)

Case 1

My upsert code looks like this:

%Reader{}
    |> Reader.changeset(attrs)
    |> Repo.insert(
      on_conflict: [set: [updated_at: now]],
      conflict_target: [:email],
      returning: [:id]
    )

I am getting this error when I try an upsert:

ERROR 42P10 (invalid_column_reference) there is no unique or exclusion constraint matching the ON CONFLICT specification

When I check my index via psql, this is what I get: :readers_email_index

Case 2

Trying out with different syntax for conflict_target.

%Reader{}
    |> Reader.changeset(attrs)
    |> Repo.insert(
      on_conflict: [set: [updated_at: now]],
-      conflict_target: [:email],
+      conflict_target: {:constraint, :readers_email_index},
      returning: [:id]
    )

This is now returning:

ERROR 42704 (undefined_object) constraint «readers_email_index» for table «readers» does not exist

(As checked in psql again, the index definitely does exists.)

Case 3

Tried to remove the on_conflict altogether:

%Reader{}
    |> Reader.changeset(attrs)
    |> Repo.insert()

Ecto is now failing correctly:

no match of right hand side value: {:error, #Ecto.Changeset<action: :insert, changes: %{email: "asd@gmail.com"}, errors: [email: {"has already been taken", [constraint: :unique, constraint_name: "readers_email_index"]}], data: #Caesar.Readers.Reader<>, valid?: false>}

Case 4 (conflict_target: :email)

I also tried to run with conflict_target: :email.

This returns:

ERROR 42P10 (invalid_column_reference) there is no unique or exclusion constraint matching the ON CONFLICT specification

Case 5 (conflict_target: :readers_email_index)

unknown field :readers_email_index in conflict_target

Note

It is worth noting that, I initially had my index created like this:

create unique_index(:readers, :email)

And this works fine, conflict_target is fine, unique_constraint is fine. However I wanted to add lower() to get some DB guarantee, so I did and updated it to look like this:

- create unique_index(:readers, :email)
+ create unique_index(:readers, "lower(slug)")

This now generates me a index name that looks something like :readers_lower_email_index or something and IIRC, conflict_target started failing here. I figured that since the previous index name worked perfectly, I will use that one instead, so I rollbacked my database, created with the previous syntax, got the previous index name (readers_email_index), and then updated the whole migration to look like this:

create unique_index(:readers, "lower(slug)", name: :readers_email_index)

For now since this is not working I’ve just resorted not to use lower at all, and decided to manually lowercase data in my application level.

Expected behavior

I expect the conflict_target option to actually work.

Понравилась статья? Поделить с друзьями:

Читайте также:

  • 42901 ошибка тендер
  • 429 ютуб ошибка сервера
  • 429 слишком много запросов как исправить
  • 429 ошибка стим
  • 429 ошибка на сайте арбитражного суда

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии