我有一张桌子“收据”。我有列customer_id(谁有收据)和receipt_number。对于每个客户,receipt_number应该从1开始并且是序列。这意味着customer_id和receipt_number将是唯一的。我怎样才能优雅地做到这一点。我可以使用CREATE SEQUENCE或类似的内置序列功能吗?似乎我必须为每个客户创建一个序列,这当然不是一个优雅的解决方案。
编辑:必须有线程安全和白痴安全的方法来做到这一点。这应该是一个非常简单/普遍的需求。
答案 0 :(得分:2)
SEQUENCE不保证没有差距。例如,一个事务可能会生成一个新数字然后中止(由于错误或电源故障或其他...)。然后,下一个交易将盲目地获得下一个数字,而不是那个“丢失”的数字。
如果您的客户申请不依赖于第一个地方的“无差距”假设,那将是最好的。但是,您可以最大限度地减少这样的差距:
SELECT MAX(receipt_number) FROM receipts WHERE customer_id = :ci
INSERT INTO receipts(customer_id, receipt_number) VALUES (:ci, aboveresult+1)
,如果 第1步 返回NULL,则只插入1。* 因为并发事务已经过同一个进程并已提交。
只要添加行而不删除行,即使在并发环境中也可以防止出现任何空白。
INSERT INTO receipts (customer_id, receipt_number)
SELECT :ci, COALESCE(MAX(receipt_number), 0) + 1
FROM receipts
WHERE customer_id = :ci;
PK {customer_id,receipt_number}下面的索引应确保有效满足此查询的SELECT部分。
答案 1 :(得分:1)
-- next CustomerReceiptNo
select coalesce(max(CustomerReceiptNo), 0) + 1
from Receipt
where CustomerId = specific_customer_id;
这不是线程安全的,因此如果两个单独的线程试图同时为给定客户创建新收据,请务必实施错误处理。
修改强>
线程安全不仅仅是避免竞争条件。假设有两个单独的线程同时为同一个客户创建新收据。它会发生吗?这是正常的,错误还是安全漏洞?假设一个银行,两个柜员同时为同一个客户创建一个新记录 - 这是非常错误的。如果发生这种情况,你可以使用锁;如果没有,那么就会出现某种错误。
答案 2 :(得分:1)
为什么每个客户的收据编号都以1开头?这是定义要求的一部分吗?
完成此任务的最简单方法是让生成新收据的程序在数据库中查询max(ReceiptNumber),其中CustomerId = CurrentCustomerId,然后添加1.
currentCustomerId是程序变量而不是数据库值。
这有点不雅,因为需要额外搜索表格。您需要仔细创建索引,以便在没有全表扫描的情况下让其中一个索引回答问题。
在插入时更快一点的替代方法是在customer表中创建一个名为MaxReeceiptNumber的额外列。当您想要插入新收据时增加。
答案 3 :(得分:1)
您可以使用这样的触发器来更新列:
对customer_id,receipt_number具有唯一约束的表定义:
CREATE TABLE receipts (id serial primary key, customer_id bigint, receipt_number bigint default 1);
CREATE UNIQUE INDEX receipts_idx ON receipts(customer_id, receipt_number);
检查客户端的最大receipt_number的功能,如果没有先前的收据,则检查为1
CREATE OR REPLACE FUNCTION get_receipt_number() RETURNS TRIGGER AS $receipts$
BEGIN
-- This lock will block other transactions from doing anything to table until
-- committed. This may not offer the best performance, but is threadsafe.
LOCK TABLE receipts IN ACCESS EXCLUSIVE MODE;
NEW.receipt_number = (SELECT CASE WHEN max(receipt_number) IS NULL THEN 1 ELSE max(receipt_number) + 1 END FROM receipts WHERE customer_id = new.customer_id);
RETURN NEW;
END;
$receipts$ LANGUAGE 'plpgsql';
触发在每行插入时触发函数:
CREATE TRIGGER rcpt_trigger
BEFORE INSERT ON receipts
FOR EACH ROW
EXECUTE PROCEDURE get_receipt_number();
然后,执行以下操作:
db=> insert into receipts (customer_id) VALUES (1);
INSERT 0 1
db=> insert into receipts (customer_id) VALUES (1);
INSERT 0 1
db=> insert into receipts (customer_id) VALUES (2);
INSERT 0 1
db=> insert into receipts (customer_id) VALUES (2);
INSERT 0 1
db=> insert into receipts (customer_id) VALUES (2);
应该产生:
id | customer_id | receipt_number
----+-------------+----------------
14 | 1 | 1
15 | 1 | 2
16 | 2 | 1
17 | 2 | 2
18 | 2 | 3
答案 4 :(得分:0)
我想为这个问题提出解决方案-使用customer表上的+1列存储last_receipt_id,并使用增量函数next_receipt_id(customer_id):
ALTER TABLE customers ADD COLUMN latest_receipt_id integer DEFAULT 1;
-- ensure customer_id, receipt_number pair uniqueness
CREATE UNIQUE INDEX customer_receipt_ids_pair_uniq_index ON receipts USING btree (customer_id, receipt_number);
-- sequence-like function for the next receipt id,
-- will increment it on every execution
CREATE FUNCTION next_receipt_id( for_customer_id integer ) RETURNS integer
LANGUAGE plpgsql AS
$$
DECLARE
result integer;
BEGIN
UPDATE customers SET latest_receipt_id = latest_receipt_id + 1 WHERE id = for_customer_id RETURNING latest_receipt_id INTO result;
RETURN result;
END;
$$;
然后,您可以在收据INSERT触发器中使用它:
-- somewhere inside trigger function, triggered on receipt INSERT
NEW.receipt_number := next_receipt_id( NEW.customer_id );
ORM(伪代码)中的OR:
# it does not matter when you assign the receipt_number,
# it could be even in standalone update execution, just do it only once!
receipt.update( 'receipt_number = next_receipt_id(customer_id)' )
不管插入的任何并发性,您将始终具有顺序ID。
干杯!