27. marts 2004 - 00:32Der er
19 kommentarer og 1 løsning
Find bestemte ord
Jeg vil gerne bede om hjælp til et problem. Jeg har en database med ADO. dayabasen hedder meninger. I den er der tabel en id(autonummer) en meningerom (tekst) I den tabel meningerom er der meget tekst, nu vil jeg gerne kunne finde alle ord der fx hedder politik i ALLE rækkerne. Dvs ikke kun det først ord den finder med politik med alle rækker igennem hele databasen i tabellen meningerom der indeholder ordet politik. Kan det lade sig gøre med ADO ? Jeg er ikke så god til Query så hvis den skal bruges bedes i skrive det tydeligt. Det ville være mega fint om jeg fx kunne skrive ordet politik en en tekst1.txt box og så fx klikke på en knap hvorefter alle dem den finder bliver sat ind i en listbox eller sådan noget. Har nogen mod på det ?
Jeg ville foretrække SQL da filtre normalt er ineffektive og kan give forvirringer. En Query ved man hvad er.
select id,tekst from meninger where tekst like "'*politik*"
Hvis du skulle gøre brug af TParameters mener jeg at sætningen ser sådan ud:
select id,tekst from meninger where tekst like :Pattern
Så ender det med noget med i retning af (med et TListView i ReportMode m. to kolonner defineret):
procedure SearchTekster(aListView : TTListView; aPattern : string); begin try // Burde ikke sættes her idet den er statisk, kun for at illustrere Query1.Sql := 'select id,tekst from meninger where tekst like :Pattern';
// Antager at aPattern indeholder 'politik' - der søges afhængigt af store og // små bogstaver. Ved ikke hvordan man rammer "POLitik" og lignende - det kan // betyde at du skal gemme strengen i "uppercase" og konvertere tilbage når du // viser strengen.
aListView.Items.BeginUpdate; try aListView.Items.Clear; while not Query1.EOF do begin with aListView.Items.Add do begin Caption := IntToStr(Query1.FieldByName('Id').AsInteger); SubItems.Add(Query1.FieldByName('Tekster').AsString); end; // with Query1.Next; end; // while finally aListView.Items.EndUpdate; end; // try finally Query1.Close; // Sikrer at Queryen er lukket igen. end; // try end;
Pyh hcr, du er godt nok langt fremme. Den ser meget fin ud.. men du må give lidt til til en "Begynder?" til at forstå.. aPatten er det tekst1.tekst ? De der end // try hvad betyder de ? procedure SearchTekster er det en proceduer du bare har lavet ? Query1 er det ADOQuery og virker de ned ? aListView er det listbox1 ? Hvor kaldes proceduren og hvordan ? I ADOQuery sættes teksten ind hvor ? Hvad for en tekst sættes ind eller SQL kommeando sættes ind ?
Ja, ja. Det vil naturligvis give en masse fejl. Jeg ved ikke hvad du har testet det opimod.
1. aPattern er den parameter som man sender med til proceduren: SearchTekster(aListView : TTListView; aPattern : string); Det er igen en defacto standard at parametre har et 'a' foran så man altid ved at det er noget der "kommer udenfor" proceduren. På samme måde plejer man at sætte 'f' foran variable der står i private-delen på klasser.
Nå men, SearchTekster skal have to parametre: et TListView er den komponent du ser, hvis du klikker på "Min computer". Der kan du skifte mellem details, ikoner. Meningen er, at resultaterne skal proppes deri. aPattern er søgeteksten.
2. Query1 er en TADOQuery (Access) eller en TQuery (mod BDE/Paradox-tabeller). Parameteren :Pattern er lidt speciel. En TQuery-variant har noget der hedder parametre og der kan du indsætte værdier. I dette tilfælde er der kun een og den hedder Pattern. Når SQL-selecten køres så indsættes værdien af Pattern dér i teksten hvor :Pattern står.
3. Min kæphest: Try-finally og try-except. En genial konstruktion som forbedrer kodesikkerheden væsentligt
3a. Try-Finally: En smart function der sikrer at en kodestump afvikles uanset hvordan operationen forløb
var StringList : TStringList; begin Screen.Cursor := crHourglass; // På med timeglasset StringList := TStringList.Create; // Opret en liste instans try // Gør et eller andet med strenglisten der tager lang tid og som måske kan gå ned. finally StringList.Free; // Frigiv den igen Screen.Cursor := crDefault; // Sæt cursor tilbage til normal end; end;
Her sætte jeg cursoren til timeglasset inden en tung operation. Ved at have linien "Screen.Cursor := crDefault;" inde i finally delen kan jeg være 100% sikker på, at den vil blive sat tilbage igen - også selvom min lange operation eventuelt fejler (og raiser en exeption). Det er det samme med StringListen som er sikker på at blive frigivet fra hukommelsen.
3b. Try-except: Hvis noget går galt, så ender man her.
Samme konstruktion men hvis noget fejler i Delphi, så kastes en exception. Du kan tænke på det som en slags "lad-mig-komme-ud-herfra"/goto-tingest. Dit Delphiprogram har sådan en try-except som fanger de fejl der må komme og den sørger for at vise en fejlmeddelelse på skærmen. Det kan bare godt være at man gerne selv vil prøve at reparere fejlen.
function StrToIntSafe(aString : string); begin try result := StrToInt(aString); except result := 0; end; end;
Hvis aString er tom eller indeholder tegn og ikke tal, så sker der en exception hvor den kører StrToInt. Programmet springer ned til except-delen og afvikler den kode der står der. I dette tilfælde sætter den returneringsresultetet til 0. Alle andre gange springer programmet over denne kodestump.
Der er noget mere i konceptet, men hvis du forstår min omfangsrige tekst, så må du indrømme at det er da smart.
Endelig kan man kombinere det:
begin Screen.Cursor := crHourglass; try DataSet.BeginTransaction; try Query.Open; // Gør et eller andet; DataSet.EndTransaction; except DataSet.RollbackTransaction; raise; end; finally Screen.Cursor := crDefault; end; end;
Begin-, End- og rollbackTransaction er en databasefunktion der sikrer at enten er data skrevet til tabellen eller så er den ikke. Hvis det er nyt for dig, så stol på at det også er smart. Hvis operationen gik galt, så ruller den operationen tilbage og raiser i øvrigt exceptionen igen. Udenfor sidder en garanti for at cursoren er normal når proceduren forlades. Pyha. Det var meget tekst.
Grunden til jeg næsten altid skriver "end; // try" eller "end; // for" skyldes at hvis man har mange af den slags inden i hinanden, så mister man let overblikket. Det er bare en forklarende kommentar som jeg sætter efter "end;".
4. Ja. Det var bare en procedure jeg lige griflede ned.
5. Man kan godt sige at TListView er en smartere variant af TListBox - men det er en tilsnigelse og det vil ikke virke om du prøvede med en TListBox.
5. Jeg ville placere koden i en ButtonClick event:
procedure TfrmMain.Button1Click(Sender : TObejct); begin ListView1.Items.BeginUpdate; try // Her starter endnu en try SearchTekster(ListView1,Edit1.Text); finally ListView1.Items.EndUpdate; end; // Slut på Try-finally end;
6. Når man åbner en Query, så afvikles SQL-scriptet. Her henter den alle de linier der indeholder aPattern. Derefter gennemløbes det fundne og placeres i listviewet.
while not Query1.EOF do begin with aListView.Items.Add do begin // Indsæt linie i TListView Caption := IntToStr(Query1.FieldByName('Id').AsInteger)); // i liniens caption sæt ID SubItems.Add(Query1.FieldByName('Tekster').AsString); // placér tekster ved siden af. end; // with Query1.Next; // Ryk en linie frem i Query'en. end; // while
Mange tak. Jeg har fundet på en selv i mellemtiden.. Den er nogenlunde (jeg vil helt klart prøve din! ) Den som jeg har lavet gentager sig selv og det er noget skidt.. Du kan se den her :
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if datamodul.DataModule2.ADOTable1.Locate('Bruges',Edit1.text,[loCaseInsensitive,loPartialKey]) then
if edit1.text<>'' then if datamodul.DataModule2.adotable1.Locate('Bruges',edit1.text,[Lopartialkey]) then listbox1.Items.Add(datamodul.DataModule2.ADOTable1.FieldByName('Bruges').AsString); datamodul.DataModule2.ADOTable1.Next;
Jeg beklager men der er alt for meget "smartSprog" i den til at jeg forstår det. Jeg har siddet mange , mange timer med den nu..
ADOQuery1.Sql := 'select nummer,tekst from kode where tekst like :Pattern'; Giver denne fejl : [Error] findkodemodul.pas(86): Incompatible types: 'TStrings' and 'String'
Giver den her fejl : [Error] findkodemodul.pas(87): Undeclared identifier: 'AsString'
Ingen af dem ved hvad patten er for noget. Heller ikke jeg.. Skal den ikke have en
VAR
Det ville hjælpe meget hvis du ikke lavde det om til ditten og datten men bare kaldte det , det som det hedder. At fx ListView1: TListView; Ikke skal hedder : aListView : TTListView Det tager timer at finde ud af hvorfor den fejler.. Desuden vil det aldrig komme til at hedde TTListView Men højst TListView
Jeg kan ikke få det til at virke og jeg kan ikke gennemskue den..
Koden12: Det var aldrig tiltænkt at mit eksempel var køreklart, men lad os tage det nogenlunde i den rækkefælge som du melder dem:
ADOQuery1.Sql := 'select nummer,tekst from kode where tekst like :Pattern;';
Her er tale om en lille sjuskfejl fra min side (og jeg beklager). ADOQuery.Sql er en TStringList så du skal lige tilføje et .Text, altså ADOQuery.Sql.Text := ''. Det er noget du kan gøre i delphi's "Object Inspector", linie SQL (tryk på ...-knappen) og indtast sætningen: select nummer,tekst from kode where tekst like :Pattern;
Når du har tastet ovenstående ind (du skal naturligvis også have en database parat som indeholder tabellen og dens felter), så prøv at klikke på Parameters. Der skulle gerne være en parameter der hedder Pattern.
Det smarte er her, at sætningen er den samme, men at du ændrer værdien af Pattern. Det sker med linien ADOQuery1.Parameters.ParamByName('Pattern').Value := aPattern;
Du brokker dig over at jeg kalder en parameter for noget med a. Det kan ikke være mere simpelt, men det kræver rutine. Rutine opnår man ved at være stringent med hvordan man gør tingene. Jeg sætter ALTID et a foran en parameter og sætter ALTID et f foran variable som gemmer sig i en klasses private del.
Hvis en variabel hedder noget med a, så kan du være 100% sikker på at den findes i procedurehovedet, eksempelvis denne:
aListView er ikke nødvendigvis det samme som ListView1. Det er noget jeg bestemmer når jeg kalder SearchTekster.
Nej. Der skal ikke være en VAR i procedurehovedet. Når jeg kalder SearchRec:
SearchRec(ListView1,'politik');
så sender jeg i virkeligheden pointeren til ListView1 med over (og en pointer er bare RAM-adressen hvor ListView1-objektet ligger). I realiteten betyder det, at aListView = ListView1. Mht. aPattern så kunne jeg forbedre det lidt ved at tilføje et const foran:
Nu må du ikke fortvivle. Jeg tror at grunden du er gået lidt i selvsving skyldes, at det er lettere end du tror. Der var de to fejl du nævnte og har man aldrig arbejdet med varianter af TStrings så er det ikke logisk at jeg havde glemt at sætte .Text bagefter. Resten er ligeud af landevejen.
Proceduren gør følgende (den rettede):
procedure SearchTekster(aListView : TTListView; const aPattern : string); begin try // Antager at aPattern indeholder 'politik' - der søges afhængigt af store og // små bogstaver. Ved ikke hvordan man rammer "POLitik" og lignende - det kan // betyde at du skal gemme strengen i "uppercase" og konvertere tilbage når du // viser strengen.
// Sæt værdien ind i Pattern parameteren Query1.Parameters.ParamByName('Pattern').Value := QuotedStr('*' + aPattern + '*');
Query1.Open; // Kør select sætningen med den værdi som Pattern indeholder
aListView.Items.BeginUpdate; // For hurtigere opdatering af ListView'et try aListView.Items.Clear; // Ryd listen
// Gennemløb de fundne linier while not Query1.EOF do begin
// Opret et TListItem (en linie) og fyld data i with aListView.Items.Add do begin Caption := IntToStr(Query1.FieldByName('Id').AsInteger); SubItems.Add(Query1.FieldByName('Tekster').AsString); end; // with
Query1.Next; // Tag næste fundne record end; // while finally aListView.Items.EndUpdate; // Slut på opdatering. end; // try finally Query1.Close; // Sikrer at Queryen er lukket igen. end; // try end;
Skal du have en variant som passer til et ListBox så kommer den her
procedure SearchTekster2(aStrings : TStrings; const aPattern : string); begin try // Sæt værdien ind i Pattern parameteren Query1.Parameters.ParamByName('Pattern').Value := QuotedStr('*' + aPattern + '*');
Query1.Open; // Kør select sætningen med den værdi som Pattern indeholder
aStrings.BeginUpdate; // For hurtigere opdatering af ListView'et try aStrings.Clear; // Ryd listen
// Gennemløb de fundne linier while not Query1.EOF do begin
// Fyld teksten i en TListBox (ikke plads til ID) aStrings.Add(Query1.FieldByName('Tekster').AsString);
Query1.Next; // Tag næste fundne record end; // while finally aStrings.EndUpdate; // Slut på opdatering. end; // try finally Query1.Close; // Sikrer at Queryen er lukket igen. end; // try end;
I dit program kalder du proceduren på følgende måde:
procedure Button1OnClick(Sender : TObject); begin SearchTekster2(ListBox1.Items,'politik'); // Fordi ListBox1.Items er TStringList som er en nedarving af TStrings // (Ignorér det for nuværende) end;
Ja, ja. Den var lige ved at knække mig. Der er masser der har problemer med at få en ADOQuery med "like" til at virke. Det skyldes jokertegnet '*' som fungerer i Access hvis man skriver SQL der. I Delphi (og PHP for den sags skyld) er det '%' der er joker.
Der er i øvrigt også den tegnvise joker: Hvis man vil søge hann? (for Hanne og Hanna) så virker ? i Access, mens man her skal bruge _ (understregning). Desuden narrede de hersens parametre mig. Jeg troede at der skulle anførselstegn på de strenge jeg fodrede Pattern ('"%politik%"'), men dem sætter den åbenbart selv.
Den endelige funktion (bemærk at jeg har placeret den som en del af DM, hvilket du også burde):
procedure TDM.SearchTekster2(aStrings: TStrings; const aPattern: string); begin aStrings.BeginUpdate; // For hurtigere opdatering af ListView'et try aStrings.Clear; // Ryd listen ADOQuery.Parameters.ParamByName('Pattern').Value := '%' + aPattern + '%'; ADOQuery.Open; while not ADOQuery.EOF do begin aStrings.Add(ADOQuery['Tekster']); ADOQuery.Next; end; // while finally ADOQuery.Close; aStrings.EndUpdate; // Slut på opdatering. end; // try end;
WAW , du får da lavet noget..Det var ikke min hensigt sådan at øge din arbejdsbyrde med SÅ meget. Jeg kigger på den , lige nu har jeg travlt. DM hvad er det ? (Danmarks Mesterskab? : ) Jeg tester det af senere. VH Koden12
[Error] sogtestpas.pas(73): Incompatible types: 'TStrings' and 'TListItems'
Men jeg er godt tilfreds med at listbox virker. Det var jo den jeg ønskede : )
Sikker en masse jeg lærte... mange.. mange tak !!
VH koden12
PS Skulle forresten have været til semanar på SAS hotellet kl 13.00 med Borland som er kommet til DK. Jag var inviteret : ) Desværre nåede jeg det ikke. De skulle præsenterer deres nye Delphi med ASP kode inde (eller sådan noget)
Nå, var vennerne i Danmark. Det er vel også kun rimeligt de kigger forbi en gang imellem, idet deres firma jo er grundlagt vha. et dansk produkt, Poly Pascal. En lille annekdote i øvrigt. Da Poly-Pascal gutterne forsøgte at sælge deres produkt i USA til kr1500 så gik salget af h. til. Så satte de prisen til kr4500 og så var successen hjemme. Tale om omvendt logik.
DM er det navn jeg har givet mit TDataModule - bare en forkortelse idet jeg ikke gider at skrive DetteErMitDataModul.ADOQuery1 osv. Det er lettere med DM.
Har du bemærket at "like" tingesten er ligeglad med store og små bogstaver (der skal nok være noget med de danske bogstaver)?
Dine problemer med at få ListView'en til at du skyldes at du skriver SearchTekster(ListView1.Items...) I parameterhovedet kan du se at den forventer en TListView og det er ikke det du sender med. Fjern .Items og så tror jeg det virker.
Den er genial.. hverken mere eller mindre..Selv om Listviev kan jeg ikke få til at virker.. men pyt..Med eller uden Item.. *** Sjov historie : ) Poly_pascal er rigtig nok en dansk opfindelse..Flere ting er lavet med den. Her forleden dag opdagede jeg at Stellar Offices et lille regnskab program DK er lavet i Poly-Pacal... I dag må den være milliarder værd..
'Like' var ikke let at få til at virke, det medgiver jeg blankt. Brug den med omtanke da det er en tung operation for en database. Hvis du engang kommer til at arbejde på en stresset database, så er denne operation sikkert forbudt og så må man finde andre veje (plukke vigtige ord ud af teksten og placere dem i et indeks eller lignende).
Mht. ListViewet skal du sætte det i report mode: ViewStyle := vsReport. Dernæst skal du dobbeltklikke på komponenten og lave to kolonner - så tror jeg den virker.
Jeg kan godt nok ikke få den til at virke med Datamodul selv om det er i USES og hele balladen..Det har jeg måtte pille af, desværre..
VH koden12
Synes godt om
Ny brugerNybegynder
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.