Во-первых, если ссылочная целостность обеспечивается с помощью ограничений FK, вы можете полностью удалить таблицу patient
из запроса:
SELECT COUNT(DISTINCT patient) -- still not optimal
FROM event a
JOIN event o USING (patient_id)
JOIN event m USING (patient_id)
WHERE a.category = 'admission'
AND o.category = 'operation'
AND m.category = 'medication'
AND m.date > o.date
AND o.date > a.date;
Затем избавьтесь от повторного умножения строк и * 1005.* чтобы противостоять этому во внешнем SELECT
с помощью EXISTS
полусоединений вместо:
SELECT COUNT(*)
FROM event a
WHERE EXISTS (
SELECT FROM event o
WHERE o.patient_id = a.patient_id
AND o.category = 'operation'
AND o.date > a.date
AND EXISTS (
SELECT FROM event m
WHERE m.patient_id = a.patient_id
AND m.category = 'medication'
AND m.date > o.date
)
)
AND a.category = 'admission';
Обратите внимание, что все еще может быть дубликаты в допуске , но это, вероятно, главная проблема в вашей модели данных / дизайне запроса, и потребуется пояснение, как описано в комментариях.
Если вы действительно хотите объединить все случаивместе с одним и тем же пациентом по какой-то причине существуют различные способы получить самую раннюю госпитализацию для каждого пациента на начальном этапе - и повторять аналогичный подход для каждого дополнительного этапа.Вероятно, самый быстрый для вашего случая (повторное представление таблицы пациентов в запросе):
SELECT count(*)
FROM patient p
CROSS JOIN LATERAL ( -- get earliest admission
SELECT e.date
FROM event e
WHERE e.patient_id = p.id
AND e.category = 'admission'
ORDER BY e.date
LIMIT 1
) a
CROSS JOIN LATERAL ( -- get earliest operation after that
SELECT e.date
FROM event e
WHERE e.patient_id = p.id
AND e.category = 'operation'
AND e.date > a.date
ORDER BY e.date
LIMIT 1
) o
WHERE EXISTS ( -- the *last* step can still be a plain EXISTS
SELECT FROM event m
WHERE m.patient_id = p.id
AND m.category = 'medication'
AND m.date > o.date
);
См .:
Вы можете оптимизировать дизайн таблицы, сократив длинные (и избыточные) имена категорий.Используйте справочную таблицу и сохраняйте только значение integer
(или даже int2
или "char"
как FK.)
Для лучшей производительности (и это крайне важно) используйте многоколонный индекс на (parent_id, category, date DESC)
и убедитесь, что все три столбца определены NOT NULL
.Порядок выражений индекса важен.DESC
здесь в основном необязателен.Postgres может использовать индекс с порядком сортировки по умолчанию ASC
почти так же эффективно, как в вашем случае.
Если VACUUM
(предпочтительно в форме автоочистки) может идти в ногу с операциями записи или у вас есть только для чтенияДля начала вы получите очень быстрое сканирование только по индексу из этого.
Связанный:
Для реализации дополнительных временных рамок (ваш «расширенный вариант использования» ), основываться на втором запросе, так как мы должны снова рассмотреть все событий.
У вас действительно должны быть идентификаторы случаев или что-то более определенное, чтобы связать операцию с поступлением и лечениемоперация и т. д., где это уместно.(Это может быть просто id
указанного события!) Одни даты / метки времени подвержены ошибкам.
SELECT COUNT(*) -- to count cases
-- COUNT(DISTINCT patient_id) -- to count patients
FROM event a
WHERE EXISTS (
SELECT FROM event o
WHERE o.patient_id = a.patient_id
AND o.category = 'operation'
AND o.date >= a.date -- or ">"
AND o.date < a.date + 7 -- based on data type "date"!
AND EXISTS (
SELECT FROM event m
WHERE m.patient_id = a.patient_id
AND m.category = 'medication'
AND m.date >= o.date -- or ">"
AND m.date < o.date + 30 -- syntax for timestamp is different
)
)
AND a.category = 'admission';
О date
/ timestamp
арифметика: