Tips_tutorials   >   Studio102   >   Town/Cities
In this section we will add a Towncity table to the database, insert some records, create the schema class, query class, and window class.
If all of our contacts were only located in Canada and the USA we could just link each Towncity record to a Stateprov record and then rely on the Stateprov record to join the Towncity to the correct Country.
However, not all countries have states or provinces, so we must include foreign keys in the Towncity table to the Stateprov table and the Country table.
The Stateprov_fkey foreign key in the Towncity table needs to be optional for towns which are located in a country that does not have states or provinces.
The correct way to do this in the SQL world is to enter a null value in the Stateprov_fkey and then use an outer join to select the Towncity/Stateprov/Country records.
Unfortunately the Omnis Studio data file performance for outer joins is very slow. Using outer joins in your SQL text adds some complexity to your SQL scripts. To overcome these issues I use a technique which may cause some SQL gurus to shudder. The technique is to create an empty parent record with its primary key set to zero (0). I then set the optional foreign key value in the child record to zero (0), thereby making a valid join to the empty parent record, negating the need for using outer joins in the SQL scripts. The downside on using the empty record technique is that you need to remember to exclude the empty records when selecting records from tables which contain emtpy records.
It is up to you whether you use a null value foreign key and outer joins, or a zero value foreign key and an empty record. For this tutorial we'll use the empty record technique.In this section we will add a Towncity table to the database. The foreign key columns, Country_fkey, and Stateprov_fkey will be included in the Towncity table.
CREATE TABLE Towncity (Towncity_pkey INTEGER NOT NULL,TownCityName VARCHAR (30) NOT NULL,Stateprov_fkey INTEGER NOT NULL,Country_fkey INTEGER NOT NULL)
CREATE CASE SENSITIVE UNIQUE INDEX Towncity_pkey ON Towncity (Towncity_pkey)
CREATE CASE SENSITIVE INDEX TownCityName ON Towncity (TownCityName)
CREATE INDEX Towncity_Stateprov_fkey ON Towncity (Stateprov_fkey)
CREATE INDEX Towncity_Country_fkey ON Towncity (Country_fkey)
The above indexes do not prevent the user from entering the same TownCityName twice. Different countries could have the same TownCityName so we can not make this a unique index. To prevent the user from entering a TownCityName twice for the same country we will add a composite unique index on the TownCityName and Country_fkey.
CREATE CASE SENSITIVE UNIQUE INDEX TownCityName_Country_fkey ON Towncity (TownCityName,Country_fkey)
The above SQL scripts used to create the unique indexes is specific to the Omnis data file. For creating unique index constraints on other databases check the documentation specific to the RDBMS.
I find it helpful to have some test data in the database when developing an application so lets insert a few Towncity records using the window.
Test the join between the Towncity, Stateprov, and Country records.
The reason that Country_fkey is prefixed with Towncity in the above SELECT is that there is a Country_fkey column in the Towncity table and the Stateprov table. Both tables are included in this select, so we must specify Country_fkey column's table in the SELECT.
To allow a Towncity record to use zero (0) in the Stateprov_fkey field, we need to add a Country record with the Country_pkey set to zero (0) and a Stateprov record with the Stateprov_pkey set to zero (0) and the Country_fkey set to zero (0).
To avoid selecting the empty records we need to modify the $getAllRecords method in tBase.
; Prepare the ORDER BY text.
If len(pOrderBySQL)
Calculate OrderBy as pOrderBySQL
Else
Calculate OrderBy as $cinst.$:DefaultOrderBy
End If
; Prepare the SQL text to exclude the empty zero(0) primary key record.
Calculate ColName as $cinst.$:PrimaryKeyColName
If pos("WHERE ",upp($cinst.$extraquerytext))
Calculate SQLText as con("AND ",ColName," <> 0")
Else
Calculate SQLText as con("WHERE ",ColName," <> 0")
End If
Calculate SQLText as con(SQLText,' ',OrderBy)
; Select all the records in the table.
Do $cinst.$select(SQLText) Returns FlagOK
If not(FlagOK)
OK message [sys(85)] (Icon) {Flag false after $cinst.$select([SQLText])}
Else
; Fetch all the records in the table.
Do $cinst.$fetch(kFetchAll) Returns FetchStatus
If not(FetchStatus)
Calculate FlagOK as kFalse
OK message [sys(85)] (Icon) {Flag false after $cinst.$fetch(kFetchAll)}
Else
; Set the current line to the first line.
Do $cinst.$line.$assign(1)
End If
End If
Quit method FlagOK
In this section we will create a schema class which maps to the Towncity table in the database.
We need a to create a table class which is mapped to the sTowncity schema class.
Quit method 'ORDER BY TownCityName'
We will create a query class which joins the Towncity table to the Stateprov table and to the Country table.
We need to create a table class mapped to this query class. We can either create another subclass of tBase or just copy the tTowncity table class and change the $sqlclassname property to qTowncityList.
We can test the schema and query classes using the
.; Define the list binding it to the table class.
Do List.$definefromsqlclass('tTowncity')
If List.$colcount=0
OK message [sys(85)] (Icon) {The list has zero columns after $definefromsqlclass('tTowncity')}
Else
; Set the session object.
Do List.$sessionobject.$assign($ctask.dbsessionobj)
; Get all the records.
Do List.$getAllRecords() Returns FlagOK
End If
Since the wTowncityList window is going to be similar to the wStateprovList window, we can save some work by duplicating the wStateprovList window and then modifying the duplicate.
; Define and build the list variables used in this window and fetch records as applicable.
Do method retDefinedList ('tTowncityList') Returns List
If List.$colcount=0
Calculate FlagOK as kFalse
Else
; Get all the Stateprov records joined to their parent Country records.
Do List.$getAllRecords() Returns FlagOK
If FlagOK
; Define a list based on the sStateprov schema.
Do method retDefinedList ('tTowncity') Returns iList
If iList.$colcount=0
Calculate FlagOK as kFalse
Else
; Add columns to the Towncity list for the stateprov name and country name.
Do iList.$cols.$add('StateProvName',kCharacter,kSimplechar,100)
Do iList.$cols.$add('CountryName',kCharacter,kSimplechar,100)
; Merge the query based list into the schema based list.
Do iList.$merge(List,kTrue) ;; (Match column names)
; Make the list into a smartlist.
Do iList.$smartlist.$assign(kTrue)
Do method retDefinedList ('tStateprovList') Returns iStateProvList
If iStateProvList.$colcount=0
Calculate FlagOK as kFalse
Else
; Get all the records in the Country table for use by the CountryName lookups.
Do iStateProvList.$getAllRecords() Returns FlagOK
If FlagOK
Do method retDefinedList ('tCountry') Returns iCountryList
If iCountryList.$colcount=0
Calculate FlagOK as kFalse
Else
; Get all the records in the Country table for use by the CountryName lookups.
Do iCountryList.$getAllRecords() Returns FlagOK
; Set the list to be a smartlist so that we can save any changes to the list.
Do iCountryList.$smartlist.$assign(kTrue)
End If
End If
End If
End If
End If
End If
Quit method FlagOK
; Preset the flag to true.
Calculate FlagOK as kTrue
; Make sure the CountryName entered by the user matches an entry in the Countries list.
If iStateProvList.$search(low($ref.StateProvName)=low(iList.StateProvName),1,0,0,0)
; Update the foreign key in the Stateprov list.
Calculate iList.StateProvName as iStateProvList.StateProvName
Calculate iList.Stateprov_fkey as iStateProvList.Stateprov_pkey
Calculate iList.CountryName as iStateProvList.CountryName
Calculate iList.Country_fkey as iStateProvList.Country_fkey
Else
; If not, ask the user whether or not they want to add the country.
Process event and continue (Discard event)
No/Yes message [sys(85)] (Icon) {The State/Province Name entered does not match a State/Province in the state/provinces list.////Do you wish to add [$cobj.$contents] as a State/Province in the country [iList.CountryName]?}
If flag true
; Insert a record in the StateProv table.
Do method retDefinedList ('tStateprov') Returns List
If List.$colcount=0
Calculate FlagOK as kFalse
Else
Do List.$smartlist.$assign(kTrue)
Do List.$add()
Do List.$line.$assign($ref.$linecount)
Calculate List.StateProvName as $cobj.$contents
Calculate List.Country_fkey as iList.Country_fkey
Do List.$dowork() Returns FlagOK
If FlagOK
; Rebuild the state/prov list.
Do iStateProvList.$getAllRecords() Returns FlagOK
If FlagOK
; Make sure the CountryName entered by the user matches an entry in the Countries list.
If iStateProvList.$search(low($ref.StateProvName)=low(iList.StateProvName),1,0,0,0)
; Update the foreign key in the Stateprov list.
Calculate iList.StateProvName as iStateProvList.StateProvName
Calculate iList.Stateprov_fkey as iStateProvList.Stateprov_pkey
Calculate iList.CountryName as iStateProvList.CountryName
Calculate iList.Country_fkey as iStateProvList.Country_pkey
End If
End If
End If
End If
End If
End If
Quit method FlagOK
On evBefore
; Find the matching country in the countries list.
Do iStateProvList.$search($ref.Stateprov_pkey=iList.Stateprov_fkey,1,0,0,0)
On evClick ;; Event Parameters - pRow ( Itemreference )
; Set the foreign key to match the selected country's primary key.
Process event and continue
Calculate iList.Stateprov_fkey as pRow.Stateprov_pkey
Calculate iList.StateProvName as pRow.StateProvName
Calculate iList.Country_fkey as pRow.Country_fkey
Calculate iList.CountryName as pRow.CountryName
Do $cinst.$redraw()
On evAfter
Do method event_evAfter
On evExtend
Process event and continue
; Copy the previous row value to the new row.
Do iList.$assignrow(iList.[iList.$line-1],kTrue) ;; (Match column name)
; Clear the primary key and the TownCityName columns.
Calculate iList.Towncity_pkey as #NULL
Calculate iList.TownCityName as ''
; Redraw the window to display the copied values.
Do $cinst.$redraw()
In order to open the wTowncityList window we need to add a menu line to the menu.
; Find and open an instance of the state/prov list window class.
Calculate ClassName as 'wTowncityList'
Do $clib.$windows.$findname(ClassName) Returns rClass
If isnull(rClass)
OK message [sys(85)] (Icon) {Unable to find the window class '[ClassName]'.}
Else
Do rClass.$openonce('*') Returns rWin
If isnull(rWin)
OK message [sys(85)] (Icon) {Unable to open an instance of the window class [ClassName].}
End If
End If
Quit method rWin
We are ready to test the wTowncityList window.