Question
Je cherche une seule requête capable de convertir les informations suivantes dans la table
name:time :state
a :10:00 AM:login
b :10:05 AM:login
a :10:06 AM:chatting
a :10:08 AM:Idle
b :10:11 AM:chatting
a :10:10 AM:Logout
b :10:12 AM:Logout
vers quelque chose comme ceci (étant donné l'intervalle de temps compris entre 10 h et 10 h 15)
name: State :Duration
a : chatting :2 Minutes
a : Idle :2 Minutes
b : chatting :1 Minute
Cela peut-il être fait UNIQUEMENT en utilisant SQL? J'utilise Informix version 11.5
La solution
Cela peut être fait en une seule instruction SQL. Voici la preuve.
Configuration
CREATE TEMP TABLE eventtable
(
name CHAR(3) NOT NULL,
time DATETIME HOUR TO MINUTE NOT NULL,
state CHAR(8) NOT NULL
);
INSERT INTO eventtable(name, time, state) VALUES('a', '10:00', 'login');
INSERT INTO eventtable(name, time, state) VALUES('b', '10:05', 'login');
INSERT INTO eventtable(name, time, state) VALUES('a', '10:06', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('a', '10:08', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('b', '10:11', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('a', '10:10', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('b', '10:12', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:01', 'login');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:02', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:03', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:04', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:05', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:06', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:07', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:08', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:09', 'login');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:11', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:12', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:13', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:14', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:15', 'Logout');
Requête correcte
Notez les conditions. La table de résultats doit exclure les points entre "login" et le premier autre événement. de plus, il doit exclure la période entre "Déconnexion" et le prochain événement (vraisemblablement un "login"). La jointure automatique entre la table de la colonne nom , puis la jointure asymétrique de la colonne heure (à l'aide de '<
') garantit que les événements se déroulent dans l'ordre chronologique. La sous-sélection NOT EXISTS garantit que seuls les événements adjacents sont pris en compte. L'utilisation de BETWEEN AND dans la sous-requête est une erreur car elle inclut ses extrémités et il est essentiel que r1.time
et r2.time
soient exclus de la plage; il m'a fallu quelques minutes pour repérer ce bogue (la requête a fonctionné mais n'a renvoyé aucune ligne, mais pourquoi ?)!
SELECT r1.name, r1.state, r2.TIME - r1.TIME AS duration
FROM eventtable r1, eventtable r2
WHERE r1.name = r2.name
AND r1.time < r2.time
AND r1.state != 'login'
AND r1.state != 'Logout'
AND r1.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
AND DATETIME(10:15) HOUR TO MINUTE
AND r2.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
AND DATETIME(10:15) HOUR TO MINUTE
AND NOT EXISTS (SELECT 1 FROM eventtable r3
WHERE r3.time > r1.time AND r3.time < r2.time
AND r3.name = r1.name
AND r3.name = r2.name);
Ceci produit la réponse:
name state duration
a chatting 0:02
a Idle 0:02
b chatting 0:01
c chatting 0:01
c Idle 0:01
c Idle 0:01
c Idle 0:01
c chatting 0:01
c Idle 0:01
c chatting 0:01
c Idle 0:01
La valeur de 'durée' est un intervalle entre heures et minutes; si vous voulez une valeur en quelques minutes seulement, vous devez la convertir avec une conversion (utilisez 4 pour la précision afin de permettre des intervalles allant jusqu'à 1440 minutes ou 1 jour; les données sont ambiguës pour des périodes plus longues):
(r2.time - r1.time)::INTERVAL MINUTE(4) TO MINUTE
Ou:
CAST (r2.time - r1.time AS INTERVAL MINUTE(4) TO MINUTE)
IBM Informix Dynamic Server (IDS) comporte des notations très détaillées pour les constantes de temps. En SQL standard, vous pouvez utiliser TIME en tant que type et TIME '10: 00: 00 'en tant que valeur, mais les secondes sont nécessaires en SQL strict. IDS fournit les types exacts souhaités par les utilisateurs - tels que DATETIME HEURE À MINUTE. Vous pouvez également écrire INTERVAL MINUTE (4) en SQL standard; "À MINUTE" devrait être facultatif.
Requête incorrecte
Dans ma réponse à la réponse de Ray Hidayat, j’ai souligné que la sous-requête EXISTS est nécessaire pour garantir que les événements considérés sont contigus - il n’existe aucun événement intermédiaire. Voici la même requête avec les heures de début et de fin ajoutées à la sortie et la clause EXISTS manquante (et "durée" renommé en "caduc"):
SELECT r1.name, r1.state, r2.TIME - r1.TIME AS lapse,
r1.time AS start, r2.time AS end
FROM eventtable r1, eventtable r2
WHERE r1.name = r2.name
AND r1.time < r2.time
AND r1.state != 'login'
AND r1.state != 'Logout'
AND r1.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
AND DATETIME(10:15) HOUR TO MINUTE
AND r2.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
AND DATETIME(10:15) HOUR TO MINUTE;
Ceci produit la réponse:
name state lapse start end
a chatting 0:04 10:06 10:10
a chatting 0:02 10:06 10:08
a Idle 0:02 10:08 10:10
b chatting 0:01 10:11 10:12
c chatting 0:13 10:02 10:15
c chatting 0:12 10:02 10:14
c chatting 0:11 10:02 10:13
c chatting 0:10 10:02 10:12
c chatting 0:09 10:02 10:11
c chatting 0:07 10:02 10:09
c chatting 0:06 10:02 10:08
c chatting 0:05 10:02 10:07
c chatting 0:04 10:02 10:06
c chatting 0:03 10:02 10:05
c chatting 0:02 10:02 10:04
c chatting 0:01 10:02 10:03
c Idle 0:12 10:03 10:15
c Idle 0:11 10:03 10:14
c Idle 0:10 10:03 10:13
c Idle 0:09 10:03 10:12
c Idle 0:08 10:03 10:11
c Idle 0:06 10:03 10:09
c Idle 0:05 10:03 10:08
c Idle 0:04 10:03 10:07
c Idle 0:03 10:03 10:06
c Idle 0:02 10:03 10:05
c Idle 0:01 10:03 10:04
c Idle 0:10 10:05 10:15
c Idle 0:09 10:05 10:14
c Idle 0:08 10:05 10:13
c Idle 0:07 10:05 10:12
c Idle 0:06 10:05 10:11
c Idle 0:04 10:05 10:09
c Idle 0:03 10:05 10:08
c Idle 0:02 10:05 10:07
c Idle 0:01 10:05 10:06
c Idle 0:08 10:07 10:15
c Idle 0:07 10:07 10:14
c Idle 0:06 10:07 10:13
c Idle 0:05 10:07 10:12
c Idle 0:04 10:07 10:11
c Idle 0:02 10:07 10:09
c Idle 0:01 10:07 10:08
c chatting 0:04 10:11 10:15
c chatting 0:03 10:11 10:14
c chatting 0:02 10:11 10:13
c chatting 0:01 10:11 10:12
c Idle 0:03 10:12 10:15
c Idle 0:02 10:12 10:14
c Idle 0:01 10:12 10:13
c chatting 0:02 10:13 10:15
c chatting 0:01 10:13 10:14
c Idle 0:01 10:14 10:15
Ceci montre comment chaque ligne de début éligible pour l'utilisateur 'c' est appariée avec chaque ligne de fin éligible, donnant ainsi de nombreuses lignes de données parasites. La sous-requête NOT EXISTS est un thème commun lors du traitement de requêtes temporelles. Vous pouvez trouver des informations sur ces opérations dans le & Quot; développement d'applications orientées temps de Snodgrass en SQL " (PDF disponible en ligne à l'adresse URL) et dans Date, Darwen et Lorentzos & Quot; Données temporelles et modèle relationnel & ";
Autres conseils
Je suis presque sûr que cela peut être fait en utilisant uniquement du SQL, cela va me prendre un peu de temps pour trouver une requête pour vous, je la modifierai quand j'aurai terminé. Je pense que les étapes de base seraient d’abord de calculer le temps que chacune prend (faites en prenant chaque entrée et en la joignant à l’entrée suivante et en soustrayant pour trouver la différence de temps), puis un simple groupe par clause avec une somme sera facilement obtenu dans la forme que vous avez décrite.
Edit: voici ce que j'ai trouvé
SELECT l.userid, l.state, SUM(t.minutes) AS duration
FROM Log l
INNER JOIN (
SELECT l1.id, (l2.time - l1.time) AS minutes
FROM Log l1, Log l2
WHERE l2.time == (
-- find the next entry --
SELECT TOP 1 ls.time
FROM Log ls
WHERE ls.Time > l1.Time && ls.userid = l1.userid
ORDER BY ls.Time
)
) t ON l.id == t.id
GROUP BY l.userid, l.state
ORDER BY l.userid
Il s’agit d’un semi-pseudo-code, j’ai composé tous les noms de tables et d’autres choses, et vous ne pourrez pas simplement soustraire une fois, vous utiliserez probablement la fonction DATEDIFF. En plus de cela, je pense que c'est l'essentiel. Je pense que SQL est l’un des langages les plus étonnants, vous pouvez faire presque tout avec peu de code.