Выполнить второй SQL-запрос, если первый возвращает меньше строк, чем ограничение

У меня есть два таких SQL-запроса для postgresql:

Первый запрос — это выборка подписок, которые никогда не синхронизировались или были недавно обновлены и синхронизированы давным-давно:

SELECT * 
FROM subscriptions
WHERE (last_sync_at IS NULL OR last_sync_at < updated_at) AND updated_at < $1
ORDER BY last_sync_at NULLS FIRST
LIMIT $2`,

Второй запрос — получение подписок, но первой синхронизируется та, которая была синхронизирована раньше всего.

SELECT *
FROM subscriptions
ORDER BY last_sync_at NULLS FIRST
LIMIT $1`,

Я хочу объединить эти два запроса следующим образом: Если первый запрос возвращает меньше результатов, чем лимит, я хочу выполнить второй запрос, чтобы заполнить пакет.

Например, если предел равен 100 и первый запрос возвращает 90, я хочу выполнить второй с пределом 10.

Я могу легко сделать это с помощью кода, но мне интересно, есть ли способ сделать это с помощью одного SQL-запроса и будет ли он более производительным.

Я пробовал что-то вроде этого, но, очевидно, это не работает:

`(SELECT *, COUNT(*) as nb_rows_selected
    FROM subscriptions
    WHERE (last_chartmogul_sync_at IS NULL OR last_chartmogul_sync_at < updated_at) AND updated_at < $1
    ORDER BY last_chartmogul_sync_at NULLS FIRST
    GROUP BY id
    LIMIT $2)
UNION 
(SELECT * 
    FROM subscriptions
    ORDER BY last_sy NULLS FIRST
    LIMIT nb_rows_selected - $2)`,

Любая помощь будет оценена по достоинству. Спасибо!

Пусть запрос включает результаты обоих типов, но используйте ORDER BY CASE WHEN <criteria 1> THEN 1 WHEN <criteria 2> THEN 2 END, чтобы расставить приоритеты строк, которые должны находиться в пределах лимита.

Joel Coehoorn 22.08.2024 21:36
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
0
1
60
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Один из трюков, который вы можете использовать, — это добавить вычисляемый столбец для отслеживания каждой половины объединения. Затем снова выполните подзапрос и ограничите порядок, используя этот вычисляемый столбец для обеспечения порядка:

SELECT *
FROM
(
    (
        SELECT *, 1 AS src
        FROM subscriptions
        WHERE (last_sync_at IS NULL OR last_sync_at < updated_at) AND
          updated_at < $1
        ORDER BY last_sync_at NULLS FIRST
        LIMIT $2
    )
    UNION
    (
        SELECT *, 2
        FROM subscriptions
        ORDER BY last_sync_at NULLS FIRST
        LIMIT $1
    )
) t
ORDER BY src
LIMIT 100;

В приведенном выше примере будут использоваться записи из первого запроса по приоритету для заполнения предельной квоты в 100. Только если этот первый запрос возвращает менее 100 записей, будут использоваться записи из второго запроса.

Спасибо! У меня есть два замечания: 1. С точки зрения производительности, я думаю, это довольно плохо, не так ли? Потому что второй запрос всегда будет вычисляться, даже если первый вернул 100 записей? 2. Также, если записей меньше лимита. Например, 80 записей. Этот запрос заполнит результат, добавив дубликаты, чтобы достичь предела в 100. Что проблематично с моей точки зрения

Valentin Nasraty 22.08.2024 15:38

1. Да, возможно, производительность не очень хорошая, но вы всегда можете проверить план объяснения, индекс и т. д. 2. Ограничение в 100 означает, что 100 записей означает возврат наиболее доступных, но не более 100. Если доступно только 80, это будет вернуть 80.

Tim Biegeleisen 22.08.2024 15:52
Ответ принят как подходящий

Я нашел правильный ответ, немного изменив свой первый подход. Я по-прежнему использую UNION и COUNT, но на этот раз я упростил сложность, используя предложение С:

WITH unsynced_or_recently_updated_subscriptions AS (
    SELECT *
    FROM subscriptions
    WHERE (last_sync_at IS NULL OR last_sync_at < updated_at) 
    AND updated_at < $1
    ORDER BY last_sync_at NULLS FIRST
    LIMIT $2
),
oldest_synced_subscriptions AS (
    SELECT *
    FROM subscriptions
    WHERE id NOT IN (SELECT id FROM unsynced_or_recently_updated_subscriptions)
    ORDER BY last_sync_at NULLS FIRST
    LIMIT $2 - (SELECT COUNT(*) FROM unsynced_or_recently_updated_subscriptions)
)
SELECT *
FROM unsynced_or_recently_updated_subscriptions
UNION ALL
SELECT *
FROM oldest_synced_subscriptions;

Объяснение:

При таком подходе второй запрос ограничивается [the number of rows of the first query] - [the limit]. Что очень эффективно. Потому что, если первый запрос превысил лимит, второй запрос будет иметь лимит 0 и немедленно вернет результат.

Вопросы производительности:

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

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

Кроме того, предложение NOT IN должно оказывать минимальное влияние на производительность, поскольку столбец id индексируется.

Обновлено:

Еще лучшим решением может быть использование NOT EXISTS вместо NOT IN для второго подзапроса:

oldest_synced_subscriptions AS (
    SELECT *
    FROM subscriptions
    WHERE NOT EXISTS (
        SELECT id FROM unsynced_or_recently_updated_subscriptions 
        WHERE unsynced_or_recently_updated_subscriptions.id = subscriptions.id
    )
    ORDER BY last_chartmogul_sync_at NULLS FIRST
    LIMIT $2 - (SELECT COUNT(*) FROM unsynced_or_recently_updated_subscriptions)
)

Предложение NOT EXISTS обычно более эффективно, чем NOT IN, особенно при работе со значениями NULL. С точки зрения производительности NOT EXISTS часто работает быстрее, чем NOT IN, потому что он может прекратить оценку, как только найдет совпадение, тогда как NOT IN, возможно, придется сканировать весь набор результатов подзапроса.

Другие вопросы по теме