Как преодолеть отсутствие у Netezza уникального принуждения / соблюдения ссылочной целостности? - PullRequest
2 голосов
/ 13 апреля 2011

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

  • Netezza выполняет несколько задач в нашей среде: производство, исследования, контроль качества и постановка. Естественно, наши процессы ETL не могут быть зрелыми и не могут проверить все ограничения во всех этих сценариях.
  • Даже в самых зрелых наших приложениях, используемых в производстве, где данные проверяются во время загрузки ETL, мы создаем серию таблиц, каждая из которых является результатом вычислений по его предшественникам. Иногда целостность данных нарушается по пути, а не сразу (в результате ошибочного оператора)

Кто-нибудь может порекомендовать методологию / инструмент, позволяющий избежать этих головных болей?

Ответы [ 3 ]

5 голосов
/ 28 ноября 2012

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

  • Всегда в курсе, учитывая текучую природу DDL / DML
    • Новые / удаленные таблицы
    • Новые / обновленныепервичные ключи
    • Новые / обновленные / удаленные строки
  • Самонасыщенная история
    • Отслеживание улучшений со временем
    • Обеспечивает основу для трендованализ на всех уровнях
  • Простой запрос таблиц назначения для исследовательских целей
    • Самостоятельное объединение с предложением HAVING или поиск ключевых столбцов не требуется
  • Адреса только первичных ключей в это время
    • Может быть легко расширен до адреса уникальных ограничений (CONTYPE = 'u' в _V_RELATION_KEYDATA)

Что следуетэто все, что требуется с точки зрения Netezza.Где отмечено, вам нужно будет заполнить пробелы для создания динамического SQL.

Сначала я создал таблицу, которая отслеживает базу данных, таблицу и внутренний идентификатор строки всех дублированных записей.

CREATE TABLE
    NZ_DUPLICATE_PKS
    (
        DATABASE_NAME CHARACTER VARYING(128) NOT NULL
        ,TABLE_OWNER CHARACTER VARYING(128) NOT NULL
        ,TABLE_NAME CHARACTER VARYING(128) NOT NULL
        ,ROW_ID BIGINT NOT NULL
        ,CURRENT_RECORD_INDICATOR CHARACTER(1) NOT NULL
        ,CREATE_TIMESTAMP TIMESTAMP NOT NULL
        ,LAST_UPDATE_TIMESTAMP TIMESTAMP NOT NULL
    )
DISTRIBUTE ON
    (
        ROW_ID
    );

ПРИМЕЧАНИЕ : YMMV для ключа распределения и объема строк, входящих в таблицу.Идентификаторы строк в нашем приложении Netezza имели достаточно естественное распределение, что хорошо мне помогало на базе Mustang NPS 10050.

Затем была создана промежуточная версия этой таблицы:

CREATE TABLE
    STG_NZ_DUPLICATE_PKS
    (
        DATABASE_NAME CHARACTER VARYING(128)
        ,TABLE_OWNER CHARACTER VARYING(128)
        ,TABLE_NAME CHARACTER VARYING(128)
        ,ROW_ID BIGINT
        ,CURRENT_RECORD_INDICATOR CHARACTER(1)
        ,CREATE_TIMESTAMP TIMESTAMP
        ,LAST_UPDATE_TIMESTAMP TIMESTAMP
    )
DISTRIBUTE ON
    (
        ROW_ID
    );

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

SELECT
    DATABASE
    ,OWNER
    ,RELATION
    ,CONSTRAINTNAME
    ,ATTNAME
FROM
    {YOUR_DATABASE_NAME}._V_RELATION_KEYDATA
WHERE
    CONTYPE = 'p'
    -- Exclude the duplicate tracking table
    AND RELATION != 'NZ_DUPLICATE_PKS'
ORDER BY
    DATABASE
    ,OWNER
    ,RELATION
    ,CONSTRAINTNAME
    ,CONSEQ
;

Теперь я перебираю базовый запрос для динамического создания запросов на вставку.Мой магазин использует DataStage, чей подход эзотеричен, и здесь его не стоит подробно объяснять.

ПРИМЕЧАНИЕ : Здесь требуется небольшая работа для создания цикла и построения динамического SQL.Можно использовать множество разновидностей shell, Perl, Python и т. Д. Используя пример таблицы с ключом из двух столбцов, вот что нужно построить для вставки в промежуточную таблицу:

INSERT
INTO
    STG_NZ_DUPLICATE_PKS
    (
        DATABASE_NAME
        ,TABLE_OWNER
        ,TABLE_NAME
        ,ROW_ID
        ,CURRENT_RECORD_INDICATOR
        ,CREATE_TIMESTAMP
        ,LAST_UPDATE_TIMESTAMP
    )
SELECT
    '{YOUR_DATABASE_NAME}' DATABASE_NAME
    ,'{YOUR_TABLE_OWNER}' TABLE_OWNER
    ,'{YOUR_TABLE_NAME}' TABLE_NAME
    ,DUPS.ROWID ROW_ID
    ,'Y' CURRENT_RECORD_INDICATOR
    ,CURRENT_TIMESTAMP CREATE_TIMESTAMP
    ,CURRENT_TIMESTAMP LAST_UPDATE_TIMESTAMP
FROM
    {YOUR_TABLE_NAME} DUPS
    INNER JOIN
        (
            SELECT
                {KEY_COLUMN_1}
                ,{KEY_COLUMN_2}
            FROM
                {YOUR_TABLE_NAME}
            GROUP BY
                {KEY_COLUMN_1}
                ,{KEY_COLUMN_2}
            HAVING
                COUNT(*) > 1
        )
        KEYS
        ON
            DUPS.{KEY_COLUMN_1} = KEYS.{KEY_COLUMN_1}
            AND DUPS.{KEY_COLUMN_2} = KEYS.{KEY_COLUMN_2};

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

UPDATE
    NZ_DUPLICATE_PKS
SET
    CURRENT_RECORD_INDICATOR = 'N'
    ,LAST_UPDATE_TIMESTAMP = CURRENT_TIMESTAMP
WHERE
    CURRENT_RECORD_INDICATOR = 'Y'
    AND
    (
        DATABASE_NAME
        ,TABLE_OWNER
        ,TABLE_NAME
        ,ROW_ID
    )
    NOT IN
    (
        SELECT
            DATABASE_NAME
            ,TABLE_OWNER
            ,TABLE_NAME
            ,ROW_ID
        FROM
            STG_NZ_DUPLICATE_PKS
    )
;

Наконец, вставьте последние записи в целевую таблицу:

INSERT
INTO
    NZ_DUPLICATE_PKS
    (
        DATABASE_NAME
        ,TABLE_OWNER
        ,TABLE_NAME
        ,ROW_ID
        ,CURRENT_RECORD_INDICATOR
        ,CREATE_TIMESTAMP
        ,LAST_UPDATE_TIMESTAMP
    )
SELECT
    DATABASE_NAME
    ,TABLE_OWNER
    ,TABLE_NAME
    ,ROW_ID
    ,CURRENT_RECORD_INDICATOR
    ,CREATE_TIMESTAMP
    ,LAST_UPDATE_TIMESTAMP
FROM
    STG_NZ_DUPLICATE_PKS
WHERE
    (
        DATABASE_NAME
        ,TABLE_OWNER
        ,TABLE_NAME
        ,ROW_ID
    )
    NOT IN
    (
        SELECT
            DATABASE_NAME
            ,TABLE_OWNER
            ,TABLE_NAME
            ,ROW_ID
        FROM
            NZ_DUPLICATE_PKS
        WHERE
            CURRENT_RECORD_INDICATOR = 'Y'
    )
;

ПРИМЕЧАНИЕ: Наша среда не такова, что необходима модель только для вставки.Ветераны Netezza будут знакомы с этой мыслью.Если ваша среда только для вставки, измените стратегию соответствующим образом.

Как только все будет готово, очень просто найти повторяющиеся строки для исследования:

SELECT
    *
FROM
    {YOUR_TABLE_NAME}
WHERE
    ROWID IN
    (
        SELECT
            ROW_ID
        FROM
            NZ_DUPLICATE_PKS
        WHERE
            CURRENT_RECORD_INDICATOR = 'Y'
            AND DATABASE_NAME = '{YOUR_DATABASE_NAME}'
            AND TABLE_OWNER = '{YOUR_OWNER_NAME}'
            AND TABLE_NAME = '{YOUR_TABLE_NAME}'
    );

Мне это нравится, потому что это просто иТо же самое для всех таблиц, независимо от различий в объявлении первичного ключа.

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

SELECT
    DATABASE_NAME
    ,TABLE_OWNER
    ,TABLE_NAME
    ,COUNT(*) QUANTITY
FROM
    NZ_DUPLICATE_PKS
WHERE
    CURRENT_RECORD_INDICATOR = 'Y'
GROUP BY
    1
    ,2
    ,3
ORDER BY
    1
    ,2
    ,3;

Это суммирует все.Я надеюсь, что некоторые люди находят это полезным.Мы уже добились большого прогресса в этом подходе.На данный момент, вы можете быть удивлены, почему я пошел на все эти неприятности.Я терпеть не могу, что нарушения PK допускаются на наш склад, и я хотел комплексный подход к их устранению.Вышеуказанный процесс выполнялся ежедневно в нашей производственной среде в течение пары месяцев.У нас есть ~ 350 таблиц с объявленными первичными ключами, размером от 5 измерений строк до ~ 200 миллионов фактов строк при 10 ГБ.Для Netezza это довольно скромные затраты.Весь процесс занимает меньше 10 минут на нашем Mustang NPS 10050.

2 голосов
/ 23 апреля 2011

Мы изначально написали хранимую процедуру для обработки этой самой вещи в нашем хранилище данных.
Хотя он работает теоретически, он довольно медленный для огромных таблиц, обычно встречающихся в NZ (~ 10 минут для таблицы записей 500M).

Я объясню хранимый процесс ниже, но сейчас я хотел бы сказать, что мы даже не используем процесс в данный момент, потому что наши инкрементные / upsert нагрузки явно только вставляют записи, которые еще не существуют в цели база данных. (Для upsert, мы в основном просто удаляем любые записи, которые существуют в нашем наборе записей, чтобы вставить перед вставкой).

У этого есть свои проблемы, в частности, потому что NZ не любит удаления и ему нужно постоянно готовить таблицы, чтобы фактически освободить пространство, не говоря уже о том, что наши upserts могут потерять исторические данные столбца, удалив старые записи, которые имели с тех пор как были изменены (у нас есть другие процессы для загрузки медленно меняющихся измерений, которые мы хотим отслеживать.)

В любом случае, хранимое ограничение proc выглядит так:

check_constraints('table','constraint_type') returns boolean

и в основном циклы:

  select constraintname, contype, attname
    from _v_relation_keydata
   where relation = upper(p_table) order by constraintname, conseq;

Чтобы получить столбцы, которые нужно сравнить. Для каждого ограничения он запускает что-то вроде:

  select count(*) 
    from ( 
    select distinct col1,col2,...
      from p_table 
     where col1 is not null 
       and col2 is not null... );

и сравните это число с

  select count(*) 
    from p_table 
   where col1 is not null 
     and col2 is not null...;

Если они разные, мы выдвигаем исключение.

1 голос
/ 07 марта 2014

Почему бы не добавить столбец, который является хеш-значением столбцов, к которым вы хотите применить ограничение? Пакет расширений NZ SQL имеет функции хеширования. Если вы используете hash8 ('key'), где 'key' - это конкатенация значений вашего ограниченного столбца (приведенных к varchar), то вы получите, вероятно, уникальное значение BIGINT. Я говорю «вероятно», потому что вероятность коллизии хэша конечна, хотя и будет низкой. Если вам нужна более сильная гарантия уникальности, вы можете использовать вместо этого функцию криптографического уровня 'hash ()'.

Однако, если у вас нет действительно огромного количества данных, лучшим выбором будет hash8. Так как он дает целочисленные значения, сравнение строк является вопросом простой арифметики и, следовательно, является быстрым. Так, например, вы можете просто обновить или вставить строки на основе значения хеша, которое представляет собой целочисленное сравнение.

...