FileMaker 12 - Internal SQL Code Formatting
Aug 02, 2012 11:53 AM
One of the new features in FileMaker 12 the ability to perform SQL Select statements against FileMaker data. SQL Select statements can be simple and can easily get complicated and difficult to read. This is just one way to make it SQL statements easier to read.
What does that mean? We'll normally, to find a Contact's phone number, you would:
With the new ExecuteSQL function you can do this in one step:
To fix that problem, you would pass in the field and table names in a function so FileMaker's field and table name references stay intact. We like this format to make the code easier to read. If you have any ideas to make this even better comment below.
"" &
ExecuteSQL (
"SELECT " &
FieldNameQuoted ( Contacts::PhoneNumber ) & " " &
"FROM " &
FieldTableNameQuoted ( Contacts::_ID ) & " " &
"WHERE " &
FieldNameQuoted ( Contacts::Name ) & "=?"
; " " ; " " ; "Hal Gumbert" ) &
""
There's a bit more in this than the simple ExecuteSQL statement above. First, we've changed the column and row delimiters to be XML like to make it easier to read the data back in case the data contains commas, returns or other text that might break parsing. The following example would return:
407-555-1212
That a bit excessive for this simple example, but if we returned back more data, it would be helpful:
Hal Gumbert CampSoftware 407-555-1212
To extract the data from the XML like data, we can use another calculation. The following will return the first field from the first record. To get any other field from a record, you just have to change the RecordIndex and the FieldIndex.
Let ( [
RecordIndex = 1 ;
FieldIndex = 1 ;
Record = ParseData ( $XML ; "" ; " " ; RecordIndex ) ;
Field = ParseData ( Record ; "" ; " " ; FieldIndex )
] ;
Field
)
The calculations above use three Custom Functions, FieldNameQuoted, FieldTableNameQuoted, and ParseData, but they could be replaced by calculations. The idea is to get either the Field or Table name from a FileMaker reference name and also quote the name. We use keys that start with underscores which require quoting. Below are the custom functions:
// FieldNameQuoted ( theField )
//
// Returns the only the Field Name, dropping the text before and including the '::'. Contact::FirstName would return 'FirstName'
Let ( [
theFullName = GetFieldName ( theField ) ;
theColons = Position ( theFullName ; "::" ; 1 ; 1 ) ;
theFieldName = Middle ( theFullName ; theColons + 2 ; 99999 ) ;
theTableName = Left ( theFullName ; theColons - 1 )
] ;
Char ( 34 ) & theFieldName & Char ( 34 )
)
// FieldTableName ( theField )
//
// Returns the only the Table Name, dropping the text after and including the '::'. Contact::FirstName would return 'Contact'
Let ( [
theFullName = GetFieldName ( theField ) ;
theColons = Position ( theFullName ; "::" ; 1 ; 1 ) ;
theFieldName = Middle ( theFullName ; theColons + 2 ; 99999 ) ;
theTableName = Left ( theFullName ; theColons - 1 )
] ;
Char ( 34 ) & theTableName & Char ( 34 )
)
// ParseData ( theText; theStartTag; theEndTag; theOccurance)
//
// Extract the text between two strings.
//
// Parameters:
// theText = the text to parse
// theStartTag = the string that comes before the text to extract
// theEndTag = the string that comes after the text to extract
// theOccurance = the instance of the text to extract
//
// Return Value:
// the instance of text found in theText beween theStartTag and theEndTag based on theOccurance
//
Let ( [
theStartPos = Position ( theText ; theStartTag ; 1 ; theOccurance ) ;
theResult = Case (
// ------------------------------
// If theStartTag was not found, return an empty string.
theStartPos = 0 ; "" ;
// ------------------------------
// ------------------------------
// If theStartTag was found, get the string we are looking for.
theStartPos > 0 ;
Let ( [
theStartPos = theStartPos + Length ( theStartTag ) ;
theEndPos = Position ( theText ; theEndTag ; theStartPos ; 1 ) ;
theLengthToKeep = theEndPos - theStartPos;
theResult = Middle ( theText ; theStartPos ; theLengthToKeep )
] ;
theResult
)
// ------------------------------
) // End case
] ;
theResult
)
- Go to the Contact Layout
- Enter find Mode
- Go to the Contact name fields and enter their name.
- Perform the Find
- Then finally, get the Contact Phone number.
With the new ExecuteSQL function you can do this in one step:
- ExecuteSQL ( "SELECT PhoneNumber FROM Contacts WHERE Name ='Hal Gumbert'" )
To fix that problem, you would pass in the field and table names in a function so FileMaker's field and table name references stay intact. We like this format to make the code easier to read. If you have any ideas to make this even better comment below.
"
ExecuteSQL (
"SELECT " &
FieldNameQuoted ( Contacts::PhoneNumber ) & " " &
"FROM " &
FieldTableNameQuoted ( Contacts::_ID ) & " " &
"WHERE " &
FieldNameQuoted ( Contacts::Name ) & "=?"
; "
""
There's a bit more in this than the simple ExecuteSQL statement above. First, we've changed the column and row delimiters to be XML like to make it easier to read the data back in case the data contains commas, returns or other text that might break parsing. The following example would return:
That a bit excessive for this simple example, but if we returned back more data, it would be helpful:
To extract the data from the XML like data, we can use another calculation. The following will return the first field from the first record. To get any other field from a record, you just have to change the RecordIndex and the FieldIndex.
Let ( [
RecordIndex = 1 ;
FieldIndex = 1 ;
Record = ParseData ( $XML ; "
Field = ParseData ( Record ; "
] ;
Field
)
The calculations above use three Custom Functions, FieldNameQuoted, FieldTableNameQuoted, and ParseData, but they could be replaced by calculations. The idea is to get either the Field or Table name from a FileMaker reference name and also quote the name. We use keys that start with underscores which require quoting. Below are the custom functions:
// FieldNameQuoted ( theField )
//
// Returns the only the Field Name, dropping the text before and including the '::'. Contact::FirstName would return 'FirstName'
Let ( [
theFullName = GetFieldName ( theField ) ;
theColons = Position ( theFullName ; "::" ; 1 ; 1 ) ;
theFieldName = Middle ( theFullName ; theColons + 2 ; 99999 ) ;
theTableName = Left ( theFullName ; theColons - 1 )
] ;
Char ( 34 ) & theFieldName & Char ( 34 )
)
// FieldTableName ( theField )
//
// Returns the only the Table Name, dropping the text after and including the '::'. Contact::FirstName would return 'Contact'
Let ( [
theFullName = GetFieldName ( theField ) ;
theColons = Position ( theFullName ; "::" ; 1 ; 1 ) ;
theFieldName = Middle ( theFullName ; theColons + 2 ; 99999 ) ;
theTableName = Left ( theFullName ; theColons - 1 )
] ;
Char ( 34 ) & theTableName & Char ( 34 )
)
// ParseData ( theText; theStartTag; theEndTag; theOccurance)
//
// Extract the text between two strings.
//
// Parameters:
// theText = the text to parse
// theStartTag = the string that comes before the text to extract
// theEndTag = the string that comes after the text to extract
// theOccurance = the instance of the text to extract
//
// Return Value:
// the instance of text found in theText beween theStartTag and theEndTag based on theOccurance
//
Let ( [
theStartPos = Position ( theText ; theStartTag ; 1 ; theOccurance ) ;
theResult = Case (
// ------------------------------
// If theStartTag was not found, return an empty string.
theStartPos = 0 ; "" ;
// ------------------------------
// ------------------------------
// If theStartTag was found, get the string we are looking for.
theStartPos > 0 ;
Let ( [
theStartPos = theStartPos + Length ( theStartTag ) ;
theEndPos = Position ( theText ; theEndTag ; theStartPos ; 1 ) ;
theLengthToKeep = theEndPos - theStartPos;
theResult = Middle ( theText ; theStartPos ; theLengthToKeep )
] ;
theResult
)
// ------------------------------
) // End case
] ;
theResult
)