ORACLE 12c Сбой уникального ограничения в команде MERGE, которое соответствует всем столбцам из индекса - PullRequest
2 голосов
/ 20 марта 2019

У меня есть таблица агрегированных данных, которые были собраны неправильно, и я пытаюсь объединить результаты выбора в нее, и я столкнулся с нарушением ограничения и, возможно, пропустил что-то очевидное. Таблица проста:

 CREATE TABLE "DVRA_STATS_AGG_HOURLY" 
       (    "ID" NUMBER(*,0), 
        "SHIPMENTS" NUMBER, 
        "EVENT_DATETIME" DATE, 
        "COUNTRY" VARCHAR2(2 CHAR), 
        "DATA_TYPE" VARCHAR2(3 CHAR), 
        "EVENT_TYPE" CHAR(15)
       )
    CREATE UNIQUE INDEX "DVRA_STATS_AGG_HOURLY_PK" ON "DVRA_STATS_AGG_HOURLY" ("ID")
    CREATE UNIQUE INDEX "DVRA_STATS_AGG_HOURLY_UK1" ON "DVRA_STATS_AGG_HOURLY" ("EVENT_DATETIME", "COUNTRY", "DATA_TYPE", "EVENT_TYPE")
 CREATE INDEX "DVRA_STATS_AGG_HOURLY_INDEX1" ON "DVRA_STATS_AGG_HOURLY" ("EVENT_DATETIME" DESC)

С последовательностью запуска и триггером

CREATE OR REPLACE EDITIONABLE TRIGGER "DVRA_STATS_AGG_HOURLY_AINC" 
BEFORE INSERT ON dvra_stats_agg_hourly 
FOR EACH ROW

BEGIN
  SELECT stats_seq.NEXTVAL
  INTO   :new.id
  FROM   dual;
END;

Слияние тоже не сложно:

MERGE INTO dvra_stats_agg_hourly stats
USING(
    SELECT COUNT(*) as SHIPMENTS, TRUNC(event_datetime,'HH24') as event_datetime,COUNTRY,data_type,event_type
FROM AUDIT 
WHERE   event_type in (<List of events>)
    and TRUNC(event_datetime,'HH24') < trunc(sysdate,'HH24')
    and audittable.event_datetime is not null
    and audittable.COUNTRY is not null
    and audittable.data_type is not null
    and audittable.event_type is not null
GROUP BY TRUNC(event_datetime,'HH24'),COUNTRY,data_type,event_type
    ) audittable 
    ON (
            audittable.event_datetime = stats.event_datetime 
        and audittable.COUNTRY = stats.COUNTRY
        and audittable.data_type = stats.data_type 
        and audittable.event_type = stats.event_type
        )
WHEN MATCHED THEN 
    UPDATE SET stats.SHIPMENTS = audittable.SHIPMENTS WHERE stats.SHIPMENTS <> audittable.SHIPMENTS
WHEN NOT MATCHED THEN 
    INSERT (SHIPMENTS,event_datetime,STATS.COUNTRY,data_type,event_type)
    VALUES (audittable.SHIPMENTS,audittable.event_datetime,audittable.COUNTRY ,audittable.data_type ,audittable.event_type)
;

И я получаю ошибку: ORA-00001: уникальное ограничение (DVRA_MONITORING.DVRA_STATS_AGG_HOURLY_UK1) нарушено

Я сопоставляю те же столбцы, что и в уникальном ограничении. Я вставляю только несоответствующие строки и группирую по одним и тем же столбцам, поэтому для уникальной комбинации этих столбцов должна быть только одна строка.

Я выполняю эту операцию только в одной таблице обновления, поэтому никакой другой сеанс не манипулирует данными во время выполнения запроса.

Что мне здесь не хватает?

Изменить: данные в DVRA_STATS_AGG_HOURLY были вставлены с вставкой в ​​выбор. С таким же выбором, который используется в этом слиянии. Просто некоторые данные еще не были загружены должным образом, поэтому я делаю исправление.

Edit2: добавлено не ноль

Edit3: изменения в запросе на слияние после обсуждения с Alex

MERGE INTO dvra_stats_agg_hourly stats
USING(
    SELECT COUNT(*) as SHIPMENTS, TRUNC(event_datetime,'HH24') as event_datetime,COUNTRY,data_type,event_type
    FROM BCTCUSTOM.V_DVRA_AUDIT 
    WHERE   event_type in (<List of types>)
        and TRUNC(event_datetime,'HH24') < trunc(sysdate,'HH24')
        and TRUNC(event_datetime,'HH24') is not null
        and event_datetime is not null
        and COUNTRY is not null
        and data_type is not null
        and event_type is not null
    GROUP BY TRUNC(event_datetime,'HH24'),COUNTRY,data_type,event_type
    ) audittable 
    ON (
            audittable.event_datetime = stats.event_datetime 
        and ((audittable.COUNTRY is null and stats.COUNTRY is null) or audittable.COUNTRY = stats.COUNTRY)
        and ((audittable.data_type is null and stats.data_type is null) or audittable.data_type = stats.data_type)
        and ((audittable.event_type is null and stats.event_type is null) or audittable.event_type = stats.event_type)
        )
WHEN MATCHED THEN 
    UPDATE SET stats.SHIPMENTS = audittable.SHIPMENTS WHERE stats.SHIPMENTS <> audittable.SHIPMENTS
WHEN NOT MATCHED THEN 
    INSERT (SHIPMENTS,event_datetime,STATS.COUNTRY,data_type,event_type)
    VALUES (audittable.SHIPMENTS,audittable.event_datetime,audittable.COUNTRY ,audittable.data_type ,audittable.event_type)
;

1 Ответ

3 голосов
/ 20 марта 2019

Если какой-либо из четырех столбцов, которые вы сравниваете в предложении ON, равен нулю, он не будет соответствовать вашим текущим условиям, поскольку null = null неизвестно.

Вы можете добавить явные проверки нуля; вместо:

ON (
        audittable.event_datetime = stats.event_datetime 
    and audittable.COUNTRY = stats.COUNTRY
    and audittable.data_type = stats.data_type 
    and audittable.event_type = stats.event_type
    )

сделать что-то вроде:

ON (
        audittable.event_datetime = stats.event_datetime 
    and ((audittable.COUNTRY is null and stats.COUNTRY is null)
       or audittable.COUNTRY = stats.COUNTRY)
    and ((audittable.data_type is null and stats.data_type is null)
       or audittable.data_type = stats.data_type)
    and ((audittable.event_type is null and stats.event_type is null)
       or audittable.event_type = stats.event_type)
    )

db <> скрипка без нулей - работает нормально.

db <> fiddle with null - изначально не удается, но работает с добавлением явных проверок null.


Другая возможность - несоответствие типов данных, так как вы используете CHAR(15) в таблице, которую вы показали. Если в таблице аудита этот столбец определен как VARCHAR2(15), то сравнение также не будет выполнено (поскольку «Oracle использует семантику сравнения без дополнения всякий раз, когда одно или оба значения в сравнении имеют тип данных VARCHAR2 или NVARCHAR2» ) и неявное преобразование во время вставки вызовет нарушение ограничения. В этом случае вы можете обрезать значение CHAR:

ON (
        audittable.event_datetime = stats.event_datetime 
    and audittable.COUNTRY = stats.COUNTRY
    and audittable.data_type = stats.data_type 
    and audittable.event_type = trim(stats.event_type)
    )

дб <> скрипка

...