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.