2016-10-05 3 views
0

Я использую базу данных postgres, и моя проблема включает в себя две таблицы, упрощенные версии которых приведены ниже.PostgreSQL INSERT с операторами

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"; 

ли possibile добиться этого с помощью одного INSERT - команды?

ответ

2

Вы можете сделать это так:

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

и написать статью WHERE, что только истинно, если ваше условие выполнено.

Но есть фундаментальная проблема с этим подходом, а именно, что оно подлежит условию гонки.

Если два таких оператора выполняются одновременно, оба могут найти условие удовлетворенным, но когда каждый добавит свою строку и совершит ошибку, условие может быть нарушено. Это потому, что ни одно из утверждений не может увидеть эффекты другого, прежде чем они совершают.

Есть два решения для этого:

  • Блокировка таблицы перед проверкой и вставить. Это просто, но очень плохо для параллелизма.

  • Использование SERIALIZABLE сделок по всему миру. Затем это должно привести к ошибке сериализации, и одно из утверждений должно быть повторено и будет обнаруживать, что условие нарушено, когда оно происходит.

+0

Здравствуйте, извините за поздний ответ. В настоящее время я решил свою проблему, выполнив запрос, который получает разницу между max_persons и number_booked/number_reserved и выполняет только оператор insert, если он больше 0. Является ли это жизнеспособной практикой? И еще один вопрос, если я использую транзакции SERIALIZABLE, я в порядке с проблемами, например, когда два человека пытаются зарегистрироваться для одного и того же события одновременно, но остается только одно место? – PrototypeX7

+0

Ваша текущая техника уязвима для проблемы, которую я указал в своем ответе --- если две такие транзакции выполняются параллельно, и только одно место бесплатное, они оба добавят новый запрос. С SERIALIZABLE это не могло случиться. Одна из транзакций будет прекращена с ошибкой сериализации и должна быть повторена. Во время повторной попытки было показано, что мероприятие уже забронировано. –

+0

Спасибо, собираюсь попробовать это! – PrototypeX7

Смежные вопросы