Avatar billede petruss Nybegynder
07. februar 2005 - 12:45 Der er 24 kommentarer og
2 løsninger

Udvælge poster, det er svært.

Jeg skal lave en forespørgsel hvor i der er 180 poster, af dem skal jeg starte med nr 3 og så skal den sammen med nr 3 trække hver 7 post ud opefter, hvad skriver jeg i kriterier for at få den til det?
På forhånd tak hvis nogen kan hjælpe.
Avatar billede flim Nybegynder
07. februar 2005 - 12:55 #1
select * from tabel where id-3 % 7 == 0...

Noget i den dur. HVordan det så skrives i SQL må du lige selv rode med :-)
Avatar billede petruss Nybegynder
07. februar 2005 - 15:48 #2
Da jeg spørger her i dette forum er det lig med at jeg ikke er nogen ørn til det.
Hvor skal jeg sætte det ind flim?
Avatar billede kjulius Novice
08. februar 2005 - 03:45 #3
Ja, se det er et af den slags ting som er meget svære at gøre i en SQL forespørgsel. Hvis du nu havde et ID felt som var sat som et autonummereringsfelt (typisk det primære felt) kunne det måske lade sig gøre ala noget lignende flim foreslår:

SELECT * FROM din_tabel
WHERE ID = 3 OR ((ID - 3) / 7) = INT((ID - 3) / 7)

Men!! - at bruge autonummereringsfelter på den måde er lidt farligt. Hvad hvis du nu sletter en row i tabellen. Så er der pludseligt et spring i numrene, som gør at SQL sætningen ikke længere virker efter hensigten.

Eller hvad hvis data i tabellen ikke ligger i den rigtige rækkefølge, men du ønsker at sortere de fysiske rows i en eller anden rækkefølge før udvælgelsen. Så vil det slet ikke virke.

SQL indeholder ikke som standard en ROW-counter funktion, man evt kunne have brugt - altså at hver selekteret row bliver tildelt et nummer.


Hmm.... alternativt kunne du selvfølgelig også oprette din egen lille row-counter funktion i VBA (i et modul):

Public Function ROWCOUNTER () As Long
Static Counter As Long
Counter = Counter + 1
ROWCOUNTER = Counter
End Function

Problemet med en sådan funktion er, at Access slet ikke er indrettet til den type funktioner - for at det skal kunne fungere, skulle Access have en eller anden event på enten StartForespørgsel eller SlutForespørgsel hvor man kunne nulstille tælleren igen.

Som det er nu vil en sådan tæller bare blive ved med at tælle opad, hver gang man læser en row - og fortsætte hvor den slap næste gang forespørgslen bliver kørt, lige til access bliver lukket ned. Det var jo ikke lige det vi ønskede - den skulle jo gerne starte fra 1 hver gang forespørgslen blev kørt. Som sagt understøtter Access ikke den slags, så derfor er vi nødt til manuelt (eller i hvert fald i VBA koden) at nulstille tælleren før hver kørsel. De begrænser jo de praktiske muligheder for at bruge en forespørgsel, som bruger den, noget. Tænk bare på, at man ikke bare kan bruge en .Requery på en forms timer-event. Det ville give helt forskellige resultater hver gang.

Hmm... tænke, tænke...

Der er måske alligevel en mulighed. Sagen er, at hvis man ikke angiver et databasefelt som parameter, vil funktionen kun blive kaldt én gang, mens den vil blive kaldt for hver row hvis man angiver et feltnavn som parameter. Ligeledes bliver funktioner i WHERE delen kaldt før funktioner i SELECT delen. Det kan vi udnytte. Hvis vi laver funktionen sådan, at tælleren nulstilles når der ikke medsendes en parameter, virker det faktisk (sådan da, det er stadig lidt primitivt):

Public Function ROWCOUNTER(Optional AnyField As Variant) As Long
Static Counter
If IsMissing(AnyField) Then
    Counter = 0
Else
    Counter = Counter + 1
End If
ROWCOUNTER = Counter
End Function

En forespørgsel som denne vil faktisk nu virke:

SELECT * FROM min_tabel
WHERE ROWCOUNTER()=0 AND ROWCOUNTER(etfeltnavn) = 6
ORDER BY etandetfeltnavn

Der er dog en hage ved det. ROWCOUNTER bliver kaldt hver gang en row bliver enten undersøgt (WHERE) eller returneret (SELECT). Det betyder desværre også, at vi er nødt til at lave en rutine for hver gang vi bruger tælleren. Hvis vi ønsker at medtage ROWCOUNTER i SELECT delen, kan vi ikke genbruge den samme funktion i WHERE delen. Vi er altså nødt til at definere to funtioner, f.eks. ROWCOUNTER1 og ROWCOUNTER2:

Public Function ROWCOUNTER1(Optional AnyField As Variant) As Long
Static Counter
If IsMissing(AnyField) Then
    Counter = 0
Else
    Counter = Counter + 1
End If
ROWCOUNTER1 = Counter
End Function

Public Function ROWCOUNTER2(Optional AnyField As Variant) As Long
Static Counter
If IsMissing(AnyField) Then
    Counter = 0
Else
    Counter = Counter + 1
End If
ROWCOUNTER2 = Counter
End Function

SQL delen vil herefter se sådan ud:

SELECT min_tabel.*, ROWCOUNTER1(etfeltnavn) FROM min_tabel
WHERE ROWCOUNTER1() = 0 AND ROWCOUNTER2() = 0 AND ROWCOUNTER2(etfeltnavn) = 6
ORDER BY etandetfeltnavn

Læg mærke til at vi så også er nødt til at sikre, at tællerne bliver nulstillet hver gang forespørgslen køres. Heldigvis kan man nøjes med at placere nulstillingen i WHERE delen.

Desværre giver det endnu en ting som måske er problematisk. ROWCOUNTER1 i SELECT delen tæller nu kun de rows med som faktisk bliver returneret. I ovenstående tilfælde vil den enlige row, som ROWCOUNTER2 funktionen i WHERE delen tildeler nr. 6 bliver altid tildelt nr. 1. Hvis man derfor troede, at man fik returnere nummereret fra den oprindelige row, så tja, bad luck.

Desværre så optimerer Access heller ikke kaldene til funktionen. Hvis man derfor angiver det samme udtryk flere gange i en WHERE sætning (med samme felt som parameter) bliver funktionen også kaldt flere gange. Det betyder, at man også her må ty til kald til identiske funktioner med forskellige navne.

Hvis vi nu backstepper lidt, og kigger på min oprindelige forespørgsel som skulle løse dit problem:

SELECT * FROM din_tabel
WHERE ID = 3 OR ((ID - 3) / 7) = INT((ID - 3) / 7)

og erstatter ID feltet med ROWCOUNTER funktionen, så skulle den jo nu se sådan ud:

SELECT * FROM din_tabel
WHERE ROWCOUNTER1() = 0 AND ROWCOUNTER2() = 0 AND ROWCOUNTER3() = 0 AND (ROWCOUNTER1(et_felt_fra_din_tabel) = 3
  OR ((ROWCOUNTER2(et_felt_fra_din_tabel) - 3) / 7) = INT((ROWCOUNTER3(et_felt_fra_din_tabel) - 3) / 7))
ORDER BY din_sortering

Med denne metode er du ikke længere afhængig af den fysiske placering af dine rows (eller med andre ord, dit autonummereringsfelt aka. ID feltet aka. den primære key).

Desværre betyder Access' manglende mulighed for at definere SQL funktioner med specielle egenskaber, at man må ty til disse primitive metoder, som at bruge identiske VBA-funktioner for hver gang man bruger dem i en SQL forespørgsel. I dette tilfælde, er man altså nødt til at oprette tre funktioner, ROWCOUNTER1, ROWCOUNTER2 og ROWCOUNTER3.

Metoden er brugbar, men kønt ser det ikke ud.


Jeg håber dog du kan bruge mit svar til noget...
Avatar billede petruss Nybegynder
08. februar 2005 - 04:46 #4
Respekt kjulius, du har da gjort en indsats ud over det man kunne forvente. Jeg kan følge dig i en del :-) men for at jeg skal få noget ud af det må du lige guide mig ind hvor jeg skal skive disse linier.
Når du skiver din_tabel går jeg ud fra jeg her skal skrive navnet på min tabel ikke?
Avatar billede petruss Nybegynder
08. februar 2005 - 05:33 #5
Ja undskyld filmen knækkede lige et øjeblik. Jeg går selvfølgelig ind i Sql visning og her taster jeg så det ind. Du skriver order by din sortering, der kan jeg ikke gætte mig til hvad der skal stå???
Når jeg sætter det andet ind kommer der følgende: Der er en ikke- defineret funktion ROWCOUNTER1 i udtrykket.
Skal der her stå noget i paranteserne?
Jeg skal lave en sortering hos vores medlemmer i vores andelsforening og som start ser det sådan ud:
SELECT Andelshavere.Andelsnr, Andelshavere.[Ejer navn], Andelshavere.[Ejer Adresse], Andelshavere.[Ejer Postnr], Andelshavere.[Ejer by], Andelshavere.[Andels Adresse], Andelshavere.Målernummer, Andelshavere.[Målernummer nyt]
FROM Andelshavere;
Det er andelsnr der skal sorteres så skal jeg så bare sætte din ind sådan:
SELECT Andelshavere.Andelsnr, Andelshavere.[Ejer navn], Andelshavere.[Ejer Adresse], Andelshavere.[Ejer Postnr], Andelshavere.[Ejer by], Andelshavere.[Andels Adresse], Andelshavere.Målernummer, Andelshavere.[Målernummer nyt]
FROM Andelshavere;
SELECT * FROM Andelshavere
WHERE ROWCOUNTER1() = 0 AND ROWCOUNTER2() = 0 AND ROWCOUNTER3() = 0 AND (ROWCOUNTER1(Andelsnr) = 3
  OR ((ROWCOUNTER2(Andelsnr) - 3) / 7) = INT((ROWCOUNTER3(Andelsnr) - 3) / 7))
ORDER BY din_sortering <<<<<<HVAD SKAL STÅ HER???
Eller har jeg ikke fattet en disse?
Avatar billede flim Nybegynder
08. februar 2005 - 08:43 #6
I ORDER BY.. skal der stå det felt du vil sortere efter..

ASC er stigende, DESC er faldende..

Dvs hvis du vil sortere atigende efter adelsnr skal der stå:

... ORDER BY andelsnr ASC
Avatar billede flim Nybegynder
08. februar 2005 - 08:44 #7
Og jeg kan ikke ramme tasterne.

atigende = stigende
adelsnr = andelsnr

:-)
Avatar billede petruss Nybegynder
08. februar 2005 - 10:45 #8
Så er det på plads Flim, tak.
Men ligemeget om jeg sætter kun det ind som kjulius skriver eller tilføjer det efter det der står i forvejen får jeg fejl.
Kan i ud fra det jeg tidlige skrev finde ud af hvad der er galt?
Avatar billede kjulius Novice
08. februar 2005 - 13:41 #9
Du har husket at oprette de tre funktioner i et modul, ikke?

Hvis ikke, så er proceduren sådan her:

(helt fra bunden, håber ikke du tager anstød af det :-)

I listen over objekter (Tabeller, Forespørgsler, Formularer osv.) er der nederst et punkt, der hedder Moduler. Klik på det. Hvis du i forvejen har et modul oprettet i dit projekt, vil det nu stå ude til højre. I så fald dobbeltklikker du bare på det. Hvis du endnu ikke har oprettet et modul, så klik nu på "Ny" oppe for oven.

Du er nu inde i VBA koden for modulet du enten vil redigere i eller lige har oprettet. Det er her du skal indføje koden til de tre tællerutiner:

Public Function ROWCOUNTER1(Optional AnyField As Variant) As Long
Static Counter
If IsMissing(AnyField) Then
    Counter = 0
Else
    Counter = Counter + 1
End If
ROWCOUNTER1 = Counter
End Function

Public Function ROWCOUNTER2(Optional AnyField As Variant) As Long
Static Counter
If IsMissing(AnyField) Then
    Counter = 0
Else
    Counter = Counter + 1
End If
ROWCOUNTER2 = Counter
End Function

Public Function ROWCOUNTER3(Optional AnyField As Variant) As Long
Static Counter
If IsMissing(AnyField) Then
    Counter = 0
Else
    Counter = Counter + 1
End If
ROWCOUNTER3 = Counter
End Function

Klik nu på Save (diskette-ikonet) eller Ctrl-S eller via menuen File, Save db
Hvis du før oprettede et nyt modul, vil du blive spurgt om navnet (default er Modul1).

Er der stadig problemer?
Avatar billede kjulius Novice
08. februar 2005 - 14:27 #10
Desværre har jeg lige fået min værste anelse bekræftet (var vist lidt træt, da jeg "opfandt" løsningen). Selektion i ORDER BY orden virker ikke. :-(

Access optimerer naturligt nok (indser jeg nu), sådan at sorteringen først sker EFTER at de valgte rows er indkredset. Når jeg derfor gik ud fra, at man kunne tildele hver row et nummer iht. sorteringsordenen før selektionen i WHERE sektionen, er det forkert.

Er løsningen så i det hele taget bedre end den oprindelige med at bruge et autonummereringsfelt? Ikke meget! Den er stadig lidt bedre - i hvert fald til endnu en af mine forudsætninger falder fra hinanden :-(.

Renummereringen betyder, at det er lige meget, om der er blevet slettet rows i tabellen, og der således er "huller" i rækkefølgen som tildelt i autonummereringsfeltet.
Avatar billede petruss Nybegynder
08. februar 2005 - 18:12 #11
Det køre nu kjulius og er brugbart uden du behøver flere skuffelser.
Det er fint du tager det fra bunden så er det nemmere at følge med.
Den har en post med som ikke skal være der(nr4) ellers er de alle der, kan du få den væk?
Avatar billede kjulius Novice
08. februar 2005 - 22:47 #12
Faktisk tror jeg ved nærmere eftertanke, at hele forespørgslen kan cuttes ned til:

SELECT * FROM din_tabel
WHERE ROWCOUNTER1() = 0 AND ROWCOUNTER2() = 0 AND (((ROWCOUNTER1(et_felt_fra_din_tabel) - 3) / 7) = INT((ROWCOUNTER2(et_felt_fra_din_tabel) - 3) / 7))
Avatar billede petruss Nybegynder
09. februar 2005 - 07:41 #13
Du har helt ret kjulius og nu er den der, fantastisk :-)
Jeg har dog lige opdaget at jeg skal stoppe efter nr 94, kan du lægge en linie ind så post 94 er den sidste, hvis ikke er det ok da jeg så bare klare det under udskrivningen.
Du skal have mange tak for hjælpen (også til dig Flim for at deltage) jeg havde aldrig selv fundet ud af det. Nu skal jeg så bare finde ud af det med point, hvorfor får jeg ikke begge jeres navne op så jeg kan dele???
Avatar billede mugs Novice
09. februar 2005 - 07:44 #14
flim har kun lagt kommentarer, så der er ikke noget svar at acceptere.
Avatar billede petruss Nybegynder
09. februar 2005 - 08:29 #15
Selvfølgeliiig mugs, det første man bliver blind på er øjnene :-)
Så Flim kom til truget hvis du vil have point
Avatar billede flim Nybegynder
09. februar 2005 - 17:01 #16
Kommer her :-)
Avatar billede kjulius Novice
09. februar 2005 - 18:47 #17
Okay, så er vi vist tilbage til at skulle bruge 3 tællere igen:

SELECT * FROM din_tabel
WHERE ROWCOUNTER1() = 0 AND ROWCOUNTER2() = 0 AND ROWCOUNTER3() = 0 AND ROWCOUNTER1(et_felt_fra_din_tabel) <= 94 AND (((ROWCOUNTER2(et_felt_fra_din_tabel) - 3) / 7) = INT((ROWCOUNTER3(et_felt_fra_din_tabel) - 3) / 7))
Avatar billede kjulius Novice
09. februar 2005 - 19:36 #18
Access gør det altså pokkers svært at bruge sådanne tællere, fordi den er så ukonsekvent mht. optimeringen af sine parametre:

Funktioner uden databasefelter genkender databasemotoren godt nok som funktioner som kun kan returnere én værdi. Derfor kaldes den kun én gang i hver sektion (SELECT og WHERE). Fint.

Men hvorfor erkender den så ikke, at en funktion med det samme databasefelt iflg. de samme regler vil returnere den samme værdi (pr. row). Access burde iflg. den samme logik kun kalde funktionen én gang pr. row. Men det gør den ikke. Det gør, at man er tvunget til at bruge flere tællere til hver enkelt delkondition.

Til gengæld optimerer access logikken i en selektion, sådan at hvis et element i WHERE delen ikke er opfyldt, så afbryder den valideringen af resten af elementerne.

Alt dette betyder, at placeringen af elementerne i WHERE delen også har betydning, og der vil utvivlsomt være situationer, hvor det slet ikke vil være muligt at bruge tællere, sådan som jeg har gjort i denne lille "opgave".
Avatar billede petruss Nybegynder
10. februar 2005 - 09:56 #19
Mht. det sidste har du sikkert helt ret kjulius :-)
I hvertfald virker det ikke, den tager alle posterne med så du har ret i at man utvivltsomt komme i situationer hvor det ikke vil virke :-)
Avatar billede kjulius Novice
10. februar 2005 - 18:34 #20
petruss: Du har skrevet det nøjagtigt sådan som jeg gjorde ikke, altså placeret den tæller som skulle stoppe selektionen ved de 94 rows først i WHERE sætningen, ikke?

For hvis du ikke gør det, vil den tæller kun blive opdateret hver gang de andre konditioner er opfyldt, dvs. stoppet bliver først udløst ved 94 * 7 poster, hvilket jo er langt mere end dine 180 poster.

Det er netop det, der er så frustrerende ved Access' måde at optimere sine forespørgsler på.

Hvis du derimod har skrevet det nøjagtigt, som jeg gjorde, så #¤¤%%!!%, som sergeanten siger i Basserne.
Avatar billede petruss Nybegynder
10. februar 2005 - 20:25 #21
Jeg har simpelhen kopieret det her fra siden (dit svar kl 18.47.58) og så sat det ind i access. Hvis der er en fejl idet, så er den kommet med og jeg har ikke hjernen til at se om der er nogen, jeg stoler helt på dig :-)
Avatar billede kjulius Novice
10. februar 2005 - 23:27 #22
Okay, beklager min manglende afprøvning, men efter lidt debugging, viser det sig, at rækkefølgen ganske rigtigt har betydning, men modsat min formodning, så ser det ud til at testen sker fra højre mod venstre. Så derfor:

SELECT * FROM din_tabel
WHERE ROWCOUNTER1() = 0 AND ROWCOUNTER2() = 0 AND ROWCOUNTER3() = 0 AND (((ROWCOUNTER1(et_felt_fra_din_tabel) - 3) / 7) = INT((ROWCOUNTER2(et_felt_fra_din_tabel) - 3) / 7) AND (ROWCOUNTER3(et_felt_fra_din_tabel) <= 94));
Avatar billede petruss Nybegynder
11. februar 2005 - 07:32 #23
Ja så er den lige i øjet og jeg er meget taknemmelig for din store hjælp.
Avatar billede petruss Nybegynder
11. februar 2005 - 07:36 #24
Jeg forstår ikke hvad der sker med den point givning, skal man acceptere inden man kan dele?
Avatar billede petruss Nybegynder
11. februar 2005 - 07:40 #25
Nå nu lykkedes det vist :-)
Jeg har et spørgsmål mere til dig kjulius og det er meget sværer end det her (kan sikkert ikke lade sig gøre) og ville også gerne belønne dig med flere point for dit svar her.
For at du kan få dem skal jeg vel oprette et nyt spørgsmål eller kan vi bare fortsætte her?
Avatar billede kjulius Novice
11. februar 2005 - 11:50 #26
Tak, det var da dejligt! :-)

Af hensyn til sitets formål, hvor man skal kunne søge efter gennemprøvede løsninger, så er det nok bedst om du opretter et nyt spørgsmål, hvis topic er et andet end det der blev behandlet her.
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
Dyk ned i databasernes verden på et af vores praksisnære Access-kurser

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