[Postgresql] Top N rows in GROUP BY

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

Anoniem: 36940

Topicstarter
Voor een rating rapportage tool wil ik de Top 5 ratingscore per maand tonen van records die gerate zijn.
Met de functie date_trunc kan ik de maand van de rating timestamp en daarmee groeperen, maar ik kan er geen Top 5 resultaten per maand eruit krijgen. Ik zie iets over het hoofd, maar wat?

Ik zal een paar voorbeelden geven:

Tabel rating:
rating_createcontent_idscore
2009-12-01 09:12:12104
2009-12-05 10:12:12104
2009-12-17 11:12:12103
2009-12-10 12:12:12103
2009-12-21 13:12:12102
2009-12-15 14:12:12105
2009-12-11 15:12:12173
2009-12-20 16:12:12175
2009-12-01 17:12:12104
2009-12-05 18:12:12104
2010-01-17 19:12:12123
2010-01-10 20:12:12123
2010-01-21 21:12:12152
2010-01-15 22:12:12155
2010-01-11 23:12:12173
2010-01-20 24:12:12175

Etc. etc...

SQL:
1
2
3
4
SELECT to_char(date_trunc('month',rating_create),'YYYY-MM-DD') AS trunc, content_id, AVG(score) AS score, COUNT(rating_id) AS votes 
FROM trunc 
GROUP BY dates, content_id 
ORDER BY dates, score DESC, votes DESC


Het resultaat is dan bijvoorbeeld:

trunccontent_idscorevotes
2009-01-01234.9122
2009-01-01244.1144
2009-01-01254.1133
2009-01-01263.6155
2009-01-01273.4166
2009-01-01283.1177
2009-01-01293.0188
2010-01-01204.590
2010-01-01214.1100
2010-01-01224.0110
2010-01-01233.9120
2010-01-01243.4130
2010-01-01253.0140
2010-01-01262.8150
2010-01-01272.7160
2010-01-01281.8170


Ok, het resultaat per maand met unieke content_id's klopt, maar ik wil alleen de eerste 5 records van elke maand. Met LIMIT 5 krijg ik alleen de eerste 5 rows van het hele resultaat. Met HAVING COUNT(DISTINCT truncate) <= 5 gebeurt er niets met het resultaat :? Wat doe ik verkeerd?

Alvast bedankt!

Acties:
  • 0 Henk 'm!

  • JaQ
  • Registratie: Juni 2001
  • Laatst online: 08:44

JaQ

Als je echt lui bent, kan je een inline sub-query gebruiken?

Egoist: A person of low taste, more interested in themselves than in me


Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

Per group 5 records tonen is niet echt standaard sql-functionaliteit voor, althans niet als je voor 3 verschillende datums in totaal maximaal 15 records terug wilt zien, en dan voor elk van de drie 5.

Je kan wat klooien met subqueries of functions om zo de top5 terug te geven, in bijvoorbeeld een array. Maar in principe loopt dit snel uit op ofwel hele complexe queries, ofwel gewoon de keus om het dan maar per datum die je wilt weergeven los op te halen.
Als je records uniek identificeerbaar zijn met die sortering op rating, dan wordt het overigens makkelijker, dan kan je ze in de where-clause met een subquery eruit filteren door iets als WHERE id IN (SELECT id from ... where month = '...' order by rating limit 5).

Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

't Kan trouwens wel in PostgreSQL als je versie 8.4 gebruikt. Je kan dan gebruik maken van de "windowing functions" en dan in dit geval met name de rank()-functie.

Zie hier de voorbeelden aan het eind van dit verhaal:
http://www.postgresql.org...tive/tutorial-window.html

Acties:
  • 0 Henk 'm!

  • DamadmOO
  • Registratie: Maart 2005
  • Laatst online: 23:07
ACM schreef op donderdag 14 januari 2010 @ 17:27:
Per group 5 records tonen is niet echt standaard sql-functionaliteit voor, althans niet als je voor 3 verschillende datums in totaal maximaal 15 records terug wilt zien, en dan voor elk van de drie 5.
Semi offtopic: In MSSQL heb je daar sinds 2005 de volgende syntax voor
SQL:
1
 ROW_NUMBER() OVER(PARTITION BY Month ORDER BY Ranking DESC) AS RowNumber

en door dat dan in een CTE te wrappen kan je gewoon een query doen waar RowNumber <= 5

Verder zit het ook in Oracle en dus in PostgreSQL vanaf 8.4 e het zit in de standaard SQL;2003. Dus om te zeggen dat het niet echt standaard sql-functionaliteit is gaat me eigenlijk wel wat te ver.

Acties:
  • 0 Henk 'm!

  • cariolive23
  • Registratie: Januari 2007
  • Laatst online: 18-10-2024
DamadmOO schreef op donderdag 14 januari 2010 @ 21:19:
en door dat dan in een CTE te wrappen kan je gewoon een query doen waar RowNumber <= 5
Dat is volgens mij niet nodig, je kunt de query gewoon als subquery opnemen in een FROM en in de WHERE een vegelijking doen op het rownumber:

SELECT * FROM (hier jouw subquery met windowing) AS sub WHERE rownumber <= 5;

Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

DamadmOO schreef op donderdag 14 januari 2010 @ 21:19:
Dus om te zeggen dat het niet echt standaard sql-functionaliteit is gaat me eigenlijk wel wat te ver.
Nouja, je moet eerst alle rows produceren en dan achteraf een (groot?) deel wegfilteren. Dus op zich is het niet echt standaard beschikbaar in sql, bij databases die dergelijke ranks/rownumbers kunnen genereren per partitie kan je het inderdaad wel gewoon bereiken.

Acties:
  • 0 Henk 'm!

  • cariolive23
  • Registratie: Januari 2007
  • Laatst online: 18-10-2024
ACM schreef op vrijdag 15 januari 2010 @ 10:32:
[...]

Nouja, je moet eerst alle rows produceren en dan achteraf een (groot?) deel wegfilteren. Dus op zich is het niet echt standaard beschikbaar in sql, bij databases die dergelijke ranks/rownumbers kunnen genereren per partitie kan je het inderdaad wel gewoon bereiken.
<mierenneukmodus>
Het is dus wél standaard beschikbaar in SQL, zie de SQL Standaarden. Dat niet ieder merk database dit heeft geïmplementeerd (in iedere versie), dat is het probleem van dit merk database en niet van de taal SQL.

PostgreSQL, SQL Server en Oracle kennen het wel, doe er je voordeel mee.
</mierenneukmodus>

Acties:
  • 0 Henk 'm!

Anoniem: 36940

Topicstarter
Allemaal bedankt voor de reacties!

Ik had eerder de window functie gezien, maar m'n hosting heeft PG 8.2, dus dat is geen optie. Ik heb ook een rank optie geprobeerd met een 'temporary sequence' zoals http://www.barik.net/archive/2006/04/30/162447/ maar dat werkt ook niet.

De enigste manier voor mij lijkt om deze query los op te halen per 'month'. Als PG 8.4 (of hoger) draait, dan kan ik de query met window ombouwen.

Acties:
  • 0 Henk 'm!

  • DamadmOO
  • Registratie: Maart 2005
  • Laatst online: 23:07
cariolive23 schreef op vrijdag 15 januari 2010 @ 09:53:
[...]

Dat is volgens mij niet nodig, je kunt de query gewoon als subquery opnemen in een FROM en in de WHERE een vegelijking doen op het rownumber:

SELECT * FROM (hier jouw subquery met windowing) AS sub WHERE rownumber <= 5;
Je kan in de where clause geen alias opgeven. En de ROW_NUMBER() OVER clause kan ook niet in de where. Wat je wel kan doen is overal op groeperen en het dan in je having af te vangen. Maar dan is het overzichtelijker om een CTE of derived table te gebruiken,

Acties:
  • 0 Henk 'm!

  • cariolive23
  • Registratie: Januari 2007
  • Laatst online: 18-10-2024
@DamadmOO: Je ziet iets over het hoofd, de kolommen die je in de subquery aanmaakt, kun je prima in de outerquery aanroepen in de WHERE. Het is dus niet nodig (ook niet mogelijk) om de functie ROW_NUMBER() aan te roepen, dat doe je in de subquery, de alias die je daarin aanmaakt, gebruik je weer in de outerquery. En dat werkt als een zonnetje!

Acties:
  • 0 Henk 'm!

  • DamadmOO
  • Registratie: Maart 2005
  • Laatst online: 23:07
SQL:
1
2
3
4
5
SELECT INV.ProductID
, ROW_NUMBER() OVER (PARTITION BY INV.GroupID ORDER BY INVP.OutPrice DESC ) AS [RowNumber]
FROM dbo.Inventory AS INV
JOIN dbo.InventoryPrice AS INVP ON INV.ProductID = INVP.ProductID
WHERE RowNumber <= 5

Geeft me toch echt een foutmelding:
code:
1
2
Msg 207, Level 16, State 1, Line 5
Invalid column name 'RowNumber'.

Acties:
  • 0 Henk 'm!

  • cariolive23
  • Registratie: Januari 2007
  • Laatst online: 18-10-2024
Dat klopt, je hebt er dan ook geen subquery van gemaakt:
cariolive23 schreef op vrijdag 15 januari 2010 @ 09:53:
Dat is volgens mij niet nodig, je kunt de query gewoon als subquery opnemen in een FROM en in de WHERE een vegelijking doen op het rownumber:

SELECT * FROM (hier jouw subquery met windowing) AS sub WHERE rownumber <= 5;
;)

SQL:
1
2
3
4
5
6
7
8
9
10
11
12
SELECT
  *
FROM
  (SELECT 
    INV.ProductID
  , ROW_NUMBER() OVER (PARTITION BY INV.GroupID ORDER BY INVP.OutPrice DESC ) AS RowNumber
  FROM 
    dbo.Inventory AS INV 
      JOIN dbo.InventoryPrice AS INVP ON INV.ProductID = INVP.ProductID
  ) AS sub
WHERE
  RowNumber <= 5;

[ Voor 28% gewijzigd door cariolive23 op 15-01-2010 15:23 . Reden: Subquery van gemaakt ]


Acties:
  • 0 Henk 'm!

  • DamadmOO
  • Registratie: Maart 2005
  • Laatst online: 23:07
cariolive23 schreef op vrijdag 15 januari 2010 @ 15:17:
Dat klopt, je hebt er dan ook geen subquery van gemaakt:

[...]

;)

SQL:
1
2
3
4
5
6
7
8
9
10
11
12
SELECT
  *
FROM
  (SELECT 
    INV.ProductID
  , ROW_NUMBER() OVER (PARTITION BY INV.GroupID ORDER BY INVP.OutPrice DESC ) AS RowNumber
  FROM 
    dbo.Inventory AS INV 
      JOIN dbo.InventoryPrice AS INVP ON INV.ProductID = INVP.ProductID
  ) AS sub
WHERE
  RowNumber <= 5;
Dat is geen subquery... En lees eens mijn quote waarop je reageerde.
DamadmOO schreef op vrijdag 15 januari 2010 @ 14:30:
[...]

Je kan in de where clause geen alias opgeven. En de ROW_NUMBER() OVER clause kan ook niet in de where. Wat je wel kan doen is overal op groeperen en het dan in je having af te vangen. Maar dan is het overzichtelijker om een CTE of derived table te gebruiken,
We hebben dus langs elkaar heen zitten praten ;)

Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

DamadmOO schreef op vrijdag 15 januari 2010 @ 18:56:
Dat is geen subquery... En lees eens mijn quote waarop je reageerde.
Als dat geen subquery is ben ik benieuwd wat het volgens jou wel is. Volgens mijn begrip van de term is het namelijk in principe elke query die binnen een andere query gebruikt wordt. En daarbij maakt het mij niet uit of het om een query in de SELECT-list, FROM-clause, of WHERE-clause gaat.
't Kan natuurlijk zijn dat je heel moeilijk allemaal losse definities voor de verschillende plekken wilt hanteren, maar gezien het reeds gegeven voorbeeld had je toch al moeten zien dat hij zijn eigen latere voorbeeld bedoelde ;)

Acties:
  • 0 Henk 'm!

  • cariolive23
  • Registratie: Januari 2007
  • Laatst online: 18-10-2024
Derived table, tja, dat is inderdaad wel een naampje die ze daar voor gebruiken en die ik even over het hoofd had gezien. Voor mij is het niet meer dan een subquery, het zal mij een zorg zijn waar de subquery staat, een sub is een sub en dat werkt prima!


:)
Pagina: 1