【问题标题】:Prevent consecutive duplicate values without a trigger在没有触发器的情况下防止连续重复值
【发布时间】:2020-09-03 13:01:48
【问题描述】:

在一个组中,我想防止INSERTs 连续重复值,其中“连续”由一个简单的ORDER BY 子句定义。

想象一组定期从传感器采样值的实验。我们只想插入一个新值对于该实验

请注意,旧值 允许重复。所以这是允许的:

id    experiment    value
 1             A       10
 2             A       20
 3             A       10

但这不是:

id    experiment    value
 1             A       10
 2             A       10

我知道如何找到每个实验的前一个值:

  SELECT
  *,
  lag(sample_value) OVER experiment_and_id
  FROM new_samples
  WINDOW experiment_and_id AS (
    PARTITION BY experiment
    ORDER BY id
  );

the docs我知道CHECK约束不允许在他们的检查中使用其他行:

PostgreSQL 不支持引用表数据的 CHECK 约束,而不是正在检查的新行或更新行。虽然违反此规则的 CHECK 约束在简单测试中可能会起作用,但它不能保证数据库不会达到约束条件为假的状态(由于所涉及的其他行的后续更改)。这将导致数据库转储和重新加载失败。即使完整的数据库状态与约束一致,重新加载也可能失败,因为未按满足约束的顺序加载行。如果可能,请使用 UNIQUE、EXCLUDE 或 FOREIGN KEY 约束来表示跨行和跨表限制。

如果您希望在插入行时对其他行进行一次性检查,而不是持续保持一致性保证,则可以使用自定义触发器来实现。 (这种方法避免了转储/重新加载问题,因为 pg_dump 在重新加载数据之前不会重新安装触发器,因此在转储/重新加载期间不会强制执行检查。)

EXCLUDE 约束看起来很有希望,但主要用于测试不相等的情况。而且我不确定我是否可以在其中包含窗口功能。

所以我留下了一个自定义触发器,但这似乎有点像一个相当常见的用例。

任何人都可以改进使用触发器吗?

理想情况下,我只想说:

INSERT ....
ON CONFLICT DO NOTHING

让 Postgres 处理剩下的事情!


最小工作示例

BEGIN;
  CREATE TABLE new_samples (
    id INT GENERATED ALWAYS AS IDENTITY,
    experiment VARCHAR,
    sample_value INT
  );

  INSERT INTO new_samples(experiment, sample_value)
  VALUES
  ('A', 1),
  -- This is fine because they are for different groups
  ('B', 1),
  -- This is fine because the value has changed
  ('A', 2),
  -- This is fine because it's different to the previous value in
  -- experiment A.
  ('A', 1),
  -- Two is not allowed here because it's the same as the value
  -- before it, within this experiment.
  ('A', 1);

  SELECT
  *,
  lag(sample_value) OVER experiment_and_id
  FROM new_samples
  WINDOW experiment_and_id AS (
    PARTITION BY experiment
    ORDER BY id
  );
ROLLBACK;

【问题讨论】:

    标签: sql postgresql sql-insert check-constraints postgresql-12


    【解决方案1】:

    如果示例不会更改,则文档中引用的限制将与您的用例无关。

    您可以创建一个函数来完成此操作:

    create or replace function check_new_sample(_experiment text, _sample_value int)
      returns boolean as 
    $$
      select _sample_value != first_value(sample_value) 
                                over (partition by experiment 
                                          order by id desc) 
        from new_samples
       where experiment = _experiment;
    $$ language sql;
    
    alter table new_samples add constraint new_samples_ck_repeat 
      check (check_new_sample(experiment, sample_value));
    

    示例插入:

    insert into new_samples (experiment, sample_value) values ('A', 1);
    INSERT 0 1
    
    insert into new_samples (experiment, sample_value) values ('B', 1);
    INSERT 0 1
    
    insert into new_samples (experiment, sample_value) values ('A', 2);
    INSERT 0 1
    
    insert into new_samples (experiment, sample_value) values ('A', 1);
    INSERT 0 1
    
    insert into new_samples (experiment, sample_value) values ('A', 1);
    ERROR:  new row for relation "new_samples" violates check constraint "new_samples_ck_repeat"
    DETAIL:  Failing row contains (5, A, 1).
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-29
      • 2010-10-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多