2008-10-08 5 views
1

Je souhaite enregistrer les états des utilisateurs, puis pouvoir générer des rapports historiques en fonction de l'enregistrement des modifications que nous avons conservées. J'essaye de faire ceci en SQL (using PostgreSQL) et j'ai une structure proposée pour enregistrer des changements d'utilisateur comme le suivant.Machine à états SQL - création de rapports sur les données historiques basées sur le changeset

CREATE TABLE users (
    userid SERIAL NOT NULL PRIMARY KEY, 
    name VARCHAR(40), 
    status CHAR NOT NULL 
); 

CREATE TABLE status_log (
    logid SERIAL, 
    userid INTEGER NOT NULL REFERENCES users(userid), 
    status CHAR NOT NULL, 
    logcreated TIMESTAMP 
); 

C'est ma structure de table proposée, basée sur les données.

Pour le champ d'état « a » représente un utilisateur actif et l « » représente un utilisateur suspendu,

INSERT INTO status_log (userid, status, logcreated) VALUES (1, 's', '2008-01-01'); 
INSERT INTO status_log (userid, status, logcreated) VALUES (1, 'a', '2008-02-01'); 

Ainsi, cet utilisateur a été suspendu le 1er janvier et de nouveau actif le 1er Février.

Si je voulais obtenir une liste de clients suspendue le 15 janvier 2008, l'ID utilisateur 1 devrait apparaître. Si je reçois une liste de clients suspendus le 15 février 2008, l'ID utilisateur 1 ne devrait pas apparaître.

1) Est-ce la meilleure façon de structurer ces données pour ce type de requête?

2) Comment puis-je interroger les données dans cette structure ou dans la structure modifiée que vous proposez afin de pouvoir simplement avoir une date (par exemple le 15 janvier) et trouver une liste de clients actifs à cette date? SQL seulement? Est-ce un travail pour SQL?

Répondre

2

Cela peut être fait, mais serait beaucoup plus efficace si vous avez enregistré la date de fin de chaque journal. Avec votre modèle, vous devez faire quelque chose comme:

select l1.userid 
from status_log l1 
where l1.status='s' 
and l1.logcreated = (select max(l2.logcreated) 
        from status_log l2 
        where l2.userid = l1.userid 
        and l2.logcreated <= date '2008-02-15' 
        ); 

Avec la colonne supplémentaire, il woud être plus comme: (. Toutes mes excuses pour les erreurs de syntaxe, je ne sais pas Postgresql)

select userid 
from status_log 
where status='s' 
and logcreated <= date '2008-02-15' 
and logsuperseded >= date '2008-02-15'; 

pour répondre à certaines autres questions soulevées par Phil:

peut se déplacer de l'état actif, à un utilisateur suspendu, annulé pour, d'agir encore une fois. C'est une version simplifiée, en réalité, il y a encore plus d'états et les gens peuvent être déplacés directement d'un état à un autre.

Cela apparaît dans le tableau comme celui-ci:

userid from  to   status 
FRED 2008-01-01 2008-01-31 s 
FRED 2008-02-01 2008-02-07 c 
FRED 2008-02-08   a 

J'ai utilisé un nul pour la date « à » de l'enregistrement en cours. J'aurais pu utiliser une future date comme 2999-12-31 mais null est préférable à certains égards.

De plus, il n'y aurait pas non plus de "date de fin" pour le statut actuel, donc je pense que cela casse légèrement votre requête?

Oui, ma requête devrait être ré-écrit

select userid 
from status_log 
where status='s' 
and logcreated <= date '2008-02-15' 
and (logsuperseded is null or logsuperseded >= date '2008-02-15'); 

Un inconvénient de cette conception est que chaque fois que le statut vous change devez mettre fin à ce jour leur status_log actuelle de l'utilisateur ainsi que de créer un nouveau. Cependant, ce n'est pas difficile, et je pense que l'avantage de la requête l'emporte probablement sur cela.

0

@Tony la date de fin n'est pas forcément applicable.

Un utilisateur peut être déplacé de actif, suspendu, annulé, actif à nouveau. C'est une version simplifiée, en réalité, il y a encore plus d'états et les gens peuvent être déplacés directement d'un état à un autre.

De plus, il n'y aurait pas non plus de "date de fin" pour le statut actuel, donc je pense que cela casse légèrement votre requête?

0

@Phil

J'aime la solution de Tony. Il semble que la situation décrite soit la plus appropriée. Tout utilisateur particulier a un statut pour une période de temps donnée (une minute, une heure, un jour, etc.), mais c'est pour une durée, pas un instant dans le temps. Puisque vous voulez savoir qui était actif pendant une certaine période de temps, la modélisation de l'information comme une durée semble être la meilleure approche.

Je ne suis pas sûr que les statuts supplémentaires posent problème. Si quelqu'un est actif, puis suspendu, puis annulé, puis réactivé, chacun de ces statuts serait applicable pour une durée donnée, n'est-ce pas? Il peut s'agir d'une courte durée, par exemple de quelques secondes ou d'une minute, mais elles dureront encore longtemps.

Êtes-vous préoccupé par le fait que le statut d'une personne peut changer plusieurs fois dans une journée donnée, mais vous voulez savoir qui était actif pour un jour donné? Si c'est le cas, il vous suffit de définir plus précisément ce que signifie être actif un jour donné. S'il suffit qu'ils soient actifs pendant une partie de la journée, la réponse de Tony fonctionne bien comme elle l'est. Si elles devaient être actives pendant un certain temps au cours d'une journée donnée, la solution de Tony pourrait être modifiée pour déterminer simplement la durée (en heures, minutes ou jours) et ajouter d'autres restrictions dans la clause WHERE. récupérer la date, l'état et la durée de ce statut. Comme il n'y a pas de "date de fin" pour l'état actuel, ce n'est pas un problème tant que la date de fin est nulle. Utilisez simplement quelque chose comme ceci "WHERE enddate < = '2008-08-15' ou enddate est nul".

1

Postgres prend-il en charge les requêtes analytiques? Cela donnerait aux utilisateurs actifs sur 2008-02-15

select userid 
from 
(
select logid, 
     userid, 
     status, 
     logcreated, 
     max(logcreated) over (partition by userid) max_logcreated_by_user 
from status_log 
where logcreated <= date '2008-02-15' 
) 
where logcreated = max_logcreated_by_user 
    and status  = 'a' 
/
+0

oui, il prend en charge les requêtes analytiques -> http://www.postgresql.org/docs/8.4/static/tutorial-window.html – filiprem

Questions connexes