Je pense qu'une situation peut être résolue à l'aide de la fonction de fenêtre, mais je ne suis pas sûr.
Imaginez le tableau suivant
CREATE TABLE tmp
( date timestamp,
id_type integer
) ;
INSERT INTO tmp
( date, id_type )
VALUES
( '2017-01-10 07:19:21.0', 3 ),
( '2017-01-10 07:19:22.0', 3 ),
( '2017-01-10 07:19:23.1', 3 ),
( '2017-01-10 07:19:24.1', 3 ),
( '2017-01-10 07:19:25.0', 3 ),
( '2017-01-10 07:19:26.0', 5 ),
( '2017-01-10 07:19:27.1', 3 ),
( '2017-01-10 07:19:28.0', 5 ),
( '2017-01-10 07:19:29.0', 5 ),
( '2017-01-10 07:19:30.1', 3 ),
( '2017-01-10 07:19:31.0', 5 ),
( '2017-01-10 07:19:32.0', 3 ),
( '2017-01-10 07:19:33.1', 5 ),
( '2017-01-10 07:19:35.0', 5 ),
( '2017-01-10 07:19:36.1', 5 ),
( '2017-01-10 07:19:37.1', 5 )
;
J'aimerais avoir un nouveau groupe à chaque changement sur la colonne id_type. EG 1er groupe de 7:19:21 à 7:19:25, 2ème départ et arrivée à 7:19:26, etc.
Une fois que cela fonctionne, je veux inclure plus de critères pour définir les groupes.
En ce moment, en utilisant la requête ci-dessous ...
SELECT distinct
min(min(date)) over w as begin,
max(max(date)) over w as end,
id_type
from tmp
GROUP BY id_type
WINDOW w as (PARTITION BY id_type)
order by begin;
J'obtiens le résultat suivant:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:37.1 5
Alors que je voudrais:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:25.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:26.0 5
2017-01-10 07:19:27.1 2017-01-10 07:19:27.1 3
2017-01-10 07:19:28.0 2017-01-10 07:19:29.0 5
2017-01-10 07:19:30.1 2017-01-10 07:19:30.1 3
2017-01-10 07:19:31.0 2017-01-10 07:19:31.0 5
2017-01-10 07:19:32.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:33.1 2017-01-10 07:19:37.1 5
Après avoir résolu cette première étape, j'ajouterai d'autres colonnes à utiliser comme règles pour rompre les groupes, et ces autres seront annulables.
Postgres Version: 8.4 (Nous avons Postgres avec Postgis, donc ce n'est pas facile à mettre à jour. Postgis Functions change les noms et il y a d'autres problèmes, mais j'espère que nous sommes déjà en train de tout réécrire et la nouvelle version utilisera une version plus récente 9.X avec postgis 2.x)
Réponses:
Pour quelques points,
tmp
qui devient confuse..0
)date
. S'il a une date et une heure, c'est un horodatage (et le stocker comme un)Mieux vaut utiliser une fonction de fenêtre ..
Les sorties
Explication
Nous avons d'abord besoin de réinitialisations. Nous les générons avec
lag()
Ensuite, nous comptons pour obtenir des groupes.
Ensuite , nous envelopper dans un sous - sélection
GROUP BY
etORDER
et sélectionnez le min max (plage)la source
1. Fonctions de fenêtre et sous-requêtes
Comptez les étapes pour former des groupes, similaires à l'idée d' Evan , avec des modifications et des correctifs:
Cela suppose que les colonnes concernées le sont
NOT NULL
. Sinon, vous devez en faire plus.En supposant également
date
d'être définiUNIQUE
, sinon vous devez ajouter un bris d'égalité auxORDER BY
clauses pour obtenir des résultats déterministes. Comme:ORDER BY date, id
.Explication détaillée (réponse à une question très similaire):
A noter en particulier:
Dans les cas connexes,
lag()
3 paramètres peuvent être essentiels pour couvrir élégamment le coin de la première (ou dernière) rangée. (Le 3ème paramètre est utilisé par défaut s'il n'y a pas de ligne précédente (suivante).Comme nous ne sommes intéressés que par un changement réel de
id_type
(TRUE
), cela n'a pas d'importance dans ce cas particulier.NULL
et lesFALSE
deux ne comptent passtep
.count(step OR NULL) OVER (ORDER BY date)
est la syntaxe la plus courte qui fonctionne également dans Postgres 9.3 ou version antérieure.count()
ne compte que les valeurs non nulles ...Dans Postgres moderne, la syntaxe la plus propre et équivalente serait:
Détails:
2. Soustrayez deux fonctions de fenêtre, une sous-requête
Similaire à l'idée d' Erik avec des modifications:
Si
date
est définiUNIQUE
, comme je l'ai mentionné ci-dessus (vous ne l'avez jamais précisé),dense_rank()
serait inutile, car le résultat est le même que pourrow_number()
et ce dernier est nettement moins cher.Si
date
n'est pas définiUNIQUE
(et nous ne savons pas que les seuls doublons sont activés(date, id_type)
), toutes ces requêtes sont inutiles, car le résultat est arbitraire.En outre, une sous-requête est généralement moins chère qu'un CTE à Postgres. N'utilisez les CTE que lorsque vous en avez besoin .
Réponses associées avec plus d'explications:
Dans les cas connexes où nous avons déjà un numéro courant dans la table, nous pouvons nous contenter d'une fonction de fenêtre unique:
3. Performances optimales avec la fonction plpgsql
Étant donné que cette question est devenue très populaire, j'ajouterai une autre solution pour démontrer les meilleures performances.
SQL dispose de nombreux outils sophistiqués pour créer des solutions avec une syntaxe courte et élégante. Mais un langage déclaratif a ses limites pour des exigences plus complexes qui impliquent des éléments procéduraux.
Une fonction procédurale côté serveur est plus rapide pour cela que tout ce qui a été publié jusqu'à présent car elle n'a besoin que d'une seule analyse séquentielle sur la table et d'une seule opération de tri . Si un index approprié est disponible, même un seul balayage d'index uniquement.
Appel:
Testez avec:
Vous pouvez rendre la fonction générique avec des types polymorphes et transmettre le type de table et les noms de colonne. Détails:
Si vous ne voulez pas ou ne pouvez pas conserver une fonction pour cela, il serait même avantageux de créer une fonction temporaire à la volée. Coûte quelques ms.
dbfiddle pour Postgres 9.6, comparant les performances des trois. Construction surle cas de test de Jack, modifiée.
dbfiddle pour Postgres 8.4, où les différences de performances sont encore plus importantes.
la source
count(x or null)
ou même ce qu'il fait là-bas. Peut-être pourriez-vous montrer quelques exemples là où cela est nécessaire, car ce n'est pas nécessaire ici. Et, qu'est-ce qui pourrait rendre obligatoire la couverture de ces cas d'angle. BTW, j'ai changé mon downvote en upvote juste pour l'exemple pl / pgsql. C'est vraiment cool. (Mais, en général, je suis contre les réponses qui résument d'autres réponses ou couvrent les cas d'angle - bien que je déteste dire que c'est un cas d'angle parce que je ne le comprends pas).count(x or null)
passe. Je serai heureux de poser les deux questions si vous préférez.count(x or null)
nécessaire dans les lacunes et les îles?Vous pouvez le faire comme une simple soustraction d'
ROW_NUMBER()
opérations (ou si vos dates ne sont pas uniques, bien que toujours uniques parid_type
, vous pouvez utiliser à laDENSE_RANK()
place, bien que ce soit une requête plus coûteuse):Voir ce travail chez DB Fiddle (ou voir la version DENSE_RANK )
Résultat:
Logiquement, vous pouvez penser à cela comme un simple
DENSE_RANK()
avec unPREORDER BY
, c'est-à-dire que vous voulez queDENSE_RANK
tous les articles soient classés ensemble et que vous les ordonniez par les dates, il vous suffit de faire face au problème embêtant du fait que à chaque changement de date,DENSE_RANK
augmentera. Vous faites cela en utilisant l'expression comme je vous l'ai montrée ci-dessus. Imaginez si vous aviez cette syntaxe:DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)
où lePREORDER
est exclu du calcul du classement et seul leORDER BY
est compté.Notez qu'il est important à la
GROUP BY
fois pour laSeq
colonne générée et pour laid_type
colonne.Seq
n'est PAS unique en soi, il peut y avoir des chevauchements - vous devez également grouper parid_type
.Pour plus de lecture sur ce sujet:
Ce premier lien vous donne un code que vous pouvez utiliser si vous souhaitez que la date de début ou de fin soit la même que la date de fin / début de la période précédente ou suivante (il n'y a donc pas de lacunes). Plus d'autres versions qui pourraient vous aider dans votre requête. Bien qu'ils doivent être traduits à partir de la syntaxe SQL Server ...
la source
Sur Postgres 8.4, vous pouvez utiliser une fonction RECURSIVE .
Comment font-ils
La fonction récursive ajoute un niveau à chaque id_type différent, en sélectionnant les dates une par une dans l'ordre décroissant.
Utilisez ensuite le regroupement MAX (date), MIN (date) par niveau, id_type pour obtenir le résultat souhaité.
Vérifiez-le: http://rextester.com/WCOYFP6623
la source
Voici une autre méthode, similaire à celle d'Evan et d'Erwin en ce qu'elle utilise LAG pour déterminer les îles. Il diffère de ces solutions en ce qu'il n'utilise qu'un seul niveau d'imbrication, aucun regroupement et beaucoup plus de fonctions de fenêtre:
La
is_start
colonne calculée dans le SELECT imbriqué marque le début de chaque îlot. De plus, le SELECT imbriqué expose la date précédente de chaque ligne et la dernière date de l'ensemble de données.Pour les lignes qui sont les débuts de leurs îles respectives, la date précédente est effectivement la date de fin de l'île précédente. C'est ainsi que le SELECT principal l'utilise. Il ne sélectionne que les lignes correspondant à la
is_start = 1
condition, et pour chaque ligne renvoyée, il affiche la propre lignedate
commebegin
et les lignes suivantesprev_date
commeend
. Comme la dernière ligne n'a pas de ligne suivante,LEAD(prev_date)
renvoie une valeur nulle pour elle, pour laquelle la fonction COALESCE remplace la dernière date de l'ensemble de données.Vous pouvez jouer avec cette solution sur dbfiddle .
Lorsque vous introduisez des colonnes supplémentaires identifiant les îles, vous souhaiterez probablement introduire un sous-paragraphe PARTITION BY dans la clause OVER de chaque fonction de fenêtre. Par exemple, si vous souhaitez détecter les îles au sein des groupes définis par a
parent_id
, la requête ci-dessus devra probablement ressembler à ceci:Et si vous décidez d'utiliser la solution d'Erwin ou d'Evan, je pense qu'un changement similaire devra également y être ajouté.
la source
Plus par intérêt académique que comme solution pratique, vous pouvez également y parvenir avec un agrégat défini par l' utilisateur . Comme les autres solutions, cela fonctionnera même sur Postgres 8.4, mais comme d'autres l'ont commenté, veuillez mettre à niveau si vous le pouvez.
L'agrégat se gère
null
comme s'il était différentfoo_type
, donc les séries de valeurs nulles recevraient la même chosegrp
- cela peut ou non être ce que vous voulez.dbfiddle ici
la source
Cela peut être fait avec
RECURSIVE CTE
pour passer "l'heure de début" d'une ligne à l'autre, et quelques préparations supplémentaires (de commodité).Cette requête renvoie le résultat souhaité:
après la préparation ... partie récursive
Vous pouvez le vérifier sur http://rextester.com/POYM83542
Cette méthode ne s'adapte pas bien. Pour une table de lignes 8_641, cela prend 7 secondes, pour une table deux fois plus grande, cela prend 28 secondes. Quelques échantillons supplémentaires montrent des temps d'exécution ressemblant à O (n ^ 2).
La méthode d'Evan Carrol prend moins de 1s (c'est-à-dire: allez-y!), Et ressemble à O (n). Les requêtes récursives sont absolument inefficaces et doivent être considérées comme un dernier recours.
la source