Regroupement ou fenêtre


13

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:


4

Pour quelques points,

  • N'appelez pas une table non temporaire tmpqui devient confuse.
  • N'utilisez pas de texte pour les horodatages (vous le faites dans votre exemple, nous pouvons le dire parce que l'horodatage n'a pas été tronqué et a .0)
  • N'appelez pas un champ qui a du temps 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 ..

SELECT id_type, grp, min(date), max(date)
FROM (
  SELECT date, id_type, count(is_reset) OVER (ORDER BY date) AS grp
  FROM (
    SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
    FROM tmp
  ) AS t
) AS g
GROUP BY id_type, grp
ORDER BY min(date);

Les sorties

 id_type | grp |          min          |          max          
---------+-----+-----------------------+-----------------------
       3 |   0 | 2017-01-10 07:19:21.0 | 2017-01-10 07:19:25.0
       5 |   1 | 2017-01-10 07:19:26.0 | 2017-01-10 07:19:26.0
       3 |   2 | 2017-01-10 07:19:27.1 | 2017-01-10 07:19:27.1
       5 |   3 | 2017-01-10 07:19:28.0 | 2017-01-10 07:19:29.0
       3 |   4 | 2017-01-10 07:19:30.1 | 2017-01-10 07:19:30.1
       5 |   5 | 2017-01-10 07:19:31.0 | 2017-01-10 07:19:31.0
       3 |   6 | 2017-01-10 07:19:32.0 | 2017-01-10 07:19:32.0
       5 |   7 | 2017-01-10 07:19:33.1 | 2017-01-10 07:19:37.1
(8 rows)

Explication

Nous avons d'abord besoin de réinitialisations. Nous les générons avec lag()

SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
FROM tmp
ORDER BY date;

         date          | id_type | is_reset 
-----------------------+---------+----------
 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 |        1
 2017-01-10 07:19:27.1 |       3 |        1
 2017-01-10 07:19:28.0 |       5 |        1
 2017-01-10 07:19:29.0 |       5 |         
 2017-01-10 07:19:30.1 |       3 |        1
 2017-01-10 07:19:31.0 |       5 |        1
 2017-01-10 07:19:32.0 |       3 |        1
 2017-01-10 07:19:33.1 |       5 |        1
 2017-01-10 07:19:35.0 |       5 |         
 2017-01-10 07:19:36.1 |       5 |         
 2017-01-10 07:19:37.1 |       5 |         
(16 rows)

Ensuite, nous comptons pour obtenir des groupes.

SELECT date, id_type, count(is_reset) OVER (ORDER BY date) AS grp
FROM (
  SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
  FROM tmp
  ORDER BY date
) AS t
ORDER BY date

         date          | id_type | grp 
-----------------------+---------+-----
 2017-01-10 07:19:21.0 |       3 |   0
 2017-01-10 07:19:22.0 |       3 |   0
 2017-01-10 07:19:23.1 |       3 |   0
 2017-01-10 07:19:24.1 |       3 |   0
 2017-01-10 07:19:25.0 |       3 |   0
 2017-01-10 07:19:26.0 |       5 |   1
 2017-01-10 07:19:27.1 |       3 |   2
 2017-01-10 07:19:28.0 |       5 |   3
 2017-01-10 07:19:29.0 |       5 |   3
 2017-01-10 07:19:30.1 |       3 |   4
 2017-01-10 07:19:31.0 |       5 |   5
 2017-01-10 07:19:32.0 |       3 |   6
 2017-01-10 07:19:33.1 |       5 |   7
 2017-01-10 07:19:35.0 |       5 |   7
 2017-01-10 07:19:36.1 |       5 |   7
 2017-01-10 07:19:37.1 |       5 |   7
(16 rows)

Ensuite , nous envelopper dans un sous - sélection GROUP BYet ORDERet sélectionnez le min max (plage)

SELECT id_type, grp, min(date), max(date)
FROM (
  .. stuff
) AS g
GROUP BY id_type, grp
ORDER BY min(date);

16

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:

SELECT id_type
     , min(date) AS begin
     , max(date) AS end
     , count(*)  AS row_ct  -- optional addition
FROM  (
   SELECT date, id_type, count(step OR NULL) OVER (ORDER BY date) AS grp
   FROM  (
      SELECT date, id_type
           , lag(id_type, 1, id_type) OVER (ORDER BY date) <> id_type AS step
      FROM   tmp
      ) sub1
   ) sub2
GROUP  BY id_type, grp
ORDER  BY min(date);

Cela suppose que les colonnes concernées le sont NOT NULL. Sinon, vous devez en faire plus.

En supposant également dated'être défini UNIQUE, sinon vous devez ajouter un bris d'égalité aux ORDER BYclauses 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).

    lag(id_type, 1, id_type) OVER ()

    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. NULLet les FALSEdeux ne comptent pas step.

  • 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:

    count(step) FILTER (WHERE step) OVER (ORDER BY date)

    Détails:

2. Soustrayez deux fonctions de fenêtre, une sous-requête

Similaire à l'idée d' Erik avec des modifications:

SELECT min(date) AS begin
     , max(date) AS end
     , id_type
FROM  (
   SELECT date, id_type
        , row_number() OVER (ORDER BY date)
        - row_number() OVER (PARTITION BY id_type ORDER BY date) AS grp
   FROM   tmp
   ) sub
GROUP  BY id_type, grp
ORDER  BY min(date);

Si dateest défini UNIQUE, 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 pour row_number()et ce dernier est nettement moins cher.

Si daten'est pas défini UNIQUE(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.

CREATE OR REPLACE FUNCTION f_tmp_groups()
  RETURNS TABLE (id_type int, grp_begin timestamp, grp_end timestamp) AS
$func$
DECLARE
   _row  tmp;                       -- use table type for row variable
BEGIN
   FOR _row IN
      TABLE tmp ORDER BY date       -- add more columns to make order deterministic
   LOOP
      CASE _row.id_type = id_type 
      WHEN TRUE THEN                -- same group continues
         grp_end := _row.date;      -- remember last date so far
      WHEN FALSE THEN               -- next group starts
         RETURN NEXT;               -- return result for last group
         id_type   := _row.id_type;
         grp_begin := _row.date;
         grp_end   := _row.date;
      ELSE                          -- NULL for 1st row
         id_type   := _row.id_type; -- remember row data for starters
         grp_begin := _row.date;
         grp_end   := _row.date;
      END CASE;
   END LOOP;

   RETURN NEXT;                     -- return last result row      
END
$func$ LANGUAGE plpgsql;

Appel:

SELECT * FROM f_tmp_groups();

Testez avec:

EXPLAIN (ANALYZE, TIMING OFF)  -- to focus on total performance
SELECT * FROM  f_tmp_groups();

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.


Lisez ceci plusieurs fois - toujours incertain de ce dont vous parlez avec le décalage de trois arguments ou quand vous devriez utiliser 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).
Evan Carroll

Je les mettrais dans deux questions distinctes à réponse libre parce que je suis sûr que je ne suis pas le seul à me demander ce qui se count(x or null)passe. Je serai heureux de poser les deux questions si vous préférez.
Evan Carroll


7

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 par id_type, vous pouvez utiliser à la DENSE_RANK()place, bien que ce soit une requête plus coûteuse):

WITH IdTypes AS (
   SELECT
      date,
      id_type,
      Row_Number() OVER (ORDER BY date)
         - Row_Number() OVER (PARTITION BY id_type ORDER BY date)
         AS Seq
   FROM
      tmp
)
SELECT
   Min(date) AS begin,
   Max(date) AS end,
   id_type
FROM IdTypes
GROUP BY id_type, Seq
ORDER BY begin
;

Voir ce travail chez DB Fiddle (ou voir la version DENSE_RANK )

Résultat:

begin                  end                    id_type
---------------------  ---------------------  -------
2017-01-10 07:19:21    2017-01-10 07:19:25    3
2017-01-10 07:19:26    2017-01-10 07:19:26    5
2017-01-10 07:19:27.1  2017-01-10 07:19:27.1  3
2017-01-10 07:19:28    2017-01-10 07:19:29    5
2017-01-10 07:19:30.1  2017-01-10 07:19:30.1  3
2017-01-10 07:19:31    2017-01-10 07:19:31    5
2017-01-10 07:19:32    2017-01-10 07:19:32    3
2017-01-10 07:19:33.1  2017-01-10 07:19:37.1  5

Logiquement, vous pouvez penser à cela comme un simple DENSE_RANK()avec un PREORDER BY, c'est-à-dire que vous voulez que DENSE_RANKtous 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_RANKaugmentera. 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ù le PREORDERest exclu du calcul du classement et seul le ORDER BYest compté.

Notez qu'il est important à la GROUP BYfois pour la Seqcolonne générée et pour la id_typecolonne. Seqn'est PAS unique en soi, il peut y avoir des chevauchements - vous devez également grouper par id_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 ...


6

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.

       date           | id_type | lv
--------------------------------------
2017-01-10 07:19:21.0      3       8
2017-01-10 07:19:22.0      3       8
2017-01-10 07:19:23.1      3       8
2017-01-10 07:19:24.1      3       8
2017-01-10 07:19:25.0      3       8
2017-01-10 07:19:26.0      5       7
2017-01-10 07:19:27.1      3       6
2017-01-10 07:19:28.0      5       5
2017-01-10 07:19:29.0      5       5
2017-01-10 07:19:30.1      3       4
2017-01-10 07:19:31.0      5       3
2017-01-10 07:19:32.0      3       2
2017-01-10 07:19:33.1      5       1
2017-01-10 07:19:35.0      5       1
2017-01-10 07:19:36.1      5       1
2017-01-10 07:19:37.1      5       1

Utilisez ensuite le regroupement MAX (date), MIN (date) par niveau, id_type pour obtenir le résultat souhaité.

with RECURSIVE rdates as 
(
    (select   date, id_type, 1 lv 
     from     yourTable
     order by date desc
     limit 1
    )
    union
    (select    d.date, d.id_type,
               case when r.id_type = d.id_type 
                    then r.lv 
                    else r.lv + 1 
               end lv    
    from       yourTable d
    inner join rdates r
    on         d.date < r.date
    order by   date desc
    limit      1)
)
select   min(date) StartDate,
         max(date) EndDate,
         id_type
from     rdates
group by lv, id_type
;

+---------------------+---------------------+---------+
| startdate           |       enddate       | id_type |
+---------------------+---------------------+---------+
| 10.01.2017 07:19:21 | 10.01.2017 07:19:25 |    3    |
| 10.01.2017 07:19:26 | 10.01.2017 07:19:26 |    5    |
| 10.01.2017 07:19:27 | 10.01.2017 07:19:27 |    3    |
| 10.01.2017 07:19:28 | 10.01.2017 07:19:29 |    5    |
| 10.01.2017 07:19:30 | 10.01.2017 07:19:30 |    3    |
| 10.01.2017 07:19:31 | 10.01.2017 07:19:31 |    5    |
| 10.01.2017 07:19:32 | 10.01.2017 07:19:32 |    3    |
| 10.01.2017 07:19:33 | 10.01.2017 07:19:37 |    5    |
+---------------------+---------------------+---------+

Vérifiez-le: http://rextester.com/WCOYFP6623


5

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:

SELECT
  id_type,
  date AS begin,
  COALESCE(
    LEAD(prev_date) OVER (ORDER BY date ASC),
    last_date
  ) AS end
FROM
  (
    SELECT
      id_type,
      date,
      LAG(date) OVER (ORDER BY date ASC) AS prev_date,
      MAX(date) OVER () AS last_date,
      CASE id_type
        WHEN LAG(id_type) OVER (ORDER BY date ASC)
        THEN 0
        ELSE 1
      END AS is_start
    FROM
      tmp
  ) AS derived
WHERE
  is_start = 1
ORDER BY
  date ASC
;

La is_startcolonne 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 = 1condition, et pour chaque ligne renvoyée, il affiche la propre ligne datecomme beginet les lignes suivantes prev_datecomme end. 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:

SELECT
  parent_id,
  id_type,
  date AS begin,
  COALESCE(
    LEAD(prev_date) OVER (PARTITION BY parent_id ORDER BY date ASC),
    last_date
  ) AS end
FROM
  (
    SELECT
      parent_id,
      id_type,
      date,
      LAG(date) OVER (PARTITION BY parent_id ORDER BY date ASC) AS prev_date,
      MAX(date) OVER (PARTITION BY parent_id) AS last_date,
      CASE id_type
        WHEN LAG(id_type) OVER (PARTITION BY parent_id ORDER BY date ASC)
        THEN 0
        ELSE 1
      END AS is_start
    FROM
      tmp
  ) AS derived
WHERE
  is_start = 1
ORDER BY
  date ASC
;

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é.


5

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 nullcomme s'il était différent foo_type, donc les séries de valeurs nulles recevraient la même chose grp- cela peut ou non être ce que vous voulez.

create function grp_sfunc(integer[],integer) returns integer[] language sql as $$
  select array[$1[1]+($1[2] is distinct from $2 or $1[3]=0)::integer,$2,1];
$$;
create function grp_finalfunc(integer[]) returns integer language sql as $$
  select $1[1];
$$;
create aggregate grp(integer)(
  sfunc = grp_sfunc
, stype = integer[]
, finalfunc = grp_finalfunc
, initcond = '{0,0,0}'
);
select min(foo_at) begin_at, max(foo_at) end_at, foo_type
from (select *, grp(foo_type) over (order by foo_at) from foo) z
group by grp, foo_type
order by 1;
begin_at | end_at | foo_type
: -------------------- | : -------------------- | -------:
10/01/2017 07:19:21 | 10/01/2017 07:19:25 | 3
10/01/2017 07:19:26 | 10/01/2017 07:19:26 | 5
10/01/2017 07: 19: 27.1 | 10/01/2017 07: 19: 27.1 | 3
10/01/2017 07:19:28 | 10/01/2017 07:19:29 | 5
10/01/2017 07: 19: 30.1 | 10/01/2017 07: 19: 30.1 | 3
10/01/2017 07:19:31 | 10/01/2017 07:19:31 | 5
10/01/2017 07:19:32 | 10/01/2017 07:19:32 | 3
10/01/2017 07: 19: 33.1 | 10/01/2017 07: 19: 37.1 | 5

dbfiddle ici


4

Cela peut être fait avec RECURSIVE CTEpour 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é:

WITH RECURSIVE q AS
(
    SELECT
        id_type,
        "date",
        /* We compute next id_type for convenience, plus row_number */
        row_number()  OVER (w) AS rn,
        lead(id_type) OVER (w) AS next_id_type
    FROM
        t
    WINDOW
        w AS (ORDER BY "date") 
)

après la préparation ... partie récursive

, rec AS 
(
    /* Anchor */
    SELECT
        q.rn,
        q."date" AS "begin",
        /* When next_id_type is different from Look also at **next** row to find out whether we need to mark an end */
        case when q.id_type is distinct from q.next_id_type then q."date" END AS "end",
        q.id_type
    FROM
        q
    WHERE
        rn = 1

    UNION ALL

    /* Loop */
    SELECT
        q.rn,
        /* We keep copying 'begin' from one row to the next while type doesn't change */
        case when q.id_type = rec.id_type then rec.begin else q."date" end AS "begin",
        case when q.id_type is distinct from q.next_id_type then q."date" end AS "end",
        q.id_type
    FROM
        rec
        JOIN q ON q.rn = rec.rn+1
)
-- We filter the rows where "end" is not null, and project only needed columns
SELECT
    "begin", "end", id_type
FROM
    rec
WHERE
    "end" is not null ;

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.