Préparation du SQL pour interroger une table de file d'attente prioritaire
-
19-08-2019 - |
Question
J'implémente une petite file d'attente pour gérer le processus à exécuter en premier. J'utilise une table dans une base de données pour le faire. Voici la structure de la table (je me moque de SQLite):
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ,
"identifier" VARCHAR NOT NULL ,
"priority_number" INTEGER DEFAULT 15,
"timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP,
"description" VARCHAR
J'essaie d'écrire SQL pour me donner la ligne du processus qui peut être exécuté ensuite. Voici quelques exemples de données:
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
Si j'utilise ce SQL, je peux obtenir les données dans le bon ordre:
select * from queue_manager order by priority_number, timestamp;
Cela me donnera l'élément avec le numéro de priorité le plus bas (le plus important) en haut, et dans ces numéros de priorité, le plus tôt dans la file d'attente (par horodatage) en haut.
Je pouvais exécuter cette requête et ne prendre que la première ligne, mais je préférerais le faire avec une requête SQL qui me donnerait la ligne du processus qui se trouve en haut de la file d'attente (dans l'exemple de données ci-dessus , la ligne avec id = 7).
J'ai essayé de faire des auto-jointures et des sous-requêtes, mais je dois avoir un blocage mental. Je n'arrive pas à comprendre.
Merci d'avance!
MODIFIER
J'ai oublié de mentionner que je recherche une requête indépendante de la base de données. Je me moque de cela dans SQlite, mais il est fort probable que je vais l'implémenter dans DB2 ou Oracle. J'avais pensé utiliser une "limite 1". tapez l'opérateur sur ma requête, mais c'est différent entre les différents moteurs de base de données.
La solution
Voir si cela fonctionne:
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)
Autres conseils
select * from queue_manager order by priority_number, timestamp LIMIT 1;
Pour ce qui est de ce qu'on appelle "l'indépendance de la base de données", c'est un mythe pour la plupart des tâches du monde réel. En règle générale, vous ne pouvez même pas créer de schéma indépendamment de la base de données.
Si vous voulez que ce soit "sûr en même temps" sur quelque chose comme InnoDB, faites:
1) Ajoutez un champ 'in_progress'.
2) Désactivez AUTOCommit
3) SELECT * FROM queue_manager où in_progress = 0 commande par numéro de priorité, horodatage LIMIT 1 FOR UDPATE;
4) UPDATE queue_manager SET SET in_progress = 1 où id = X;
5) COMMIT
6) Faites le travail. Supprimez ensuite la ligne lorsque vous avez terminé. Avoir un "processus maître" gérer / redéléguer / nettoyer les anciens travaux "in_progress".
La meilleure façon de faire est dépendante de la base de données; c’est beaucoup plus simple d’avoir des processus de récupération différents pour les différents SGBD cibles par rapport à la surcharge de curseurs ou d’autres constructions.
La sélection d’un nombre limité de lignes s’effectue différemment selon les types de code SQL. Par conséquent, il se peut que vous disposiez d’une méthode intégrée pour le faire. Par exemple, dans MS SQL Server:
SELECT TOP 1
identifier,
priority_number,
timestamp,
description
FROM
dbo.Queue_Manager
ORDER BY
priority_number,
timestamp
Pour ce faire, dans un SQL compatible ANSI, les méthodes suivantes devraient fonctionner:
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
Ou vous pouvez essayer:
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
Aucune méthode ne prend en compte les correspondances exactes dans BOTH priority_number et timestamp. Par conséquent, si vous pensez que cela est possible (et peut-être même si vous ne le faites pas), vous devrez ajouter une ligne ou deux pour passer à un niveau supérieur en utilisant l'identifiant ou quelque chose d'autre qui garantit l'unicité. Ou bien écrivez simplement votre frontal pour gérer le cas occasionnel de récupérer deux rangées (peut-être simplement ignorer la seconde - vous l'obtiendrez la prochaine fois).
Testez chaque méthode et voyez laquelle vous convient le mieux.
En outre, quelle devrait être la taille de la file d'attente? Il pourrait être raisonnable de simplement interroger avec ORDER BY et de n’avoir que le client récupéré la première ligne.
Lisez la cette section et sélectionnez la variante qui vous donne le plus compatibilité appropriée. L’utilisation de curseurs est sans doute la seule solution compatible de manière plus ou moins universelle, mais présente quelques inconvénients en termes de performances qui pourraient ne pas en valoir la peine (profil!).
Les bases de données relationnelles ne permettent pas de gérer les files d'attente.
Essayez de regarder MSMQ dans le monde Windows, ActiveMQ dans le monde Java ou Websphere MQ dans le monde des affaires.
Ces produits font une seule chose, gèrent les files d'attente, mais ils le font bien.