2016-10-05 230 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