Разработка SQL-кода для запроса таблицы очередей приоритетов
-
19-08-2019 - |
Вопрос
Я реализую небольшую очередь, чтобы определить, какой процесс запускается первым.Для этого я использую таблицу в базе данных.Вот структура таблицы (я макетирую ее в SQLite):
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ,
"identifier" VARCHAR NOT NULL ,
"priority_number" INTEGER DEFAULT 15,
"timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP,
"description" VARCHAR
Я пытаюсь написать SQL, чтобы указать строку, какой процесс может быть запущен следующим.Вот некоторые примеры данных:
id identifier priority_number timestamp description
1 test1 15 2009-01-20 17:14:49 NULL
2 test2 15 2009-01-20 17:14:56 NULL
3 test3 10 2009-01-20 17:15:03 NULL
4 test4 15 2009-01-20 17:15:08 NULL
5 test5 15 2009-01-20 17:32:23 NULL
6 test6 14 2009-01-20 17:32:30 NULL
7 test7 7 2009-01-20 17:32:38 NULL
8 test8 20 2009-01-20 17:32:57 NULL
9 test9 7 2009-01-21 13:47:30 NULL
10 test10 15 2009-01-21 13:50:52 NULL
Если я использую этот SQL, я могу получить данные в правильном порядке:
select * from queue_manager order by priority_number, timestamp;
Это даст мне элемент с наименьшим номером приоритета (наиболее важный) вверху, и с этими номерами приоритета, самый ранний в очереди (по метке времени) вверху.
Я мог бы запустить этот запрос и взять только первую строку, но я бы предпочел сделать это с помощью SQL-запроса, который выдал бы мне одну строку процесса, находящуюся в верхней части очереди (в приведенном выше примере данных строка с id = 7).
Я пробовал выполнять самосоединения и вложенные запросы, но, должно быть, у меня ментальный блок - кажется, я просто не могу сделать это правильно.
Заранее спасибо!
Редактировать
Я забыл упомянуть, что я ищу запрос, независимый от базы данных.Я создаю это в SQLite, но есть большая вероятность, что я реализую это в DB2 или Oracle.Я думал использовать оператор типа "limit 1" в своем запросе, но это отличается в разных ядрах базы данных.
Решение
Посмотрим, сработает ли это:
select * from queue_manager where priority_number =
(select min(priority_number) from queue_manager) and
timestamp = (select min(timestamp)
from queue_manager qm2
where qm2.priority_number = queue_manager.priority_number)
Другие советы
select * from queue_manager order by priority_number, timestamp LIMIT 1;
Что касается так называемой "независимости базы данных", то это миф для большинства реальных задач.Как правило, вы даже не можете создать схему независимым от базы данных способом.
Если вы хотите, чтобы это было "безопасно для одновременного использования" на чем-то вроде InnoDB, сделайте:
1) Добавьте поле "in_progress".
2) Отключите автоматическую фиксацию
3) ВЫБЕРИТЕ * ИЗ queue_manager, где in_progress = 0 порядок по priority_number, ОГРАНИЧЕНИЕ по временной метке 1 ДЛЯ UDPATE;
4) ОБНОВИТЬ queue_manager, УСТАНОВИТЬ in_progress = 1, где id = X;
5) СОВЕРШИТЬ
6) Выполняйте свою работу.Затем удалите строку, когда она будет выполнена удовлетворительно.Попросите "главный процесс" обработать / переопределить / очистить старые задания "in_progress".
Лучший способ сделать это зависит от базы данных;гораздо проще иметь разные процедуры поиска для разных целевых СУБД по сравнению со всеми накладными расходами курсоров или других конструкций.
Выбор ограниченного количества строк выполняется по-разному в разных версиях SQL, поэтому в зависимости от того, что вы используете, может существовать встроенный способ сделать это.Например, в MS SQL Server:
SELECT TOP 1
identifier,
priority_number,
timestamp,
description
FROM
dbo.Queue_Manager
ORDER BY
priority_number,
timestamp
Чтобы сделать это в ANSI-совместимом SQL, должны работать следующие методы:
SELECT
QM1.identifier,
QM1.priority_number,
QM1.timestamp,
QM1.description
FROM
Queue_Manager QM1
LEFT OUTER JOIN Queue_Manager QM2 ON
QM2.priority_number < QM1.priority_number OR
(QM2.priority_number = QM1.priority_number AND QM2.timestamp < QM1.timestamp)
/* If you're concerned that there might be an exact match by priority_number
and timestamp then you might want to add a bit more to the join */
WHERE
QM2.identifier IS NULL
Или вы можете попробовать:
SELECT
QM1.identifier,
QM1.priority_number,
QM1.timestamp,
QM1.description
FROM
Queue_Manager QM1
INNER JOIN
(
SELECT
priority_number
MIN(timestamp) AS timestamp,
FROM
Queue_Manager
WHERE
priority_number =
(
SELECT
MIN(priority_number)
FROM
Queue_Manager
)
GROUP BY
priority_number
) SQ1 ON
SQ1.priority_number = QM1.priority_number AND
SQ1.timestamp = QM1.timestamp
Ни один из методов не учитывает точных совпадений КАК в priority_number, так и в timestamp, поэтому, если вы считаете, что это возможно (и, возможно, даже если вы этого не сделаете), вам нужно будет добавить одну или две строки, чтобы перейти еще на один уровень, используя идентификатор или что-то еще, что гарантирует уникальность.Или просто напишите свой интерфейс, чтобы обрабатывать случайные случаи возврата двух строк (возможно, просто проигнорируйте вторую - вы получите ее в следующий раз).
Протестируйте каждый метод и посмотрите, какой из них работает лучше для вас.
Кроме того, насколько большой вы ожидаете получить очередь?Было бы разумно просто запросить ваш ORDER BY и попросить интерфейс получить только первую строку.
Читать это разделите и выберите вариант, который обеспечивает вам наиболее подходящую совместимость.Вероятно, использование курсоров является единственным более или менее универсально совместимым способом, но имеет некоторое снижение производительности, которое может не оправдать его (profile!).
Реляционные базы данных не очень хороши в управлении очередями.
Попробуйте посмотреть на MSMQ в мире Windows, ActiveMQ в мире Java или Websphere MQ в мире бизнеса.
Эти продукты делают одно-единственное дело - управляют очередями, но делают они это хорошо.