DISTINCT
очень часто является индикатором плохо написанного запроса. Нормализованная база данных не содержит дубликатов данных, так откуда же вдруг возникли дубликаты, которые вы должны удалить с помощью DISTINCT
? Очень часто это ваш собственный запрос, производящий их. Во-первых, не создавайте дубликаты, так что вам не нужно DISTINCT
позже.
В вашем случае вы присоединяетесь к таблице notification
в своем подзапросе a
, но не используете его строки в этом подзапросе; вы выбираете только из notification_master_id
.
В конце концов, вы хотите получить мастера уведомлений, получить их последнее связанное уведомление (сначала получая его идентификатор, а затем выберите строку). Для этого вам не нужны сотни подзапросов.
Некоторые примечания:
- Чтобы получить описание от
template_classification
, вы снова присоединяетесь к таблице уведомлений, которая не требуется. ORDER BY
в подзапросе (ORDER BY nm.id DESC
) является излишним, поскольку результаты подзапроса по стандарту SQL не отсортированы. (Oracle иногда нарушает этот стандарт, чтобы применить ROWNUM
к результату, но вы не используете ROWNUM
в своем запросе.) - Жаль, что вы храните
created_at
не как DATE
или TIMESTAMP
, но как число. Это заставляет вас рассчитывать. Я не думаю, что это сильно повлияет на ваш запрос, потому что вы используете его в состоянии OR
. CURRENT_DATE
дает вам дату клиента. Это редко требуется, так как вы выбираете данные из базы данных, которая, конечно, должна относиться не к какой-то дате клиента, а к собственной дате SYSDATE
.
Если я не ошибаюсь, ваш запрос можно сократить до:
SELECT
nm.id AS masterid,
nf.id AS notification_id,
nfagg.notification_list AS notification_list,
nm.notification_type_id AS typeid,
nf.subject AS subject,
nf.approver AS approver,
nf.created_at AS created_at,
nf.created_by AS created_by,
nf.sequence_no AS sequence_no,
nm.product_id AS productid,
nm.notification_status_id AS statusid,
nf.updated_by AS updated_by,
nf.updated_at AS updated_at,
(
SELECT LISTAGG(p.name, ',') WITHIN GROUP (ORDER BY p.id)
FROM product p
INNER JOIN notification_product np ON np.product_id = p.id
WHERE np.notification_id = nf.id
) AS product_list,
(
SELECT description
FROM notification_status
WHERE id = nm.notification_status_id
) AS notification_status,
(
SELECT name
FROM template
WHERE id = nm.template_id
) AS template,
(
SELECT description
FROM notification_type
WHERE id = nm.notification_type_id
) AS notification_type,
(
SELECT description
FROM template_classification
WHERE id = nf.classification_id
) AS classification
FROM notification_master nm
INNER JOIN
(
SELECT
notification_master_id,
MAX(id) AS maxid,
LISTAGG(id,',') WITHIN GROUP (ORDER BY id) AS notification_list
FROM notification
GROUP BY notification_master_id
) nfagg ON nfagg.notification_master_id = nm.id
INNER JOIN notification nf
ON nf.id = nfagg.maxid
AND
(
(
DATE '1970-01-01' + NUMTODSINTERVAL(nf.created_at / 1000, 'SECOND')
< CURRENT_DATE + INTERVAL '-21' DAY
)
OR (nm.notification_type_id IN (2,4) AND nm.notification_status_id = 4)
)
WHERE nm.disable = 'N'
ORDER BY nm.id DESC
OFFSET 10 ROWS
FETCH NEXT 10 ROWS ONLY;
Как уже упоминалось, вы можете заменить CURRENT_DATE
на SYSDATE
.
Я рекомендую следующие индексы для запроса:
CREATE INDEX idx1 ON notification_master (disable, id, notification_status_id, notification_type_id);
CREATE INDEX idx2 ON notification (notification_master_id, id, created_at);
Последнее замечание о разбивке по страницам: чтобы пропустить n строк, чтобы получить следующие n, весь запрос должен быть выполнен для всех данных, а затем все результирующие строки должны быть отсортированы только для того, чтобы выбрать n из них, наконец. Обычно лучше запомнить последний извлеченный идентификатор, а затем выбирать только строки с более высоким идентификатором при следующем выполнении.