2016-10-05 182 views
0

我正在使用postgres数据库,我的问题包括两个表格,它们的简化版本如下。PostgreSQL插入语句

CREATE TABLE events(
    id SERIAL PRIMARY KEY NOT NULL, 
    max_persons INTEGER NOT NULL 
); 

CREATE TABLE requests(
    id SERIAL PRIMARY KEY NOT NULL, 
    confirmed BOOLEAN NOT NULL, 
    creation_time TIMESTAMP DEFAULT NOW(), 
    event_id INTEGER NOT NULL /*foreign key*/ 
); 

n事件和每个事件最多可以有events.max_persons参与者。新的请求需要确认并且有效期最长为30分钟。在这段时间之后,如果没有确认,请求将被忽略。

现在什么,我想要做的只插入一个新的request,当所有的确认请求,以及目前仍然有效的所有请求,但没有证实的总和,小于events.max_persons

我已经有查询来选择单个事件。这里是它的一个简化版本,只是给你一个想法,它应该如何工作

SELECT 
    e.id, 
    SUM(CASE WHEN r.confirmed = 1 THEN 1 ELSE 0 END) AS number_confirmed 
    SUM(CASE WHEN r.creation_time > (CURRENT_TIMESTAMP - INTERVAL '30 MINUTE') AND r.confirmed = 0 THEN 1 ELSE 0 END) AS number_reserved, 
    e.max_persons 
FROM events e, requests r 
WHERE l.id = ? 
    AND r.event_id = e.id    
    AND (r.confirmed = 1 OR r.creation_time > (CURRENT_TIMESTAMP - INTERVAL '30 MINUTE')) 
GROUP BY e.id, e.max_persons    
HAVING SUM(CASE WHEN r.confirmed = 1 OR (r.creation_time > (CURRENT_TIMESTAMP - INTERVAL '30 MINUTE')) THEN 1 ELSE 0 END) < e.max_persons"; 

难道不可能性与一个单一的INSERT实现这一目标 - 命令?

回答

2

你可以是这样做的:

INSERT INTO requests 
    SELECT * FROM (VALUES (...)) row 
     WHERE ... 

,写一个WHERE条款是,如果你的条件满足唯一的真。

但是这种方法存在一个基本问题,即它受到竞争条件的制约。

如果两个这样的语句同时运行,两者都可以找到满足的条件,但是当每个语句添加了它的行并提交时,条件可能被违反。这是因为没有任何陈述能够在他们提交之前看到其他人的影响。

这有两种解决方案:

  • 锁定表,你测试和插入之前。这很简单,但并发性很差。

  • 整个使用SERIALIZABLE交易。然后,这应该会导致序列化错误,并且其中一个语句必须重试,并且在它发生时会发现违反的条件。

+0

你好,对于最近的答案抱歉。目前我通过运行查询来解决我的问题,该查询得到max_persons和number_booked/number_reserved之间的差异,并只执行插入语句,如果它大于0。这是一种可行的做法吗?还有一个问题,如果我使用SERIALIZABLE交易,我可以解决两个人同时报名参加同一个活动的问题,但只剩下一个地方吗? – PrototypeX7

+0

您目前的技术很容易受到我在答复中指出的问题的影响 - 如果两个此类事务并行运行,并且只有一个地方是免费的,则它们都会添加新的请求。 SERIALIZABLE无法实现。其中一个事务会因序列化错误而终止,并且必须重试。在重试期间,它会显示该事件已经预订完毕。 –

+0

谢谢,试试看! – PrototypeX7