Avatar billede Slettet bruger
13. februar 2006 - 03:09 Der er 23 kommentarer og
1 løsning

Gruppere og sortere efter datoer

Hejsa,

Jeg har en tabel med nogle informationer om en bruger.
Her i blandt tidspunktet for brugerens besøg på hjemmesiden (Dato).

Jeg har forsøgt med følgende sætning som virker, men jeg kan ikke få lov at sortere efter datoen:

command.CommandText = "SELECT convert(nvarchar(10), Dato, 105) AS Dato,";
            command.CommandText += " COUNT(convert(nvarchar(10), Dato, 105)) AS AntalPerDag";
            command.CommandText += " FROM Statistik GROUP BY convert(nvarchar(10), Dato, 105) ORDER BY convert(nvarchar(10), Dato, 105) DESC";

Jeg har lavet nøjagtigt det samme med MySql:

command.CommandText = "SELECT DATE_FORMAT(Dato,'%Y %m %d') AS Dato, COUNT(Dato) FROM UserInfo AS AntalPerDag GROUP BY Dato ORDER BY Dato DESC";

Sidste sætning er rimelig enkel i forhold til den første og den virker med sorteringen.
Kan det virkelig være rigtigt at Microsoft halter efter her?
Avatar billede tjp Mester
13. februar 2006 - 04:20 #1
Bare et skud, men hvad sker der, hvis sidste linje hedder:
" FROM Statistik GROUP BY Dato ORDER BY Dato DESC";
Avatar billede Slettet bruger
13. februar 2006 - 08:50 #2
command.CommandText = "SELECT convert(nvarchar(10), Dato, 105) AS Dato,";
            command.CommandText += " COUNT(convert(nvarchar(10), Dato, 105)) AS AntalPerDag";
            command.CommandText += " FROM Statistik GROUP BY convert(nvarchar(10), Dato, 105) ORDER BY Dato DESC";

Det gør ingen forskel. Den tager vidst ikke ORDER BY sætningen med.
Avatar billede ldanielsen Nybegynder
13. februar 2006 - 10:07 #3
Det er faktisk lidt sjovt det her:

1: Du beder om at få sorteret efter  convert(nvarchar(10), Dato, 105), et udtryk der returnerer fx "13-02-2006". Med den sortering er det klart at "24-12-1947" kommer EFTER "23-10-2003", og det var ikke meningen, vel?

Det rigtige er altså at sortere efter Dato; ORDER BY Dato DESC, så får du den nyeste først.

MEN !!!!!!!1

2: I tabellen har du et felt der hedder Dato, det er datetime eller smalldatetime. Desuden har du i sætningen et felt du giver alias'et Dato (convert(nvarchar(10), Dato, 105) AS Dato). Nu er der altså TO ting i sætningen der hedder Dato, og når du så beder om at få sorteret, hvilken bestemmer MSSQL sig så for at bruge? Svar: Det felt du selv har navngivet Dato. Derfor får du samme resultat, det forkerte.

Løsningen er at undgå at bruge et eksisterende feltnavn so alias, jeg foreslår:

command.CommandText = "SELECT convert(nvarchar(10), Dato, 105) AS strDato,";
            command.CommandText += " COUNT(convert(nvarchar(10), Dato, 105)) AS AntalPerDag";
            command.CommandText += " FROM Statistik GROUP BY convert(nvarchar(10), Dato, 105) ORDER BY Dato DESC";
Avatar billede Slettet bruger
13. februar 2006 - 10:36 #4
Jeg får så fejlen:

Column "Statistik.Dato" is invalid in the ORDER BY clause because it is not contained in either an aggregate function or the GROUP BY clause.
Avatar billede Slettet bruger
13. februar 2006 - 10:37 #5
Tror forøvrigt jeg har prøvet noget lignende før, hvor jeg kaldte den Dato1 og serterede efter den i stedet for Dato.
Men nogenlunde samme resultat som det jeg fik med din statement :-(
Avatar billede Slettet bruger
13. februar 2006 - 10:42 #6
Kan man ikke bruge SUBSTRING på et datetime felt?
Det ville jo gøre det en hel del nemmere.

Har forsøgt, men fik en fejl så måske kan det ikke lade sig gøre, eller også var jeg for klumret :-)
Avatar billede Slettet bruger
13. februar 2006 - 10:52 #7
Det her virker:

command.CommandText = "SELECT LEFT(Dato, 11),";
            command.CommandText += " COUNT(LEFT(Dato, 11)) AS AntalPerDag";
            command.CommandText += " FROM Statistik GROUP BY LEFT(Dato, 11) ORDER BY LEFT(Dato, 11) DESC";

MEN, MEN, MEN...
Der sorteres desværre stadig ikke efter dato, men månederne er da i det mindste grupperet og dagene er talt op:

Column1    AntalPerDag
Oct 31 2005    3
Oct 29 2005    1
Oct 27 2005    1
Oct 26 2005    4
Oct 25 2005    1
Oct 24 2005    1
Oct 23 2005    1
Oct 22 2005    2
Oct 20 2005    1
Oct 18 2005    2
Oct 17 2005    2
Oct 15 2005    5
Oct 14 2005    4
Oct 13 2005    5
Oct 12 2005    5
Oct 11 2005    3
Oct 10 2005    3
Oct 9 2005    7
Oct 8 2005    5
Oct 7 2005    5
Oct 6 2005    3
Nov 29 2005    2
Nov 27 2005    1
Nov 24 2005    3
Nov 23 2005    2
Nov 22 2005    1
Nov 21 2005    4
Nov 20 2005    7
Nov 18 2005    1
Nov 17 2005    5
Nov 16 2005    1
Nov 15 2005    1
Nov 12 2005    1
Nov 11 2005    1
Nov 8 2005    2
Nov 3 2005    1
Jan 20 2006    2
Jan 19 2006    6
Jan 18 2006    2
Jan 17 2006    1
Jan 16 2006    2
Jan 14 2006    1
Jan 13 2006    1
Jan 12 2006    4
Jan 11 2006    3
Jan 10 2006    2
Jan 4 2006    1
Jan 3 2006    1
Feb 12 2006    71
Dec 30 2005    3
Dec 29 2005    2
Dec 24 2005    2
Dec 19 2005    1
Dec 18 2005    1
Dec 17 2005    3
Dec 16 2005    1
Dec 15 2005    2
Dec 14 2005    1
Dec 13 2005    2
Dec 11 2005    1
Dec 8 2005    2
Dec 6 2005    1
Dec 3 2005    1
Dec 2 2005    4
Avatar billede ldanielsen Nybegynder
13. februar 2006 - 11:03 #8
Du skal jo inkludere feltet i din GROUP BY, men så bliver det forkert, for alle datoerne er jo forskellige, fordi de har sekunder/millisekunder med Du vil have et tal for hver dag, ikke?

Så kan du bruge DATEDIFF:


SELECT convert(nvarchar(10), Max(Dato), 105) AS strDato,
  COUNT(Dato)) AS AntalPerDag
FROM Statistik
  GROUP BY DATEDIFF(d, Dato, GETDATE())
  ORDER BY DATEDIFF(d, Dato, GETDATE()) DESC 

Jeg grupperer altså efter "forskellen i hele dage mellem Dato og datoen i dag"; DATEDIFF(d, Dato, GETDATE())

Derfor kan jeg også sortere efter dettte.

Så kræver det et lille trick at SELECT'e Dato og der bruger jeg MAX. Der er jo en masse datoer i hver gruppe, men de er alle samme dato, blot (muligvis) forskellige klokkeslæt. Når de er fra samme dato er det ligegyldigt hvilken jeg viser, så jeg tager bare den største (hvilket vil sige den sidste). Jeg kunne lige så godt have taget den mindste, med MIN

Man kan jo kun selecte felter der includerede i GROUP BY eller som selectes med en aggregate function.
Avatar billede Slettet bruger
13. februar 2006 - 11:23 #9
Det virker fint :-)
command.CommandText = "SELECT convert(nvarchar(10), Max(Dato), 105) AS strDato,";
            command.CommandText += " COUNT(Dato) AS AntalPerDag FROM Statistik";
            command.CommandText += " GROUP BY DATEDIFF(d, Dato, GETDATE())";
            command.CommandText += " ORDER BY DATEDIFF(d, Dato, GETDATE()) ASC";

Dog undrer det mig at jeg skal sortere ASC for at få den nyeste gruppe øverst. Hvorfor er det ikke DESC?

Det er også fint nok at det virker på denne måde, men kan det ikke laves mere enkelt (gennemskueligt), fx. med LEFT eller SUBSTRING?
Jeg har lidt svært ved at gennemskue 100% hvad det er der bliver gjort og vil gerne have en "nemmere" metode da jeg skal lave det med andre data i tabellen også.

Tak! Og på forhånd tak :-)
Avatar billede tjp Mester
13. februar 2006 - 12:14 #10
>> "Dog undrer det mig at jeg skal sortere ASC for at få den nyeste gruppe øverst. Hvorfor er det ikke DESC?"
Det er fordi der ordnes på forskellen mellem dagen i Dato og nu -> jo nyere dato, jo mindre forskel.
Avatar billede Slettet bruger
13. februar 2006 - 12:25 #11
Ok. Det gir' mening.

ldanielsen, du skal jo have dine point :-) Du gav mig svaret på hvordan jeg kunne sortere og gruppere samtidig.

Med relation til mit sidste spørgsmål til dig. Er det helt umuligt at lave det med LEFT eller SUBSTRING?
Det vil i mine øjne lette mig for en del arbejde...
Avatar billede ldanielsen Nybegynder
13. februar 2006 - 13:03 #12
SURT

Jeg har lige skrevet en LAAAANGT indlæg om hvordan logikken er bag denne sætning, og så fumler jeg, og det er væk!!

Essensen er at du ikke skal bruge STRING funktioner på datoer, for det er for tungt og upraktisk. Du skal hellere blive fortrolig med de få datofunktioner der findes; DATEPART, DATEADD og DATEDIFF. Med de tre kan du lave alt det du kan ønske dig.

Ang. gruppering skal man vænne sig til at bruge de forskellige Aggregate Functions der er, mest bruges: COUNT, SUM, MAX, MIN og AVG. Det er funktioner der kan samle et sæt af data og returnere én værdi:

COUNT - Antal værdier i sættet
SUM - Summen af sættets værdier
MAX - den højeste værdi i sættet
MIN - den mindste værdi i sættet
AVG - gennemsnittet af værdier i sættet
Avatar billede Slettet bruger
13. februar 2006 - 14:03 #13
Tak for hjælpen :-)
Avatar billede jnd Nybegynder
13. februar 2006 - 20:01 #14
Du burde nok overveje at omstrukturere din tabelstruktur. At konvertere data på den måde tvinger du serveren til at lave et table scan hver gang.
Avatar billede ldanielsen Nybegynder
13. februar 2006 - 20:15 #15
jnd > Hvad mener du?
Avatar billede jnd Nybegynder
13. februar 2006 - 21:28 #16
Hvis du konverterer data med eksempelvis CAST eller CONVERT (eller en skalar funktion som DATEPART) så arbejder serveren på output data fra dise, og ikke tabellen, og demed heller ikke de index der måtte være, eksempelvis:

SELECT CAST(col1 AS VARCHAR(10))
FROM tab1
GROUP BY CAST(col1 AS VARCHAR(10))

Hvis du nu havde et index defineret på col1 kan dette ikke bruges, da du jo ikke arbejder på de data der er i indexet, derfor vil dit query bruge et table scan i stedet. Man bør derfor overveje om ikke det var bedre at omstrukturere sine tabeller så de svarer bedre til de data man har brug for.

Befinder du dig på SQL server 2005, er der en datatype der "kun" indeholder dato eller tids komponenten i forvejen. På SQL server 2000, må du selv arbejde lave det ved at bruge en int til tidskomponenten og en int til dato komponenten (eller smallint hvis du ikke har brug for en særlig høj opløsning)
Avatar billede Slettet bruger
13. februar 2006 - 21:46 #17
"Befinder du dig på SQL server 2005, er der en datatype der "kun" indeholder dato eller tids komponenten i forvejen."

Ja, den er også blevet overvejet, men da statistikken skal verificere om en ip har været talt op indenfor de sidste 20 minutter er det jo udelukket.
Men ellers har du da fuldstændig ret Ind!
Avatar billede ldanielsen Nybegynder
14. februar 2006 - 09:25 #18
jnd > Jeg har testet lidt på det du siger, og jeg må sige, at hvis jeg indexerer mine datofelter, så kan jeg ikke få den til at lave en table scan, uanset om jeg bruger CONVERT eller DATEDIFF, om jeg sorterer eller grupperer eller hvad. Den arbejder udelukkende på index'et, som jeg også forventede det.

Jeg tror du tager fejl.
Avatar billede Slettet bruger
14. februar 2006 - 09:36 #19
ldanielsen, bare sådan af ren og skær interesse (er selv lige startet med at bruger sql express edition). Hvordan laver du sådanne tests?
Avatar billede ldanielsen Nybegynder
14. februar 2006 - 10:11 #20
Jeg ved ikke hvilke redskaber du har med den udgave, men jeg bruger Query Analyzer.

Skriv din query i Query Analyzer, og vælg "Show Execution Plan" (Enten i Execute Mode dropdown'en eller i menu'en Query)

Kør Query'en, og skift fanen nederst i result-vinduet til "Execution Plan".

Jeg har ikke frygtelig meget forstand på at tyde dem, men alligevel kan det være med til at finde "flaskehalsene" i en query, og en Table Scan kan man da altid få øje på :o)
Avatar billede Slettet bruger
14. februar 2006 - 14:59 #21
Ok. Tak
Avatar billede jnd Nybegynder
19. februar 2006 - 23:53 #22
Beklager det lidt sene svar. Men ldanielsen, du må læse din execution plan forkert. Det kan ganske enkelt ikke lade sig gøre.

Hvis du kører convert på en søjle i tabellen arbejder du udelukkende på resultatet, der genereres INTET metadata der fortæller hvor stammer fra, derfor kan der på ingen måde anvendes et index. Convert skifter format på data, det er ikke blot et "filter" der præsenterer data på en ny måde.

Jeg har ikke lige adgang til en af mine servere nu, så koden skal måske lige have rettet et par syntax fejl, men prøv følgende eksempel. Af de to select statements i bunden vil kun det ene anvente indexet på den primære nøgle.

CREATE TABLE #T( col1 int PRIMARY KEY CLUSTERED, col2 char(100) )

DECLARE @i int
SELECT @i = 0

WHILE @i > 1000000
BEGIN

INSERT INTO #T(col1,col2)
VAULE (@i,'some string')

SELECT @i = @i + 1

END

SELECT col1,col2
FROM #T
WHERE col1 = 500000

SELECT col1,col2
FROM #T
WHERE CAST(@col1 AS VARCHAR(10)) = '500000'
Avatar billede ldanielsen Nybegynder
20. februar 2006 - 09:41 #23
Nu skal vi måske ikke bruge thomasso's spørgsmål til denne diskussion, men jeg vil faktisk gerne vide hvad det er du mener.

Jeg har omskrevet dit eks, rettet et par fejl, og laver en fysisk tabel i stedet for din temp tabel.

Og så laver den ikke noget table scan, men et index scan, hvis jeg bruger CAST, men blot et indes seek hvis jeg ikke gør. Jeg bestrider ikke at det går ud over performance, jeg plæderer netop for ikke at bruge CONVERT/CAST i group by, order by og where clauses. Jeg anbefaler i dette tilfælde en DATEDIFF eller DATEPART, uden at vide det bestemt tror jeg det må være en fordel.

Men det er forkert at indexer ignoreres i disse tilfælde. Laver din server et Table Scan??:


CREATE TABLE T( col1 int PRIMARY KEY CLUSTERED, col2 char(100) )

DECLARE @i int
SELECT @i = 1

WHILE @i <= 1000000
BEGIN

INSERT INTO T(col1,col2)
VALUES (@i, 'some string')

SELECT @i = @i + 1

END

SELECT col1,col2
FROM T
WHERE col1 = 500000

SELECT col1,col2
FROM T
WHERE CAST(col1 AS VARCHAR(10)) = '500000'
Avatar billede jnd Nybegynder
24. februar 2006 - 23:25 #24
Ahh, jeg tror jeg ved hvad problemet er.

Du ser et Index scan i din query analyse. I dette tilfælde er det faktisk det samme som et table scan (da det er et clustered index). Et index scan anvender ikke indexet på en hensigtsmæssig måde.

Læg mærke til at der i execution planen for :

SELECT col1,col2
FROM T
WHERE col1 = 500000

Står index SEEK, og ikke scan. Omvendt står der Index scan for:

SELECT col1,col2
FROM T
WHERE CAST(col1 AS VARCHAR(10)) = '500000'

Forskellen ligger i at man i et index scan skal læser hele leaf niveauet igennem, mens et index seek laver et opslag i indexet. Det kan godt være at det lyder som en akademisk forskel, men query 2 skal faktisk lave ca 60 gange mere arbejde end query 1
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Computerworld tilbyder specialiserede kurser i database-management

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester