Inleiding tot Gambas – PostgreSQL

Ik probeer het voorbeeld van dit artikel op Linux Journal na meer dan 10 jaar uit met één van de volgende versies van Gambas (3.x) en een andere databank die meer verschilt dan MySQL-MariaDB onderling: PostgreSQL.
Hier de beschrijving die gaat tot het ophalen en weergeven van gegevens (dus niet heel artikel uitgewerkt).

Vertrek van een Linux desktop systeem, met Gambas en PostgreSQL geïnstalleerd.
Het concept van het artikel is een soort summiere bug-tracker te maken.

Gambas IssueTracker
Start Gambas 3.9.x, new project, Database application.
– Je krijgt een lege FMain bij de “sources”.

Maak een knop om de applicatie te sluiten: kies onderaan rechts de “ok” knop uit de “Form” tab iconen, plak hem ergens op het scherm
– op het scherm staat een knop met “ok” erop.

Wijzig de tekst op de knop:
In de tab “Properties” rechts zie je een invulbaar veld naast “Text”; zet daar “Sluiten”.
– De knop op het scherm krijgt aangepaste tekst.
Zet helemaal bovenaan bij Name in plaats van Button1: btQuit

Wijzig de code van de knop:
Dubbelklik op de knop en je komt in de tekstbewerker. Tik tussen de begin en eindcode van de druk-op-de-knop procedure (die hij voor je klaarzet): Me.Close()
Samen ziet het er zo uit:

Public Sub btQuit_Click
  Me.Close()
End

Terwijl je op de run knop in de knoppenbalk van Gambas3 drukt (of F5 op je toetsenbord), weet je dat je een leeg venster op het scherm gaat zien met één knop: “Sluiten”. Je kan je niet bedwingen om op die knop te klikken.

Pauze om na te denken
Het oorspronkelijke artikel vermeldt volgende noden:

  • nieuw probleem melden
  • bijhouden wie het probleem meldde
  • probleem als opdracht toewijzen aan programmeur
  • de toestand van het probleem beheren
  • bijhouden wanneer het probleem werd gemeld
  • bijhouden in welke programmaversie het probleem is opgelost
  • overzicht krijgen van nieuwe problemen, problemen waar aan gewerkt wordt, en opgeloste problemen

Meer gedetailleerd hebben we nodig:

  • identificatie van de melder
  • identificatie van de oplosser
  • beschrijving van het probleem

Nog meer gedetailleerd:

  • gegevens van de melder: id, name
  • gegevens van de oplosser: id, name
  • details van het probleem: id, beschrijving, datum in, door wie aangebracht en opgelost, applicatie waarover ze gaat, versienummer van vaststelling probleem en oplossing

Databank

Om databank-layout uit te proberen is phpPgAdmin leuk, maar nadien weet je waarschijnlijk niet meer wat je juist gedaan hebt.
Het is handig om de databank aan te maken vanuit een bestand, zodat je met dat bestand gegarandeerd dezelfde databank kan maken.
(of je draait het om; maak in phpPgAdmin, en exporteer de vorm naar een bestand)

In MySQL/MariaDB was het zoiets:

Continue reading

Gebruik van verschillende Datasets

(DEV)
Programma’s waarbij je een database gebruikt, worden dikwijls ontwikkeld op een andere databank dan diegene die uiteindelijk zal gebruikt worden.
Stel dat de ontwikkeling gebeurt op server (DEV), met databank employee. Die server kan evengoed een virtuele machine, je eigen desktop of laptop zijn.

Structuur (DEV) –> (PRODUCTION)
Op een bepaald moment wordt het programma in gebruik genomen, en maak je dus een definitieve databank aan op de server (PRODUCTION), die werkelijk gebruikt wordt. Waarschijnlijk houd je je DEV voor verdere ontwikkeling, met een kleine test-database met relevante gegevens voor wat je aan het ontwikkelen bent, terwijl die van de (PRODUCTION) server met rasse schreden in volume toeneemt.

Data (PRODUCTION) –> (TEST)
Die verdere ontwikkeling testen met echte productie data wil je misschien laten doen door iemand anders, terwijl jij voortontwikkelt. Dan is het handig om een (TEST) systeem te hebben. De data kan je uit de productieserver halen, zodat je op grote aantallen data kan testen, wat realistischer is.

(DEV) ? (TEST) ? (PRODUCTION)
Als je nu vanop je ontwikkelomgeving programma’s wil gebruiken met data van TEST (vers geupdate van PRODUCTION), moet je de gegevens van je verbinding met de databank in het programma aanpassen. Waarschijnlijk in een settings- of configuratiebestand, wat het iets gemakkelijker maakt. Je kan dat settingsbestand dan in versies DEV en TEST maken, en met een shell script het te gebruiken configuratiebestand kopiëren over het standaard configuratiebestand. Of kan het vanuit je software?

Dataset
Concept: je kan in je programma voorzien om verschillende datasets te gebruiken. Gevolg is dat je in een menu kan omschakelen naar “dev”, “test”, of “production”, en dat de juiste connectiegegevens geladen worden.

Ik gebruik een aparte module voor de data (1)
MData

Ik nummer mijn datasets, zodat ik ze gemakkelijk kan aflopen maar ook in de Settings kan opslaan: 0, 1, 2…
Ik houd in een variabele in de data module bij welke dataset momenteel in gebruik is:
PRIVATE iCurrentDataSet AS Integer

Ik geef de dataset een standaard naam in de module die de data gebruikt:
PRIVATE CONST sDataSetName = "dbdata"

Voor de eigen data heb ik dus db configuraties:
dbdata0, dbdata1, dbdata2 (0-dev, 1-test, 2-production).

In de Settings file komt bv:
[dbdata0]
servertype="mysql"
datasetname="dbdatadev"
database="maindb"
host="localhost"
login="myapp"
password="ldfjq5sehrqz"
...

[dbdata1]
servertype="mysql"
datasetname="dbdatatest"
database="maindb"
host="test.copyleft.loc"
login="myapp"
password="ldfjq5sehrqz"
...

Om de instelling van de juiste dataset “sDataset” op te halen, te bewaren moet je hem natuurlijk kunnen benoemen:

Settings["dbdata1/host"]

Of via universelere code
sDataset & "/host"
waarbij
sDataset = getDataSetString(iCurrentDataSet)

Die is samengesteld van de naam plus het nummer, een procedure uit de betreffende module levert die:

PUBLIC SUB getDataSetString(i as Integer) AS String
   RETURN sDataSetName & Str$(iCurrentDataSet)
END

Hiermee kan je de voorkeuzelijst vullen.


NB:

Als je meer data modules gebruikt:

Een aparte module voor de data, import, export, enz, met telkens hetzelfde systeem.
MData
MImport
MExport

krijgt in elke module de dataset een *andere* naam:

dbdata = voor de eigen data van het programma
dbimport = voor data die geïmporteerd wordt uit een andere databank
..

Voor de import data heb ik dan datasets:
dbimport0, dbimport1, dbimport2

In de settingsfile zijn die dan terug te vinden:

[dbimport0]
servertype="mysql"
datasetname="dbimportdev"
database="maindb"
host="localhost"
login="readimport"
password="ldfjq5sehrqz"
...

[dbimport1]
servertype="mysql"
datasetname="dbimporttest"
database="maindb"
host="test.copyleft.loc"
login="readimport"
password="ldfjq5sehrqz"
...

Een aanzet tot ietwat universele code:

PUBLIC CONST sDataSet AS String = "dbimport" ' <---- Change this
' to change for each module - base for name in settings file

PRIVATE iCurrentDataSet AS Integer = 0 ' no of dataset, 0 is default, alternatives 1, 2, .. defined in settings

PRIVATE sCurDataSetName AS String ' 

PUBLIC $hconData AS NEW Connection



PUBLIC SUB getLastError() AS String
  
  RETURN sLastError
  
END

PUBLIC SUB resetError()
  
  sLastError = ""
  
END


PUBLIC SUB curDataSetString() AS String
  
  RETURN getDataSetString(iCurrentDataSet)
  
END

PRIVATE SUB getDataSetString(i AS Integer) AS String
  
  RETURN sDataSet & Str$(iCurrentDataSet)
  
END

PUBLIC SUB getDataSetName() AS String
  
  RETURN sCurDataSetName
  
END


PUBLIC SUB loadDataSet(iDataSet AS Integer) AS Boolean
  
  DIM sData AS String
  
  iCurrentDataSet = iDataSet
  sData = getDataSetString(iCurrentDataSet)
  
  DEBUG Settings.Path &/ Application.Name & ".conf"
  IF (Exist(Settings.Path &/ Application.Name & ".conf"))
    sCurDataSetName = Settings[sData & "/datasetname"]
    WITH $hconData
      .Host = Settings[sData & "/host"]
      .Name = Settings[sData & "/database"]
      .Login = Settings[sData & "/login"]
      .Password = Settings[sData & "/password"]
      .Type = Settings[sData & "/servertype"]
    END WITH 
    RETURN TRUE
  ELSE 
    iCurrentDataSet = -1
    sLastError = "Could not find data connection settings " & Error.Text
    RETURN FALSE
  ENDIF   
  
END


PUBLIC SUB goConnect() AS Boolean
  
  TRY $hconData.Close
  TRY $hconData.Open
  IF ERROR 
    sLastError = Error.Text
    RETURN FALSE
  ELSE 
    RETURN TRUE
  ENDIF 
  
END

Database velden met prefix van tabelnaam

Naar aanleiding van een vroeger artikel over hergebruik van code, kwam ik op de kwestie van de standaardisering van de veldnamen in een databank. Algemeen zie ik twee systemen gebruikt worden:

1. Met prefix:

Meestal een korte prefix die de naam van de tabel aangeeft, zodat je altijd kan zien waar de veldnaam op slaat:

# Name Type Null Default Extra
1 emp_id bigint(20) No None AUTO_INCREMENT
2 emp_name int(11) No 0
3 emp_data2 int(11) No 0
8 emp_creat timestamp Yes CURRENT_TIMESTAMP
9 emp_creby char(24) Yes NULL
10 emp_updat datetime Yes NULL
11 emp_updby char(24) Yes NULL

Inderdaad de employee tabel, “emp” of beter “employee”.

2. Zonder prefix

Zonder tabelnaam (afkorting) in veldnamen:

# Name Type Null Default Extra
1 id bigint(20) No None AUTO_INCREMENT
2 name int(11) No 0
3 data2 int(11) No 0
8 creat timestamp Yes CURRENT_TIMESTAMP
9 creby char(24) Yes NULL
10 updat datetime Yes NULL
11 updby char(24) Yes NULL

In dit geval moet je de (afkorting voor) de veldnaam niet meegeven. Als de database vermeld wordt is het toch duidelijk:

emp.id
employee.name
employee.fieldname1
employee.creat
employee.creby
employee.updat
employee.updby

eventueel met korte alias

emp.id
emp.name
emp.fieldname1
emp.creat
emp.creby
emp.updat
emp.updby

Veel code blijft korter, maar een luie blik op de resultaten van een query kan iets meer aandacht gaan vragen (om de oorspronkelijke tabelnaam te zoeken), en misschien worden je aliassen langer e.veld wordt emp.veld voor table employee.

Vgl:

e.emp_id, e.emp_name
emp.id, emp.name

De prefix blijkt vooral een overblijfsel te zijn van vroeger, toen alle veldnamen uniek moesten zijn (ook buiten de tabel).

De algemene raadgeving is: geen prefix gebruiken:

  • korter
  • eenduidiger
  • goede tabelnamen en aliassen te gebruiken
  • betere standaardisatie

Pro prefix:

  • oude systemen waar het moet
  • geen probleem met gereserveerde woorden (commanodo’s in de query taal)*
  • gemakkelijk bij export want tabelnaam staat in veld.
  • soms beter leesbaar omdat velden altijd dezelfde vorm hebben.

* stel dat je een veld hebt waarin je aangeeft dat er een update gebeurd is of moet gebeuren:
employee.update
Helaas, UPDATE is een gereserveerd woord in de SQL taal (SELECT update FROM …), en zo zijn er nog woorden die door de syntax highlighting kunnen aangewezen worden als “gereserveerd”. Door prefixen te gebruiken moet je daar nooit op letten. Maar je kan ook ‘quotes’ gebruiken rond het gereserveerd woord als veldnaam.

Zie ook discussie op StockOverflow: (1) en (2)

Data module met universele procedure om record toe te voegen

Gambas Modules of minstens stukken ervan kan je zo universeel maken dat je ze gemakkelijk kan hergebruiken.
Bij gebruik van een databank (bv MySQL/MariaDB) moet je altijd code schrijven om een record aan te maken. Het gemeenschappelijke daarin is minstens het toevoegen van een record met een sleutel (record id).

Je kan de aanmaak van een nieuw record (dus met nieuwe gegevens) opsplitsen in twee delen:

  • Aanmaken van een leeg record met een nieuw bekomen record nummer.
  • Het aanvullen van dat nieuwe record met alle andere gegevens, een update dus.

De update procedure moet je waarschijnlijk toch ook maken om gegevens te kunnen wijzigen aan een record waarvan je de id kent.

Het aanroepend programma kan gewoon een “ModData.saveRecord(id, data)” gebruiken, de module splitst het op in twee delen.
Als de id gegevens is, wordt de update procedure aangeroepen.
Als de id = 0 moet er een nieuw record gemaakt worden, daarna de update procudure met de ondertussen verkregen id.

Een datamodule ModData bevat:

PRIVATE sLastError AS String
' ...
PUBLIC SUB addRecord(sTablename AS String, sKeyname AS String) AS Long
' adds a record to a given database, get the record id of the empty record 
  DIM sSql AS String  
  DIM myResult AS Result
 ' 
  sSql = "INSERT INTO " & db.Quote(sTablename) & " (" & sKeyname & ") VALUES(0)" 
  DEBUG sSql
  '
  TRY $hConData.Exec(sSql)
  IF ERROR 
    sLastError &= "insert; " & Error.Text
    RETURN 0
  ELSE 
    sSql = "SELECT LAST_INSERT_ID() AS myId "
    'myId" ' FROM " & db.Quote(sTablename)
    TRY myResult = $hConData.Exec(sSql)
    IF ERROR 
      sLastError &= "last id ?" & Error.Text
      RETURN 0
    ELSE 
      RETURN myResult!myId 
    ENDIF 
  ENDIF 
  DEBUG sLastError
END

De procedure geeft het aangemaakte recordnummer terug. Daarna kunnen de gegevens ingevuld worden, wat minder universeel is. Veel kans dat dit stuk code enkel intern vanuit de module aangeroepen wordt, je zou ze dus ook PRIVATE kunnen maken.
Fouten vraagt het aanroepend programma/aanroepende procedure op met getLastError(), wat geimplementeerd is als

PUBLIC SUB getLastError() AS String
  '
  RETURN sLastError
  '
END

Alle procedures in de datamodule die een record moeten toevoegen in een database, kunnen deze zelfde functie gebruiken, mits aangeroepen met de juiste parameters voor de betreffende tabel.

Voorbeeld:

# Name Type Null Default Extra
1 emp_id bigint(20) No None AUTO_INCREMENT
2 emp_data1 int(11) No 0  
3 emp_data2 int(11) No 0  
4 emp_data3 char(1) Yes NULL  
5 emp_data4 varchar(11) Yes NULL  
6 emp_data5 datetime Yes NULL  
7 emp_data6 datetime Yes NULL  
8 emp_cre timestamp Yes CURRENT_TIMESTAMP  
9 emp_creby char(24) Yes NULL  
10 emp_upd datetime Yes NULL  
11 emp_updby char(24) Yes NULL  

Dit werkt op voorwaarde dat

  • de key record_id een getal is, bv van het type bigint(20)
  • de waarde van het veld record_id altijd automatisch wordt toegekend door de database (Auto Increment)
  • er standaardwaarden voorzien zijn in de struktuur van de tabel (0, NULL, ..).

Als geen standaardwaarden voorzien zijn krijg je een foutmelding; de database krijgt enkel een opdracht om de key in te vullen en weet niet wat er met de andere velden moet gebeuren.

Andere dingen die je kan standaardiseren:

  • Een foutmelding bijhouden (zie ook hierboven sLastError) en op laten vragen.
  • Een record verwijderen dat een bepaalde keywaarde heeft.
  • Opbouwen, openen en sluiten van de databaseverbinding
  • Een keuze tussen verschillende datasets voorzien (bv laptop/netwerk, ontwikkeling/productie)
  • enz.

Metadata
Dikwijls is het nuttig om te weten wanneer een record ontstaan is (creation date-time), en wie het gemaakt heeft (user). Als je daarvoor standaard velden opneemt in de database kan je die laten invullen vanuit deze record toevoegen-procedure. Het eenvoudigste voor het “createdon” veld is in de database als standaardwaarde “CURRENT_TIMESTAMP” in te stellen. Dan blijft nog het “createdby” veld, waar we de gebruikerslogin naam kunnen invullen (gb.user.name). Ofwel laat je een waarde opslaan die als parameter van de oproepende procedure komt, en kan het een andere vorm van gebruikers-id zijn, bv die waarmee in de applicatie ingelogd wordt.

  • Database: employee
  • Velden: id, naam, address, …, createdon, createdby
  • Database: department
  • Velden: id, building, floor, …, createdon, createdby

Dit is universeel; je kan employee.createdby of department.createdby enz. gebruiken; bij de AddRecord is het steeds dezelfde naam. Je moet dat dan voor alle tabellen zo doen!

Een andere mogelijkheid is de createdby optioneel te maken, zodat je ook nog minimale tabellen met enkel id kan houden.
En: als je niet de standaardnaam gebruikt, maar velden met een prefix voor de tabel gebruikt, moet je de veldnaam ergens vandaan halen.

  • Database: employee
  • Velden: emp_id, emp_naam, emp_address, …, emp_createdon, emp_createdby
  • Database: department
  • Velden: dep_id, dep_building, dep_floor, …, dep_createdon, dep_createdby

Gecombineerd:

PUBLIC SUB addRecord(sTablename AS String, sKeyname AS String, OPTIONAL sCreatedByField AS String) AS Long 
' adds a record to a given database, get the record id of the empty record 
  DIM sSql AS String  
  DIM myResult AS Result
 '
  sSql = "INSERT INTO " & db.Quote(sTablename) & " (" & sKeyname 
  IF sCreatedByField
    sSql &= "," & sCreatedByField & ") VALUES(0,&1)" ' & db.Quote(User.Name) & ")"
  ELSE 
    sSql &= ") VALUE(0)" 
  ENDIF 
  '
  DEBUG sSql
  '
  TRY $hconData.Exec(sSql, User.Name)
  IF ERROR 
    sLastError &= "insert; " & Error.Text
    RETURN 0
  ELSE 
    sSql = "SELECT LAST_INSERT_ID() AS myId "
    '
    TRY myResult = $hConData.Exec(sSql)
    IF ERROR 
      sLastError &= "last id ?" & Error.Text
      RETURN 0
    ELSE 
      RETURN myResult!myId 
    ENDIF 
  ENDIF 
  DEBUG sLastError
END 

De procedure AddRecord krijgt een optionele parameter “sCreatedByField”.

Als die ingevuld is, wordt dat veld gebruikt om de gebruikersnaam in te bewaren; ‘User.Name’ kan je in Gambas altijd gebruiken en geeft de loginnaam van de gebruiker die het programma draait. Hier wordt die ingevuld vanuit AddRecord, zodat er van het oproepend programma niet aan gedacht moet worden, of ook de waarde niet vergeten of verkeerd ingevuld kan worden; zelfs als de update nadien niet werkt heb je al meta-informatie.

Als geen veldnaam wordt doorgegeven om de createdby informatie in op te slaan, wordt enkel de record_id ingevuld.

Upd: Ps: misschien gebruik ik ook beter geen tabelnaam-PREFIX in de veldnamen … (http://gambas.copyleft.be/blog/archives/1453)

Keek op de week: waar begint de weektelling?

Na het vervangen van een server treden er eigenaardige fouten op, die te maken hebben met tijdsberekening. Nader onderzoek leidt me tot enkele eenvoudige testen:

Gambas commandolijn (onderaan in de IDE):

? week(now())

41

Dan op de mysql server via de browser in phpmyadmin of rechtstreeks in de mysql commandprompt:

SELECT WEEK(NOW());

+-------------+
| week(now()) |
+-------------+
|          40 |
+-------------+
1 row in set (0.02 sec)

Niet iedereen begint op dezelfde manier te tellen; de Europeanen blijkbaar vanaf de eerste dag van het jaar, de Amerikanen vanaf de eerste volle week van het jaar.

Gambas error door databank update: “wanted Integer ..”

Bij een bestaande applicatie in gambas2 duikt een fout op na een update van de database server:

Type mismatch: wanted Integer, got String instead

Net voor het probleem opdook werd de server waarop de MySQL databank draaide, geupdate van
mysql 3.23.x naar mysql 5.5.x

De reaktie van de database is veranderd bij een ontbrekende waarde; in plaats van een 0 (nul als getal), komt nu een andere waarde terug (in debug getoond als -23495771):

De test die vroeger het onderscheid maakte of data aanwezig was of niet in het veld fld_diff in een resultset resRecord:

IF resRecord!fld_diff > 0

is nu vervangen door een combinatie met een test op het type van het veld:

IF TypeOf(resRecord!fld_diff) = gb.Integer
    IF resRecord!fld_diff > 0

Pas als je weet dat het een getal is kan je de waarde gaan testen.

Daarna werkte het programma terug.

Hoe data in een cel steken van een TableView

Zet op een Form bv Form1 een tabel met de eigenschap TableView, bv tbvwFill.
Het gemakkelijkst is waarschijnlijk om de tabel te initialiseren door code die je oproept bij het openen van de Form waar ze in staat:

PUBLIC SUB Form_Open()

  initTableView()

END

Je moet dan een procedure daarvoor maken:

PUBLIC SUB initTableView()

Daarin moet je zeker bepalen wat de grootte van de tabel is, m.a.w. hoeveel cellen horizontaal en hoeveel vertikaal. Daarvoor “zet” je de eigenschap van de kolom en rij-teller door er een waarde of de waarde van een bestaande variabele aan toe te kennen:

  tbvwFill.Columns.Count = 3
  tbvwFill.Rows.Count = iDefinedRows

Je kan het aantal natuurlijk laten bepalen door een globale variabele die je elders reeds gevuld had:

  tbvwFill.Rows.Count = FMain.$hMonth.GrdTotRows()
  • FMain: het hoofdscherm van waaruit het huidig scherm (Form1) wordt opgeroepen
  • $hMonth is een object dat een maand voorstelt, daar een aantal eigenschappen over heeft, en gedefinieerd werd in FMain.
  • GrTotRows is een eigenschap van object $hMonth, de waarde is al toegekend in FMain; of hier is het eigenlijk een functie van het object $hMonth die deze waarde berekent op basis van andere eigenschappen en teruggeeft als waarde.

Op deze manier kan het aantal rijen van tabellen in verschillende forms gestuurd worden uit 1 variabele.

Een waarde toekennen aan de twee cellen of velden van de bovenste rij in de tabel:

  tbvwFill[0, 0].Text = "ID"
  tbvwFill[0, 1].Text = "Naam"
  iRowStartEmployees = 1

Procedure natuurlijk altijd afsluiten met

END

Het vullen van de tabel kan o.a. met een FOR EACH loop, bv met waarden uit een resultset – hier $hResEmployees – die op voorhand gevuld werd met waarden uit een databank.

  FOR EACH $hResEmployees
    tbvwFill[$hResEmployees.Index + iRowStartEmployees, 0].Text = $hResEmployees!emp_id
    tbvwFill[$hResEmployees.Index + iRowStartEmployees, 1].Text = $hResEmployees!emp_name
  NEXT 

De eerste twee kolommen (kolom 0 en kolom 1) worden gevuld met een de velden id en name uit de database.
De eerste rij wordt hier niet gebruikt (rij 0) omdat daar de titel in staat.

Dan moet ervoor gezorgd worden dat als je op de tabel klikt, je de waarde van de cel kan wijzigen:

PUBLIC SUB tbvwFill_Click()

  DEBUG TableView1.Column
  DEBUG TableView1.Row

  tbvwFill.Edit()
  
END

De DEBUG lijnen tonen de rij en de kolom-waard; de cel linksboven is (0,0); de headers reageert niet op deze klik om coördinaten.

En tenslotte moet je ervoor zorgen dat die ingevulde waarde ook behouden blijft in de cel:
Daarvoor moet je de methode _Save van de tabel zelf aanvullen met code:

PUBLIC SUB tbvwFill_Save(x AS Integer, y AS Integer, sText AS String)
  
  tbvwFill[x, y].Text = sText
  
END

Je kan hier natuurlijk binnen deze code controles op uitvoeren enz.

– NB: – – Continue reading

Database opvraging met result set

Een sterk hulpmiddel voor het opvragen van gegevens uit een databank en verdere verwerking ervan is de “resultset”, de resultaatset van de opvraging. Een opvraging bestaat meestal uit een tekstlijn met een SQL query. Die kan op voorhand opgebouwd worden in een variabele.
Er wordt trouwens aangeraden om eerder de echte variabelen mee te geven als parameters.( zie Databank aanspreken..)

sRequest = "SELECT * FROM &1 LIMIT &2"
TRY myResult = db.Exec(sRequest, "test", "2000")

Met de resultset kan je dan hetvolgende:

  • Kijken hoeveel records of resultaatrijen er zijn:

    PRINT myResult.Count

  • bewegen met de pointer of index:

    Die begint te tellen bij 0 en kan gevolgd worden met:
    PRINT myResult.Index

    Naar een bepaalde rij:

    myResult.MoveTo(iRow)

    Naar een relatieve rij bewegen met de pointer: eerste, laatste, volgende, vorige

    myResult.MoveFirst()
    myResult.MoveLast()
    myResult.MoveNext()
    myResult.MovePrevious()

    Een veld uit het huidige record aanspreken:
    PRINT myResult!fld_name

    Omdat het een “enumerable” resultset is hoef je zelf niet te tellen, maar kan je FOR EACH gebruiken:

    FOR EACH myResult
    PRINT myResult!fld_id & " - " & myResult!fld_name
    NEXT

    (als de databank-tabel de velden fld_id en fld_name bevat )

  • De inhoud van een veld opvragen om in te vullen in bv een TableView:

    TableView1.Data.Text = myResult[sVeldnaam]

Extra interessant zijn de “subcollecties”: bv de beschikbare velden:

myResult.Fields[]

Die zijn “enumerable”: dwz te gebruiken met FOR EACH. Je moet wel een “tellervoorwerp” hebben; bv een veld.

DIM myField AS Field

FOR EACH myField in myResult.Fields
‘ …
NEXT

Voorbeeld:

PUBLIC SUB fillLsbxTableFieldsForEach()
'
' try to make a better fillListbox by using enumerated type
'  
  DIM iInd AS Integer
  DIM sTableAndFields AS String
  DIM hField AS Field
  DIM hTable AS Table
'  
  DEBUG "Database:" & lsbxDatabases.Current.Text
  WITH $hConDb
    .Name = lsbxDatabases.Current.Text
  END WITH 
  $hConDb.Close
  $hConDb.Open
  TRY $hRes = $hConDb.Exec("SHOW TABLES")
  IF ERROR 
    Message.Error(Error.Text, "ok")
  ELSE 
    DEBUG $hRes.Count
    $hRes.MoveFirst
    lsbxDbTables.Clear
    FOR EACH hTable IN $hConDb.Tables
    '[$hRes[Str($hRes.Fields[0].Name)]]
      WITH hTable
        DEBUG .Name
        sTableAndFields = .Name & "="
      END WITH 
      FOR EACH hField IN hTable.Fields
        WITH hField
          'DEBUG .Name
          sTableAndFields &= .Name & "+" 
        END WITH 
      NEXT       
    lsbxDbTables.Add(sTableAndFields)
    NEXT      
  ENDIF 
'  
END

Upd 3/11/2010: index start op 0.

Parameters meegeven naar Form

In het voorbeeld Database (voorbeelden inbegrepen in Gambas) wordt zo informatie doorgegeven naar een opgeroepen form:


' Gambas class file

PRIVATE $hConn AS Connection

'......

PUBLIC SUB btnRun_Click()

  DIM rData AS Result
  DIM hForm AS FRequest

  rData = $hConn.Exec(txtRequest.Text)
  hForm = NEW FRequest($hConn, rData)
  hForm.Show

CATCH

  Message.Error(DConv(Error.Text))

END

De regel
hForm = NEW FRequest($hConn, rData)
geeft een Connection en een Result object mee.

Die worden opgevangen in de _new method van de aangeroepen form:


' Gambas class file

PRIVATE $hConn AS Connection
PRIVATE $rData AS Result
'....

PUBLIC SUB _new(hConn AS Connection, rData AS Result)

  $hConn = hConn
  $rData = rData

' ...

END

en omgezet in eigen variabelen (de $hConn is twee keer PRIVATE).

Kan misschien ook/anders door een aparte ModCommon te maken en daarin PUBLIC te declareren.

MySQL statements uitproberen

MySQL is even in de aandacht geweest wegens de aankoop van het moederbedrijf
MySQL AB door Sun. Hopelijk blijft het de basis van vele handige vrije software projecten.

Vanuit verschillende toepassingen kan je MySQL databank ondervragen met SQL statements.
Meestal is het handig om ze op voorhand uit te proberen.
Daarom: opslaan in een bestand, en het bestand laten uitvoeren.

Begin het bestand bv met het openen van de database:

USE mijngegevens_db;
SELECT * FROM mijntabel WHERE afdeling = "afd1";

Bewaar in een tekstbestand getMyData.sql

Vanop de mysql command prompt:
source getMyData.sql

Wil je de resultaten niet alleen over het scherm zien rollen maar ook nadien ergens terugvinden, doe dan op de mysql command prompt een “tee” alvorens de vraag te laten uitvoeren:

tee mysqlresultaat.txt
source getMyData.sql

In het tekstbestand mysqlresultaat.txt vind je het commando terug en het resultaat eronder.

Als je een aantal sql-vragen moet stellen met steeds een klein verschil in de vraag, bv telkens een andere maand, afdeling, .. kan je de sql-vraag in een tekstbestand plakken, kopieren naar het aantal totale vragen, en het ene stukje veranderen dat nodig is.

USE mijngegevens_db;
SELECT * FROM mijntabel WHERE afdeling = "afd1";
SELECT * FROM mijntabel WHERE afdeling = "afd2";
SELECT * FROM mijntabel WHERE afdeling = "afd3";

(Daar kan OOo Calc ook mee helpen; vult waarden aan bij kopieren).

Je kan dan de hele set sql-vragen tegelijk laten uitvoeren.

Telkens je iets doet wordt het bijgeschreven in het resultatenbestand.
Dat kan handig zijn omdat je zo een log hebt. Je kan het inderdaad
ook gebruiken terwijl je op de mysql command prompt werkt, om de resultaten nadien te bekijken, of de sql vragen te hergebruiken.

In bovenstaand kan je de verschillende bestaande afdelingen vinden met:

SELECT DISTINCT afdeling FROM mijntabel

die je dan weer zou kunnen gebruiken in een sql statement …