将记录插入 PostgreSQL table 并使旧记录过期

Insert record into PostgreSQL table and expire old record

我有一组数据由客户定期更新。大约一个月一次,我们将下载一组新的此类数据。数据集大约有 50k 条记录和几百列数据。

我正在尝试创建一个包含所有这些数据的数据库,以便我们可以 运行 我们自己的分析。我正在使用 PostgreSQL 和 Python (psycopg2).
有时,客户端会向数据集添加列,因此我想采取一些步骤:

  1. 向数据库添加新记录table
  2. 将旧数据集与新数据集进行比较,并在必要时更新table
  3. 保留旧记录,并添加“过期”标志或“db_expire_date”以跟踪记录是有效还是过期
  4. 为所有记录向数据库添加任何新的数据列

我知道如何使用 INSERT INTO 将新记录添加到数据库 (1),以及如何使用 ALTER TABLE 将新数据列添加到数据库 (4)。但是 (2) 和 (3) 有问题。我想出了如何使用以下代码更新记录:

rows = zip(*[update_records[col] for col in update_records])
cursor = conn.cursor()
cursor.execute("""CREATE TEMP TABLE temptable (""" + schema_list + """) ON COMMIT DROP""")
cursor.executemany("""INSERT INTO temptable (""" + var +""") VALUES ("""+ perc_s + """)""", rows)
cursor.execute("""
    UPDATE tracking.test_table
    SET mfg = temptable.mfg, db_updt_dt = CURRENT_TIMESTAMP
    FROM temptable
    WHERE temptable.app_id = tracking.test_table.app_id;
    """);
cursor.rowcount
conn.commit()
cursor.close()
conn.close()

然而,这只是更新了基于app_id作为主键的记录。
我想弄清楚的是如何保留原始记录并将其设置为“已过期”,然后创建一个新的更新记录。似乎“app_id”不应该是我的主键,所以我创建了一个新的主键作为“primary_key”INT GENERATED ALWAYS AS IDENTITY not null,'.

我只是不确定从这里到哪里去。我认为我可能只使用 INSERT INTO 将新记录发送到数据库。但我不确定如何以这种方式“过期”旧记录。可能我可以使用 UPDATE table 将旧值设置为“已过期”。但我想知道是否有更直接的方法来做到这一点。

我希望我的问题很清楚。我希望有人能指出我正确的方向。谢谢

对于第 2 步) 首先,您必须识别具有相同数据的记录,为此您可以在插入任何重新编码之前使用 where 子句 运行 select 查询并计算您收到的记录数作为输出。如果计数大于 0,请不要插入重新编码,否则您可以插入重新编码。

对于第 3 步) 为此,您可以插入上面提到的名称为 'db_expire_date' 的列,并仅在记录插入时插入过期值。

您也可以使用像 'is_expire' 这样的列,但是为此,您需要添加一个 cron 作业,该作业可以定期为该列的值更新数据库。

您正在寻找“自然键”的概念,这是您识别唯一行的方式,而不管 table 上的显式逻辑约束是什么。

这意味着您发现需要更改主键以使其更具包容性。你的新主键实际上并不能帮助你破译你正在寻找哪一行,除非你已经知道你正在寻找哪一行(那个“身份”字段)。

我可以想到两个可能的候选对象来添加到您的自然键中:日期或批次。

无论哪种方式,您都可以在数据中查找“App = X,[Date|batch] = Y”来找到那个。批次将是上传 1、上传 2 等。您只是编造它,或者从日期派生它,或者类似的东西。

如果您不确定要添加哪个,并且您永远不会在一天内多次上传,我会选择 Date。随着时间的推移,这将使您获得更多可见性,因为您可以看到事情发生变化的时间和频率。

有了自然键后,您希望在数据中明确显示它。您可以保留您的标识列(请参阅:代理键),也可以使用复合主键。在没有其他输入或限制的情况下,我会根据您的情况使用复合主键。

我是一名 MySQL DBA,所以我抄袭了这里的文档:https://www.postgresqltutorial.com/postgresql-primary-key/

你不想要这个:

CREATE TABLE test_table (
    app_id INTEGER PRIMARY KEY,
    date DATE,
    active BOOLEAN
);

相反,你想要这样:

CREATE TABLE test_table (
    app_id INTEGER,
    date DATE,
    active BOOLEAN,
    PRIMARY KEY (app_id, date)
);

我在这里也添加了一个 active 列,因为您想要停用行。从您所描述的内容来看,这并不是明确必要的 - 您始终可以假设最近的上传处于活动状态。或者您可以扩展列以具有“active_start”日期和“active_end”日期,这将启用另一组查询。但是对于您到目前为止在此处陈述的内容,仅 date 列就足够了。 :)

一个非常标准的数据仓库技术是定义两个额外的日期字段,一个起始生效日期和一个截止生效日期。您只追加行,从不更新。如果 table 中不存在源主键,或者如果 any 列值与 [=43= 中最近添加的先前记录不同,则添加候选记录] 具有相同的主键。 (每条记录取代最后一条)。

当您将记录添加到 table 时,您会做 3 件事:

  1. 新记录的生效日期获取交易文件的日期
  2. 新记录的生效日期是未来的日期,例如 9999-12-31。这里重要的是,除非你这么说,否则它不会过期。
  3. 最近的先前记录(您比较更改值的记录)的生效日期已更新为交易文件的日期减去一天。这具有使旧记录过期的效果。

这将创建一个具有相同源主键的记录链,每个记录都覆盖一个不重叠的时间段。这种格式非常容易 select from:

  • 如果您想复制最新的交易文件,您 select Where to-effective-date > Current Date
  • 如果您想在报告的任何日期复制交易文件,您 select 其中 myreportdate 在 from-effective-date 和 to-effective-date 之间。
  • 如果你想要一个键的整个更新历史,你 select * 其中键 = mykeyvalue Order By from-effective-date.

此方案唯一的缺点是添加列时,还必须更改比较测试以包含这些新列,以防发生变化。如果你希望它是动态的,你将不得不循环遍历 table 中每一列的反射元数据,但是 Python 将需要知道比较文本字段可能有何不同例如,通过比较 BLOB。

如果您真的关心拥有一个主键(许多数据仓库没有主键),您可以在源主键 + 这些有效日期之一上定义一个复合键,哪一个并不重要.