【问题标题】:Autoincrement attribute based on another attribute基于另一个属性的自动增量属性
【发布时间】:2015-07-11 20:41:07
【问题描述】:

我有一张桌子showtime 和一张桌子ticket

CREATE TABLE showtime (
    showtime_id     SERIAL PRIMARY KEY,
    theatre_id      INT REFERENCES theatre(theatre_id),
    showroom_id     INT REFERENCES showroom(showroom_id),
    movie_id        INT REFERENCES movie(movie_id),
    start_time      TIME,
    end_time        TIME,
    show_date       DATE
);

CREATE TABLE ticket (
    showtime_id     INT REFERENCES showtime(showtime_id),
    seat_number     SERIAL UNIQUE,
    price           NUMERIC(1000, 2) NOT NULL,
    time_bought     TIME,
    date_bought     DATE,
    wasUsed         BOOLEAN,
    PRIMARY KEY     (showtime_id, seat_number)
);

如您所见,showtime 中的每一行都有一个唯一的showtime_id。我想要发生的是当添加一个新的ticket 时,seat_number 应该从与showtime_id 绑定的最后一个值自动递增:

ticket

showtime_id     |   seat_number   |   instead_of   |
      1         |       1         |       1        |
      1         |       2         |       2        |
      1         |       3         |       3        |
      1         |       4         |       4        |
      2         |       1         |       5        |
      2         |       2         |       6        |
      3         |       1         |       7        |
      4         |       1         |       8        |

这在 PostgreSQL 中可行吗?

【问题讨论】:

  • 并非没有一些软锁定:每次插入票证时,您都会先锁定相应的showtime 行,并且可以安全地执行此操作。否则,您可以在运行时使用窗口函数来执行此操作,因为该数据很容易检索。

标签: postgresql auto-increment


【解决方案1】:

改写你的问题,这就是你想要的:每当你 INSERT ticket 和某个 showtime_id 时,你想将 seat_number 分配给比 ticket 中已经存在的 showtime_id 更高的数字。那么目前最高的seat_number 是多少?简单:

SELECT max(seat_number)
FROM ticket
WHERE showtime_id = ?; -- The ? being a parameter placeholder in languages like Java

seat_number 数据类型不应为 serial,因为您将拥有不同 showtimes_ids 的重复座位号。把它变成一个简单的integer

在单个INSERT 语句中:

INSERT INTO ticket VALUES
  (45,
   (SELECT max(seat_number) FROM ticket WHERE showtime_id = 45) + 1,
   ...
  );

要对您的用户或应用程序屏蔽此逻辑,您可以使用插入触发器来避免输入错误或非法(PK 违规)座位号:

CREATE FUNCTION assign_showtime_seat() RETURNS TRIGGER AS $$
BEGIN
  SELECT max(seat_number) + 1 INTO NEW.seat_number
  FROM ticket
  WHERE showtime_id = NEW.showtime_id;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER tr_assign_showtime_seat
  BEFORE INSERT ON ticket
  FOR EACH ROW EXECUTE PROCEDURE assign_showtime_seat();

您很有可能会遇到并发问题,即同一showtime_id 的两张票几乎同时输入。第一个插入进入触发器并计算一个新的seat_number。在INSERT 完成第二个工单插入之前进入触发器并计算相同的seat_number。然后,其中一个管理实际的INSERT 并完成,然后第二个票证条目在插入时因重复的PRIMARY KEY 违规而失败。该文档提供了从此类并发问题中恢复的an example

【讨论】:

    【解决方案2】:

    这是可能的,但对于并发访问来说要么非常不可靠,要么非常昂贵,因为您必须锁定整个表才能确定 - 锁定行是不够的,因为您无法锁定尚未输入的行。 (好吧,使用单独的showtime 表,引用表中的行锁会有所帮助,但仅此而已......请参阅@zerkms 的评论)

    如果可能的话,坚持使用您现在拥有的 serial 列,并使用 VIEWMATERIALIZED VIEW 得出您的数字:

    CREATE MATERIALIZED VIEW v_ticket AS
    SELECT *, row_number() OVER (PARTITION BY showtime_id
                                 ORDER BY seat_number) AS seat_number_effective
    FROM   ticket;
    

    这为您提供了每个showtime_id 没有间隔的升序数字。

    非常相似:

    【讨论】:

    • 我选择这个答案是因为它可以解决 zerkms 提出的问题。
    • "您不能锁定尚未输入的行" --- 但您只需要锁定一个必须已经存在的showtime 行。
    • @zerkms:好的,这应该适用于插入下一个seat_number。但在繁重的并发写入负载下,它仍然会很慢(可能会很慢)。然后您仍然没有解决 DELETE 或 UPDATE 引入的差距的解决方案。来自SEQUENCE 的数字可靠且快速,并且 VIEW 可以产生无间隙的结果。
    • @ErwinBrandstetter 确实(我什至提到窗口函数是一个更好的解决方案)
    猜你喜欢
    • 1970-01-01
    • 2016-07-21
    • 2014-04-19
    • 2020-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多