Django + PostgreSQL触发器导致“违反外键约束”

时间:2017-10-26 17:53:54

标签: 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),{{1}似乎没有效果:即使我检查EXISTS(SELECT 1 FROM app_product WHERE app_product.id=old_to_insert.product_id))存在,我得到:

Product

认为正在发生的事情是触发器在一个事务中运行,其中Product - >要删除ProductPricing,并在实际删除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". 之前进行EXISTS检查。 (我不确定,但这只是一个怀疑)所以创建了Product的新实例,但之后OldProductPricing这个Product行引用了删除,使数据库处于不一致状态。

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

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

PS:我正在使用Django 1.11和Postgresql 9.5。

1 个答案:

答案 0 :(得分:1)

这个问题是由一个"变异表",当DJANGO模仿ON_CASCADE时,首先删除表格上的行" ProductPricing"插入" OldProductPricing"在"产品"哪个会消失。您还有其他选择:

  1. 删除触发器并管理Product.save()和ProductPricing.save()中的逻辑,并通过on_delete = DO_NOTHING更改on_delete = CASCADE。
    1. 日志表(" OldProductPricing")应该保留数据,并显示历史记录中的所有更改" Product",因此您应该从&删除外键#34; OldProductPricing"到"产品。