YapfHoofdpagina | Info | Hulp | FAQ (veelgestelde vragen) | Speciale pagina's | Aanmelden


Printervriendelijke versie | Voorbehoud | Privacybeleid

Windowing queries

Uit Yapf


Windowing functies zijn in principe een variatie op GROUP BY. Het grote verschil is dat de uitkomst van een GROUP-BY query altijd maar één resultaat per groep heeft, terwijl de uitkomst bij windowing queries extra gegevens over de groep toevoegd aan de records in de groep.

Voor diegenen die mee willen doen staat onderaan een tabel met speeldata.


Inhoud

Windowing in een notendop

Windowing

Windowing werkt met windows, partitions en frames. De windows geven een bereik op, partitions groeperen de resultaten en frames bepalen over welk deel van het window de resultaten zijn berekend. Hoe dat precies werkt volgt later, eerst een simpel voorbeeld:

SELECT datum, SUM(waarde) OVER () FROM d;

Deze query vraagt de datum op, plus de SUM() van alle getallen in de waarde kolom. Wat onmiddelijk op zou moeten vallen is dat er geen GROUP BY in de query staat. Nee dit is geen MySQL, het is een correcte syntax dankzij de OVER() sectie die PostgreSQL vertelt dat de SUM(waarde) moet worden berekend over de set records, het window dus, die worden aangeduid met de expressie tussen de haken van OVER(). In deze query is de expressie leeg en dus worden er geen beperkingen opgelegd aan het window en dus levert SUM(waarde) OVER () gewoon de SUM van alle waarde velden uit de hele tabel.

Door een voorwaarde te stellen aan OVER() wordt het window aanzienlijk aangepast. Een simpele 'ORDER BY datum zorgt ervoor dat de SUM() niet maar een optelling van alle records is, maar van de records die in de volgorde van het datum veld langs zijn gekomen:

SELECT datum, SUM(waarde) OVER (ORDER BY datum) FROM d;

Effectief is SUM(waarde) OVER (ORDER BY datum) dus een running total van alle waarden zoals ze voorkomen in de lijst gesorteerd op datum.

Noot heel erg dat het niet uitmaakt of je achter de FROM nog een ORDER BY zet. Een ORDER BY achter de FROM zal alleen de uitslag van de query sorteren, niet de data waarop de uitslag is gebaseerd.

Partitioning

Met partitioning kun je binnen windows opdelen in blokken die iets gemeen hebben. De beste uitleg is een voorbeeld:

SELECT
  datum
  , DATE_TRUNC('month',datum)
  , waarde
  , SUM(waarde) OVER (PARTITION BY DATE_TRUNC('month',datum) ORDER BY datum)
FROM
  d
ORDER BY
  datum
LIMIT
  10;

Door de PARTITION op DATE_TRUNC worden de resultaten van de SUM(waarde) nu bekend per maand, dus het running-total begint elke maand weer met opnieuw:

        datum        |     date_trunc      | waarde | sum 
---------------------+---------------------+--------+-----
 2011-01-10 23:18:19 | 2011-01-01 00:00:00 |     40 |  40
 2011-01-11 03:43:46 | 2011-01-01 00:00:00 |     40 |  80
 2011-01-11 22:07:51 | 2011-01-01 00:00:00 |     76 | 156
 2011-01-11 23:27:25 | 2011-01-01 00:00:00 |     55 | 211
 2011-01-21 00:01:39 | 2011-01-01 00:00:00 |      3 | 214
 2011-02-03 12:24:50 | 2011-02-01 00:00:00 |     36 |  36
 2011-02-04 08:27:17 | 2011-02-01 00:00:00 |     97 | 133
 2011-02-04 17:38:51 | 2011-02-01 00:00:00 |      1 | 134
 2011-02-08 22:50:19 | 2011-02-01 00:00:00 |     84 | 218
 2011-02-15 01:34:24 | 2011-02-01 00:00:00 |     96 | 314
(10 rows)

Voorgedefinieerde windows

Om de schrijfwijze van je query netjes te houden kun je windows apart definieren en in je query hergebruiken. Dit gebeurt simpelweg door na de FROM clausule een WINDOW clausule op te nemen, in de vorm WINDOW naam AS (definitie)

SELECT
  datum
  , RANK() OVER (w)
FROM
  d
WINDOW
  w AS (ORDER BY datum)
ORDER BY
  datum ASC;

En dat helpt heel erg als je nogal los gaat met je query. Het volgende voorbeeld haalt een hele rits gegevens op en een aantal daarvan gaan over een bewerking op de datum (afkorting tot alleen de maand). Zonder voorgedefinieerde windows is dat:

SELECT
  datum
  , waarde
  , DATE_TRUNC('month',datum)
  , ROUND(AVG(waarde) OVER (PARTITION BY DATE_TRUNC('month',datum)),2) as gemiddelde_per_maand
  , SUM(waarde) OVER (PARTITION BY DATE_TRUNC('month',datum)) as totaal
  , SUM(waarde) OVER (PARTITION BY DATE_TRUNC('month',datum) ORDER BY datum ASC) as totaal
  
FROM d
ORDER BY datum ASC

Dat is correcte syntax en werkt prima, maar een aantal OVER expressie worden herhaald dus wanneer ze moeten worden aangepast moet je ze allemaal handmatig nalopen.

Met voorgedefinieerde windows wordt het:

SELECT
  datum
  , waarde
  , DATE_TRUNC('month',datum)
  , ROUND(AVG(waarde) OVER (window_per_maand),2) as gemiddelde_per_maand
  , SUM(waarde) OVER window_per_maand_sorteer_waarde as totaal
  , SUM(waarde) OVER window_per_maand_sorteer_datum as lopendtotaal_maand
  
FROM d
  WINDOW
   window_per_maand AS (PARTITION BY DATE_TRUNC('month',datum))
  , window_per_maand_sorteer_waarde AS (window_per_maand ORDER BY waarde)
  , window_per_maand_sorteer_datum AS (window_per_maand ORDER BY datum)
  ORDER BY datum ASC

Noot heel erg hoe de voorgedefinieerde windows elkaar hergebruiken. Er is maar één windowdefinitie met de DATE_TRUNC erin, de andere twee windows hergebruiken hem als basis en breiden daarop uit. Waneer er noodzaak is om de partitionering te veranderen dan hoeft alleen de definitie van window_per_maand aangepast te worden.

Functionaliteiten

Binnen windowing kun je een aantal functies toepassen om data van de windows te verkrijgen. Ze staan allemaal uitgelegd in de handleiding:

- http://www.postgresql.org/docs/9.0/interactive/functions-window.html - http://www.postgresql.org/docs/9.0/interactive/sql-select.html

Ik behandel er een paar, de rest mag je lekker zelf nalezen.


Ranking en regelnummers

Een van de handigere toepassingen van windowing is het toekennen van rijnummers, rangordes etc.

ROW_NUMBER

De ROWNUMBER() functie geeft simpelweg een nummertje dat optelt met elk record wat er binnen een window gevonden wordt.

SELECT
  datum
  , RANK() OVER (ORDER BY datum) AS rangorde
FROM
  d
ORDER BY
  datum ASC;


RANK

Met RANK krijg je een tellertje dat aangeeft op welke positie binnen de gesorteerde lijst elk record staat. Let op dat dit dus anders is dan ROW_NUMBER; als twee records dezelfde waarde hebben geeft RANK() aan dat ze op een gedeelde n-de plaats staan, terwijl ROW_NUMBER() gewoon doortelt.

SELECT
  datum
  , RANK() OVER (ORDER BY datum) AS rangorde
FROM
  d
ORDER BY
  datum ASC;


Via partitioning kun je hiermee ook ranking per partitie ophalen. De volgende query geeft van elk record aan op de hoeveelste plaats ze voorkomen binnen de maand.

SELECT
  datum
  , DATE_TRUNC('month',datum)
  , RANK() OVER(window_per_maand) AS rangorde
FROM d
  WINDOW
    window_per_maand AS (PARTITION BY DATE_TRUNC('month',datum))
ORDER BY datum ASC;


En om het verschil met ROW_NUMBER() duidelijk te maken:

SELECT
  datum
  , DATE_TRUNC('month',datum)
  , waarde
  , ROW_NUMBER() OVER (window_per_maand  order by waarde) AS rownum
  , RANK() OVER (window_per_maand ORDER BY waarde) AS ranknum
FROM d
  WINDOW
   window_per_maand AS (PARTITION BY DATE_TRUNC('month',datum))
ORDER BY DATE_TRUNC('month',datum)

Noot hoe rownum netjes 1-5 telt, en ranknum 1,2,2,4,5 bevat.

DENSE_RANK

Met DENSE_RANK wordt ook de rangorde opgegeven, maar nu zonder posities over te slaan. In plaats van 1,2,2,4,5 komt er nu 1,2,2,3,4 uit.


SELECT
  datum
  , DATE_TRUNC('month',datum)
  , waarde
  , ROW_NUMBER() OVER (window_per_maand  order by waarde) AS rownum
  , DENSE_RANK() OVER (window_per_maand ORDER BY waarde) AS ranknum
FROM d
  WINDOW
   window_per_maand AS (PARTITION BY DATE_TRUNC('month',datum))
ORDER BY DATE_TRUNC('month',datum)

Volgende en vorige

Een van de vaakvoorkomende problemen is hoe je kunt achterhalen hoeveel een waarde uit een record verschilt met het volgende of vorige record. Met windowing wordt dit een eenvoudig klusje, want met LEAD() kun je het volgende record opvragen en met LAG() het vorige, ten opzichte van het record waarvoor je de LEAD() en LAG() opvraagt.

In dit voorbeeld wordt met LEAD() de volgende datum uit de serie gesorteerd op datum opgehaald:

SELECT
  datum,
  LEAD(datum) OVER (ORDER BY datum) AS volgende
FROM
  d;

Om het verschil tussen de twee op te halen kun je ze simpelweg van elkaar aftrekken:

SELECT
  datum,
  LEAD(datum) OVER (ORDER BY datum) - datum AS verschil_met_volgende
FROM
  d;

Let goed op welke datum er nu precies gebruikt wordt, de LEAD(datum) is de datum die komt na die van het huidige record, en de datum in '- datum' is de datum van het huidige record.

Eerste en laatste waarden

Met FIRST_VALUE() en LAST_VALUE() kun je respectievelijk de eerste en laatste waarde uit het window ophalen.

SELECT
  datum
  , DATE_TRUNC('month',datum)
  , waarde
  , FIRST_VALUE(waarde) OVER (window_per_maand) AS firstvalue
  , LAST_VALUE(waarde) OVER (window_per_maand) AS lastvalue
FROM
  d
WINDOW
  window_per_maand AS (PARTITION BY DATE_TRUNC('month',datum) ORDER BY datum)
ORDER BY
  datum

Let op dat je dus niet de hoogste/laagste waarde krijgt, maar de eerste/laatste uit de set waarover je windowt.

BOUNDRIES van frames

Binnen een windowing query kun je niet alleen opgeven hoe je hele window eruitziet, je kunt ook aangeven hoe elk frame eruitziet. Een frame is dat deel van het window wat per record worrdt bekeken om de uitkomste te bepalen. Standaard loopt het frame altijd van het eerste record van het window totaan het huidige record. Dit levert een onverwacht resultaat als je vraagt om de eerste datum, gesorteerd op waarde, gepartitioneerd op maand:

SELECT
  datum
  , DATE_TRUNC('month',datum)
  , waarde
  , FIRST_VALUE(datum) OVER (window_per_maand) AS firstvalue
  , LAST_VALUE(datum) OVER (window_per_maand) AS lastvalue
FROM
  d
WINDOW
  window_per_maand AS (PARTITION BY DATE_TRUNC('month',datum) ORDER BY waarde)
ORDER BY
  datum
LIMIT
  10;
        datum        |     date_trunc      | waarde |     firstvalue      |      lastvalue      
---------------------+---------------------+--------+---------------------+---------------------
 2011-01-10 23:18:19 | 2011-01-01 00:00:00 |     40 | 2011-01-21 00:01:39 | 2011-01-10 23:18:19
 2011-01-11 03:43:46 | 2011-01-01 00:00:00 |     40 | 2011-01-21 00:01:39 | 2011-01-10 23:18:19
 2011-01-11 22:07:51 | 2011-01-01 00:00:00 |     76 | 2011-01-21 00:01:39 | 2011-01-11 22:07:51
 2011-01-11 23:27:25 | 2011-01-01 00:00:00 |     55 | 2011-01-21 00:01:39 | 2011-01-11 23:27:25
 2011-01-21 00:01:39 | 2011-01-01 00:00:00 |      3 | 2011-01-21 00:01:39 | 2011-01-21 00:01:39
 2011-02-03 12:24:50 | 2011-02-01 00:00:00 |     36 | 2011-02-18 19:21:29 | 2011-02-03 12:24:50
 2011-02-04 08:27:17 | 2011-02-01 00:00:00 |     97 | 2011-02-18 19:21:29 | 2011-02-04 08:27:17
 2011-02-04 17:38:51 | 2011-02-01 00:00:00 |      1 | 2011-02-18 19:21:29 | 2011-02-04 17:38:51
 2011-02-08 22:50:19 | 2011-02-01 00:00:00 |     84 | 2011-02-18 19:21:29 | 2011-02-08 22:50:19
 2011-02-15 01:34:24 | 2011-02-01 00:00:00 |     96 | 2011-02-18 19:21:29 | 2011-02-15 01:34:24
(10 rows)

De lastvalue levert meerdere waarden op, terwijl je er maar één verwacht: de laatste. De reden hiervoor is die boundy. Per record wordt het frame ingesteld op alle voorgaande records in het window. Alle records die na het huidige komen worden niet meegenomen en dus is het huidige record altijd zelf het laatste record en daarmee ook de last_value.

Dit is aan te passen door de boundry op te geven via het ROWS statement in de windowdefinitie.

SELECT
  datum
  , DATE_TRUNC('month',datum)
  , waarde
  , FIRST_VALUE(datum) OVER (window_per_maand) AS firstvalue
  , LAST_VALUE(datum) OVER (window_per_maand ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS lastvalue
FROM
  d
WINDOW
  window_per_maand AS (PARTITION BY DATE_TRUNC('month',datum) ORDER BY waarde)
ORDER BY
  datum
LIMIT
  10;

En nu is de uitkomst wel wat je verwacht:

        datum        |     date_trunc      | waarde |     firstvalue      |      lastvalue      
---------------------+---------------------+--------+---------------------+---------------------
 2011-01-10 23:18:19 | 2011-01-01 00:00:00 |     40 | 2011-01-21 00:01:39 | 2011-01-11 22:07:51
 2011-01-11 03:43:46 | 2011-01-01 00:00:00 |     40 | 2011-01-21 00:01:39 | 2011-01-11 22:07:51
 2011-01-11 22:07:51 | 2011-01-01 00:00:00 |     76 | 2011-01-21 00:01:39 | 2011-01-11 22:07:51
 2011-01-11 23:27:25 | 2011-01-01 00:00:00 |     55 | 2011-01-21 00:01:39 | 2011-01-11 22:07:51
 2011-01-21 00:01:39 | 2011-01-01 00:00:00 |      3 | 2011-01-21 00:01:39 | 2011-01-11 22:07:51
 2011-02-03 12:24:50 | 2011-02-01 00:00:00 |     36 | 2011-02-18 19:21:29 | 2011-02-04 08:27:17
 2011-02-04 08:27:17 | 2011-02-01 00:00:00 |     97 | 2011-02-18 19:21:29 | 2011-02-04 08:27:17
 2011-02-04 17:38:51 | 2011-02-01 00:00:00 |      1 | 2011-02-18 19:21:29 | 2011-02-04 08:27:17
 2011-02-08 22:50:19 | 2011-02-01 00:00:00 |     84 | 2011-02-18 19:21:29 | 2011-02-04 08:27:17
 2011-02-15 01:34:24 | 2011-02-01 00:00:00 |     96 | 2011-02-18 19:21:29 | 2011-02-04 08:27:17
(10 rows)

Het window soortert op waarde, dus first_value geeft de datum die bij de laagste waarde hoort: 2011-01-21 00:01:39 voor waarde 3, en last_value geeft 2011-01-11 22:07:51 voor waarde 76.

Wat doet ROWS nu precies? Het geeft aan dat het frame loopt van UNBOUNDED PRECEDING tot UNBOUNDED FOLLOWING , oftewel ongelimiteerd alle rijen voor het huidige record, plus ongelimiteerd alle records na het huidige record.

In versie 9+ kun je voor UNBOUNDED ook een getal neerzetten, zodat je kunt framen over het huidige record plus bijvoorbeeld 2 records ervoor en 4 records erna.

NOOT

Let op dat je de frame boundries wel in de voorgedefinieerde windows kunt opnemen, maar dat dat gevolgen heeft voor alle windows die daar gebruik van maken. Als je bijvoorbeeld het frame op unbounded zet en vervolgens een SUM() gaat doen, dan krijg je geen running-total meer, maar gewoon de SUM van alle waarden in het window:

SELECT
  datum
  , DATE_TRUNC('month',datum)
  , waarde
  , SUM(waarde) OVER window_per_maand
FROM
  d
WINDOW
  window_per_maand AS (PARTITION BY DATE_TRUNC('month',datum) ORDER BY waarde ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
ORDER BY
  datum
LIMIT
  10;

Geeft:

        datum        |     date_trunc      | waarde | sum 
---------------------+---------------------+--------+-----
 2011-01-10 23:18:19 | 2011-01-01 00:00:00 |     40 | 214
 2011-01-11 03:43:46 | 2011-01-01 00:00:00 |     40 | 214
 2011-01-11 22:07:51 | 2011-01-01 00:00:00 |     76 | 214
 2011-01-11 23:27:25 | 2011-01-01 00:00:00 |     55 | 214
 2011-01-21 00:01:39 | 2011-01-01 00:00:00 |      3 | 214
 2011-02-03 12:24:50 | 2011-02-01 00:00:00 |     36 | 372
 2011-02-04 08:27:17 | 2011-02-01 00:00:00 |     97 | 372
 2011-02-04 17:38:51 | 2011-02-01 00:00:00 |      1 | 372
 2011-02-08 22:50:19 | 2011-02-01 00:00:00 |     84 | 372
 2011-02-15 01:34:24 | 2011-02-01 00:00:00 |     96 | 372
(10 rows)

SUM, AVG, MIN en MAX

Deze werken feitelijk precies zoals je ze in GROUP-BY kent:

SELECT
  datum
  , DATE_TRUNC('month',datum)
  , waarde
  , SUM(waarde) OVER window_per_maand
  , MIN(waarde) OVER window_per_maand
  , MAX(waarde) OVER window_per_maand
  , AVG(waarde) OVER window_per_maand
FROM
  d
WINDOW
  window_per_maand AS (PARTITION BY DATE_TRUNC('month',datum) ORDER BY waarde ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
ORDER BY
  datum
LIMIT
  10;

Praktische toepassingen

Ontbrekende records in series

Stel je logt elke dag een aantal gegevens en je wilt controleren of je ook echt van elke dag gegevens hebt. De controle of je van alle dagen data hebt is natuurlijk simpel omdat het aantal gegevens hetzelfde moet zijn als het aantal dagen, maar het controleren tussen welke dagen er data ontbreekt en hoeveel er ontbreekt is een heel ander verhaal.

Met LEAD() kun je in een window het volgende record benaderen en als die waarde niet precies één dag groter is dan de huidige, dan ontbreekt er dus iets:

CREATE TABLE eventlog (logid INTEGER, logdate DATE, statustext TEXT);
INSERT INTO eventlog VALUES (1, '2011-01-01', 'free: 100G');
INSERT INTO eventlog VALUES  (2, '2011-01-02', 'free: 90G');
INSERT INTO eventlog VALUES  (3, '2011-01-03', 'free: 89G');
INSERT INTO eventlog VALUES  (4, '2011-01-04', 'free: 87G');
INSERT INTO eventlog VALUES  (5, '2011-01-07', 'free: 20G');
INSERT INTO eventlog VALUES  (6, '2011-01-08', 'free: 10G');
INSERT INTO eventlog VALUES  (7, '2011-01-10', 'free: 50G');
INSERT INTO eventlog VALUES  (8, '2011-01-11', 'free: 120G');
 
SELECT
  *,(t.nextdate::timestamp - t.logdate::timestamp) AS datumverschil
FROM
(
  SELECT
    *
    , LEAD(logdate) OVER w AS nextdate
  FROM
    eventlog
  WINDOW w AS (ORDER BY logdate)
  ORDER BY
    logdate
) AS t
WHERE
    -- Het laatste record in de serie heeft nog geen volgend record
    t.nextdate IS NOT NULL 
AND
    (t.nextdate::timestamp - t.logdate::timestamp) > INTERVAL '1 DAY';

appendix A: Speeldata

CREATE TABLE d (
    datum timestamp without time zone NOT NULL,
    waarde integer
);

ALTER TABLE ONLY d
    ADD CONSTRAINT pk PRIMARY KEY (datum);

INSERT INTO d VALUES ('2011-02-04 17:38:51', 1);
INSERT INTO d VALUES ('2011-06-10 11:06:08', 2);
INSERT INTO d VALUES ('2011-01-21 00:01:39', 3);
INSERT INTO d VALUES ('2011-07-16 21:49:40', 4);
INSERT INTO d VALUES ('2011-04-23 03:23:02', 5);
INSERT INTO d VALUES ('2011-07-07 12:01:38', 6);
INSERT INTO d VALUES ('2011-04-19 05:37:30', 7);
INSERT INTO d VALUES ('2011-04-23 18:24:52', 9);
INSERT INTO d VALUES ('2011-11-08 13:37:32', 10);
INSERT INTO d VALUES ('2011-03-19 20:50:44', 11);
INSERT INTO d VALUES ('2011-02-23 22:24:50', 12);
INSERT INTO d VALUES ('2011-12-27 03:31:04', 13);
INSERT INTO d VALUES ('2011-02-28 10:41:09', 14);
INSERT INTO d VALUES ('2011-10-07 10:12:58', 15);
INSERT INTO d VALUES ('2011-09-25 23:10:08', 16);
INSERT INTO d VALUES ('2011-06-10 20:08:56', 18);
INSERT INTO d VALUES ('2011-04-29 19:21:15', 19);
INSERT INTO d VALUES ('2011-06-04 14:45:05', 20);
INSERT INTO d VALUES ('2011-03-18 16:03:09', 21);
INSERT INTO d VALUES ('2011-03-05 04:22:06', 22);
INSERT INTO d VALUES ('2011-06-04 01:22:47', 24);
INSERT INTO d VALUES ('2011-12-21 02:01:47', 25);
INSERT INTO d VALUES ('2011-06-12 14:33:43', 26);
INSERT INTO d VALUES ('2011-07-08 19:43:43', 27);
INSERT INTO d VALUES ('2011-11-09 13:30:22', 28);
INSERT INTO d VALUES ('2011-09-26 09:28:06', 29);
INSERT INTO d VALUES ('2011-05-01 11:33:06', 30);
INSERT INTO d VALUES ('2011-02-21 01:16:19', 31);
INSERT INTO d VALUES ('2011-10-30 07:18:14', 32);
INSERT INTO d VALUES ('2011-10-11 03:50:31', 33);
INSERT INTO d VALUES ('2011-03-11 06:29:15', 34);
INSERT INTO d VALUES ('2011-05-16 11:19:11', 35);
INSERT INTO d VALUES ('2011-02-03 12:24:50', 36);
INSERT INTO d VALUES ('2011-09-17 23:42:10', 37);
INSERT INTO d VALUES ('2011-09-04 22:07:58', 38);
INSERT INTO d VALUES ('2011-03-21 12:57:36', 39);
INSERT INTO d VALUES ('2011-01-10 23:18:19', 40);
INSERT INTO d VALUES ('2011-07-12 16:56:48', 41);
INSERT INTO d VALUES ('2011-06-10 14:59:37', 42);
INSERT INTO d VALUES ('2011-03-04 02:54:26', 43);
INSERT INTO d VALUES ('2011-07-09 01:39:09', 44);
INSERT INTO d VALUES ('2011-08-08 06:52:03', 45);
INSERT INTO d VALUES ('2011-12-10 18:18:41', 46);
INSERT INTO d VALUES ('2011-11-24 21:02:46', 48);
INSERT INTO d VALUES ('2011-08-03 06:33:06', 50);
INSERT INTO d VALUES ('2011-04-28 16:59:08', 51);
INSERT INTO d VALUES ('2011-08-08 16:53:21', 52);
INSERT INTO d VALUES ('2011-10-07 16:06:29', 53);
INSERT INTO d VALUES ('2011-05-09 01:54:10', 54);
INSERT INTO d VALUES ('2011-01-11 23:27:25', 55);
INSERT INTO d VALUES ('2011-09-27 23:19:33', 56);
INSERT INTO d VALUES ('2011-10-20 21:39:11', 57);
INSERT INTO d VALUES ('2011-07-20 00:22:26', 58);
INSERT INTO d VALUES ('2011-08-06 18:01:12', 59);
INSERT INTO d VALUES ('2011-07-16 12:18:34', 60);
INSERT INTO d VALUES ('2011-11-20 17:06:49', 61);
INSERT INTO d VALUES ('2011-09-27 00:28:49', 62);
INSERT INTO d VALUES ('2011-05-16 01:48:05', 63);
INSERT INTO d VALUES ('2011-09-01 02:08:37', 64);
INSERT INTO d VALUES ('2011-12-07 12:09:21', 65);
INSERT INTO d VALUES ('2011-10-01 18:18:33', 66);
INSERT INTO d VALUES ('2011-10-03 19:44:43', 67);
INSERT INTO d VALUES ('2011-08-24 17:02:48', 68);
INSERT INTO d VALUES ('2011-06-05 21:37:48', 69);
INSERT INTO d VALUES ('2011-12-24 13:53:36', 70);
INSERT INTO d VALUES ('2011-09-04 21:32:24', 71);
INSERT INTO d VALUES ('2011-12-17 19:45:53', 72);
INSERT INTO d VALUES ('2011-06-04 10:04:30', 73);
INSERT INTO d VALUES ('2011-11-08 05:38:07', 74);
INSERT INTO d VALUES ('2011-06-26 02:36:19', 75);
INSERT INTO d VALUES ('2011-01-11 22:07:51', 76);
INSERT INTO d VALUES ('2011-10-18 05:08:05', 77);
INSERT INTO d VALUES ('2011-09-29 13:48:10', 78);
INSERT INTO d VALUES ('2011-12-06 00:21:54', 79);
INSERT INTO d VALUES ('2011-03-08 05:58:17', 80);
INSERT INTO d VALUES ('2011-05-02 01:32:33', 81);
INSERT INTO d VALUES ('2011-04-03 22:32:18', 82);
INSERT INTO d VALUES ('2011-10-16 04:02:55', 83);
INSERT INTO d VALUES ('2011-02-08 22:50:19', 84);
INSERT INTO d VALUES ('2011-08-12 05:37:46', 85);
INSERT INTO d VALUES ('2011-10-27 08:41:37', 86);
INSERT INTO d VALUES ('2011-11-06 03:21:10', 87);
INSERT INTO d VALUES ('2011-06-02 08:28:14', 88);
INSERT INTO d VALUES ('2011-05-16 14:15:20', 89);
INSERT INTO d VALUES ('2011-06-12 02:33:39', 90);
INSERT INTO d VALUES ('2011-12-18 01:58:05', 91);
INSERT INTO d VALUES ('2011-03-08 08:13:45', 93);
INSERT INTO d VALUES ('2011-05-03 08:57:27', 94);
INSERT INTO d VALUES ('2011-12-06 19:53:19', 95);
INSERT INTO d VALUES ('2011-02-15 01:34:24', 96);
INSERT INTO d VALUES ('2011-02-04 08:27:17', 97);
INSERT INTO d VALUES ('2011-09-09 20:49:20', 98);
INSERT INTO d VALUES ('2011-10-08 23:48:29', 99);
INSERT INTO d VALUES ('2011-07-09 11:16:22', 100);
INSERT INTO d VALUES ('2011-01-11 03:43:46', 40);
INSERT INTO d VALUES ('2011-02-18 19:21:29', 1);
INSERT INTO d VALUES ('2011-05-20 19:38:55', 5);
INSERT INTO d VALUES ('2011-04-04 06:00:34', 5);
INSERT INTO d VALUES ('2011-04-06 12:33:26', 5);
INSERT INTO d VALUES ('2011-04-17 08:59:25', 5);

Meer informatie

Windowing wordt door meerder databases ondersteunt en komt ook voor in de SQL standaard. Voor PostgreSQL kun o.a. je terecht in de handleiding.

Ontvangen van "http://www.yapf.net/index.php/Windowing_queries"

Deze pagina is 1.437 maal bekeken. Deze pagina is het laatst bewerkt op 19 jun 2011 om 08:28.


Zoeken

Bladeren
Hoofdpagina
Gebruikersportaal
In het nieuws
Recente wijzigingen
Willekeurige pagina
Hulp
Bewerken
Brontekst bekijken
Hulp bij bewerken
Paginaopties
Overlegpagina
Nieuw kopje
Printervriendelijke versie
Pagina-informatie
Paginageschiedenis
Verwijzingen naar deze pagina
Verwante wijzigingen
Mijn pagina's
Aanmelden / registreren
Speciale pagina’s
Nieuwe pagina's
Bestandslijst
Statistieken
Meer…