如何克服Netezza缺乏独特的约束/参照完整性执行?

时间:2011-04-13 12:35:50

标签: database data-integrity netezza

似乎缺乏对执行2个基本约束(唯一和外键)的支持,是失去许多工时调试和解决难题的原因。什么开始是一个简单,易于修复的问题(重复行/不一致的主要细节表)在我们的应用程序中甚至在我们的硬件中都没有被注意到,增长并引发复杂的边缘场景(例如,自我加入与重复会导致通货膨胀和储存耗尽。

  • Netezza在我们的环境中有多种用途:生产,研究,qa和分期。当然,我们的ETL过程不能成熟,也无法验证所有这些场景中的所有约束。
  • 即使在生产中使用的最成熟的应用程序中,在ETL加载数据时验证数据,我们也会创建一系列表,每个表都是其前任计算的结果。有时,数据完整性在此过程中被打破,而不是在开始时(由于错误的声明)

有人可以推荐一种避免这些令人头疼的方法/工具吗?

3 个答案:

答案 0 :(得分:5)

我知道有一个公认的答案,但我想提供一种替代方法。对我目前的职位不熟悉,我并不了解仓库中主要关键声明背后的所有业务决策。我开发了日志记录类型方法来跟踪重复行删除工作。以下是此设计的主要功能:

  • 始终保持最新,解决DDL / DML的流动性问题
    • 新/删除表格
    • 新/更新的主键
    • 新/已更新/已删除的行
  • 自我填充的历史
    • 跟踪时间的改善
    • 为各级趋势分析提供基础
  • 轻松查询目标表以供研究之用
    • 没有自我加入HAVING子句或查找所需的关键列
  • 目前仅处理主键
    • 可轻松扩展以解决唯一约束(在_V_RELATION_KEYDATA中CONTYPE ='u')

以下是Netezza视角所需的一切。需要注意的是,您需要填补空白以创建动态SQL。

首先,我创建了一个跟踪所有重复记录的数据库,表和内部rowid的表。

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应用程序中的行ID具有足够的自然分布,在基于野马的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};

循环遍历所有表以为登台表设定种子后,然后运行一系列查询,将数据库,所有者,表名和行ID视为缓慢变化的维度。此查询结束日期目标表中的记录,这些记录在登台表中不存在:

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行到大约2亿行事实@ 10Gb。对于Netezza来说,这是一个非常适度的支出。我们的Mustang NPS 10050整个过程不到10分钟。

答案 1 :(得分:2)

我们最初编写了一个存储过程来处理我们数据仓库中的这个问题 虽然它在理论上有效,但对于新西兰常见的大型桌子来说有点慢(500M记录表约10分钟)。

我将在下面解释存储过程,但是现在我想说我们现在甚至没有利用proc,因为我们的增量/ upsert加载明确只是插入目标中尚不存在的记录数据库。 (对于upsert,我们基本上只是删除插入前插入的记录集中存在的任何记录)。

这有它自己的问题,特别是因为新西兰不喜欢删除并且需要让表格不断地修复以实际回收空间,更不用说我们的upsert可能会丢失历史列数据,删除旧的记录自从被改变之后(我们还有其他处理加载我们想要跟踪的缓慢变化的维度的过程。)

无论如何,约束存储过程看起来像:

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...;

如果它们不同,我们会提出异常。

答案 2 :(得分:1)

为什么不添加一列作为要应用约束的列的哈希值? NZ SQL扩展包具有散列函数。如果你使用hash8('key'),其中'key'是你的约束列值的连接(强制转换为varchar),那么你得到一个可能唯一的BIGINT值。我说“可能”因为哈希冲突的概率是有限的,所以它很低。如果您需要更强大的唯一性,可以使用加密级别的'hash()'函数。

除非你真的有大量的数据,否则hash8应该是更好的选择。由于它给出了整数值,因此行的比较是简单的算术问题,因此速度很快。因此,例如,您可以根据哈希值更新或插入行,这只是一个整数比较。