Django + PostgreSQL 触发器导致 "violates foreign key constraint"

标签 django postgresql django-models triggers

在我的 Django... 架构中?我有一个产品模型结构、它们的价格和产品价格的历史(一种日志)。

有点像下面的(针对这个问题做了简化):

class ProductPricing(models.Model):
    price = models.DecimalField(max_digits=8, decimal_places=2,
                                help_text='price in dollars')
    product = models.OneToOneField('app.Product', related_name='pricing',
                                    null=False, on_delete=models.CASCADE)

class Product(models.Model):
    name = models.CharField(max_length=100)
    # .pricing --> related name from 'ProductPricing'

为了保留价格历史,我有一个 OldProductPricing 模型,它几乎是 ProductPricing 的副本,但允许多个 OldProductPricing(s) 每个产品:

class OldProductPricing(models.Model):
    valid_until = models.DateTimeField(null=False)
    product = models.ForeignKey('app.Product', related_name='+',
                                null=False, on_delete=models.CASCADE)

为确保每次修改或删除产品价格时,我都会尝试在我的 OldProductPricing 表中创建一个条目。

为了做到这一点,我在更新或删除时创建了一个 Posgresql 触发器:

CREATE TRIGGER price_change_trigger AFTER UPDATE OR DELETE ON app.productpricing
FOR EACH ROW EXECUTE PROCEDURE price_change_trigger_func();

创建插入的触发函数部分如下:

CREATE OR REPLACE FUNCTION price_change_trigger_func() RETURNS TRIGGER LANGUAGE plpgsql AS $$
    DECLARE
       retval RECORD;
    BEGIN
        IF (TG_OP = 'DELETE') THEN
            retval := OLD;
        ELSE
            retval := NEW;
        END IF;

        RAISE LOG 'Inserting in old_prices table copy of Pricing record ID=%% for product_id=%% because of %%', OLD.id, OLD.product_id, TG_OP;
        old_to_insert.product_id := OLD.product_id;
        old_to_insert.price := OLD.price;
        old_to_insert.valid_until := now() at time zone 'utc';
        old_to_insert.id := nextval('app_oldproductpricing_id_seq');

        IF EXISTS(SELECT 1 FROM app_product WHERE app_product.id=old_to_insert.product_id)) THEN
            INSERT INTO app_oldproductpricing VALUES(old_to_insert.*);
        END IF;
        RETURN retval;
    EXCEPTION WHEN others THEN
        -- Do nothing...
        RAISE WARNING 'Got error on %% %% %%', TG_OP, SQLERRM, SQLSTATE;
        RETURN NULL;
    END;

在大多数情况下,它工作正常,但是当 Product 被删除时,CASCADES 开始发生(从 Product --to--> ProductPricing), EXISTS(SELECT 1 FROM app_product WHERE app_product.id=old_to_insert.product_id)) 似乎无效:即使我检查了 Product 存在,我得到:

insert or update on table "app_oldproductpricing" violates foreign key constraint "app_oldproductpricing_product_id_bb078367_fk_app_product_id"
DETAIL:  Key (product_id)=(5) is not present in table "app_product".

认为发生的事情是触发器在要删除 Product --> ProductPricing 的事务中运行,并且 EXISTS 检查发生在 实际上删除Product?? (我不太确定,虽然......这只是一个怀疑)所以创建了一个新的 OldProductPricing 实例,但是之后 Product 这个 OldProductPricing 行引用已删除,使数据库处于不一致状态。

我的理论正确吗?最重要的是...有什么办法可以解决这个问题吗?

提前致谢。任何提示或建议将不胜感激。

PS:我使用的是 Django 1.11 和 Postgresql 9.5。

最佳答案

这个问题是由“变异表”引起的,当 DJANGO 模拟 ON_CASCADE 时,首先删除表“ProductPricing”上的行,然后在“OldProductPricing”上插入的行超过“Product”中的一行,该行将消失。您还有其他选择:

  1. 删除触发器并管理 Product.save() 和 ProductPricing.save() 中的逻辑,并将 on_delete=CASCADE 更改为 on_delete=DO_NOTHING。

  1. 日志表(“OldProductPricing”)应该永久保存数据,并显示从“Product”开始的所有历史变化,因此您应该删除从“OldProductPricing”到“Product”的外键。

关于Django + PostgreSQL 触发器导致 "violates foreign key constraint",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46960813/

相关文章:

Django:以微秒为单位的分辨率的DurationField

sql - 移动数据库后存储过程卡住

python - ManyToManyField 在使用自定义主键时引用 'id' 字段

Django - 从数据库中删除重复记录

html - Django 不提供来自静态文件夹的 CSS 代码

python - 你如何在 Django 中为表单编写保存方法?

sql - Node/Postgres SQL 选择不同的条目,然后将具有相同引用的所有其他条目放入一列中

sql - 左连接和 count() 缺少行所需的解释

python - 按字段分组的 Django 查询对象

django - 如何在我的 models.py 中引用 Django 设置变量?