As part of my current project, I needed to display records from a database table. My immediate choice was to use a grid (TDBGrid) linked to the database table (TTable)--simple enough and straightforward. I could easily drop the grid, table, and data source components onto my form, link everything at design time, and be finished in a matter of minutes.
However, after some thought I realized that the users would probably (later, of course) want some mechanism to view a subset of the data. As you probably know if you've used the TTable/TDBGrid combination, unless you specify a master/slave relationship or some kind of filter, every record in the table is displayed. In this case, I didn't have another table to serve as the master, and I didn't want to use a filter. Instead of the ever-familiar TTable, I decided to use a TQuery component to connect to the underlying database table. The main advantage for my application was that TQuery would let me specify parameters (variables) in the query's SQL statement that I could set during runtime.
If you've been using TTables for all your database connections, you may want to consider TQuery instead on your next project. The flexibility of the SQL alone makes it a worthwhile tool--but with the addition of parameters, the TQuery becomes an extremely powerful database component. In this article, we'll discuss TQuery parameters and how to set them in your code.
Figure A: The TQuery component has these properties.
Listing A shows a simple SQL statement. It selects all fields (columns) from the underlying database table (Measure.db), but only those records (rows) that have the string DegF in the Units field. You'd use such a statement if the user wanted to see only records that deal with temperature in degrees Fahrenheit.
Listing A: A simple SQL statement
SELECT * FROM Measure.db WHERE Units = "DegF"As I mentioned earlier, you could perform a similar task with a TTable by having another table serve as the master. You'd link the two tables to form a master/slave relationship tied through the Units field. When the user selected a record in the master, that record's Units field would automatically be used to pull out only those records in the slave's table with matching Units values.
That technique requires another table and depends on the user to select the master's records (although it's possible to select the master's records through code). There's a much more straightforward approach that requires fewer components and links. It makes use of the TQuery component instead of the TTable and also introduces parameters into the SQL statement.
Listing B: A simple SQL statement with a parameter
SELECT * FROM Measure.db WHERE Units = :UnitsWhere exactly does the :Units parameter get its value? You set its value in your code with just a few simple lines. The link between your TQuery component and the underlying table is made for you automatically when you specify both the TQuery's Database property and the table name in the SQL statement's FROM section.
As you can see in Figure A, my example database is called Articles; the Measure.db table in that database will supply the data. Measure.db has the following fields: MeasId, Description, Units, Chassis, Slot, Channel, and Reading. The Units field will be searched for a match to the parameter value. Only those records in Measure.db whose Units value matches the :Units value will be displayed in the grid.
Figure B: You define a parameter in the TQuery's Parameters dialog box.
Listing C: Building an SQL statement with a parameter at runtime
Query1->Close(); Query1->UnPrepare(); Query1->SQL->Clear(); Query1->SQL->Add ( "SELECT * FROM Measure.db WHERE Units = :Units" );The next line "unprepares" the query. Before a query is executed, it's best to "prepare" the underlying database. Doing so lets the database know ahead of time that a query is coming, so it can optimize the way it will handle the query. You need to prepare the query only the first time you open it, as long as the query doesn't change. Therefore, before you make any changes, it's a good idea to unprepare the query so the underlying database can release any resources that it set aside for the query's repeated execution.
Next in the example code comes the actual SQL property, which is nothing more than a TStrings object. To ensure that any previous query is erased, you clear the SQL value. You then write the new query to the SQL property using TStrings' Add() method.
You may have noticed that you haven't yet opened (activated) the query--at this point, it still lacks one thing. The SQL statement contains a reference to a parameter called :Units. But so far, you haven't set the value of the parameter--and you don't have to until the query is opened.
Actually, if you wanted to check the database before running the program, you could make an entry in the dialog box's Value field and set the TQuery's Active property to true. Doing so would cause the appropriate records to be pulled from the database (according to the SQL statement) and displayed in my design-time grid. To see records with other Units values at design-time, you'd deactivate the TQuery (Active=false), change the contents of the Parameters dialog box's Value field, and reactivate the query (Active=true).
Listing D: Setting the Params property at runtime
Query1->Close(); Query1->UnPrepare(); Query1->Params->ParamByName ( "Units" )->AsString = "DegF"; Query1->Prepare(); Query1->Open();Again, it's a good practice to make sure the query is closed and unprepared before making changes to properties. Next, the code searches for the current parameter ("Units") using the function ParamByName(), since there can be multiple parameters. The ParamByName() function exposes a TString property that you can set to the desired value ("DegF"). After the parameter is set, the code prepares the query to allow the underlying database to do any optimizations. Finally, the last line activates (opens) the query. The records will now be pulled from the database and displayed in your grid. Whenever the user wants a different type of measurement, you simply re-execute this code with the appropriate new value in place of "DegF".
Figure C: Our sample interface lets the user select records by clicking buttons on
this form.
Depending on the groups of buttons selected, the application sets two SQL statements. If the user clicks the All button, the application uses the code from Listing E to display all records. As you can see, this SQL statement doesn't use a parameter.
Listing E: Displaying all records at runtime
Query1->Close(); Query1->UnPrepare(); Query1->SQL->Clear(); Query1->SQL->Add ( "SELECT * FROM Measure.db" ); Query1->Prepare(); Query1->Open();You'll notice in Figure C that the buttons other than All are grouped together. These grouped buttons use the SQL statement from Listing C. This query depends on a parameter that must be set before the TQuery can be opened. The user must click the Select *... button first, to establish the SQL statement and create the parameter. After that, the user can click the DegF, PSIG, and Strain buttons to set the :Units parameter value. Once the parameter value is set, the application can activate (open) the query and display the records. For example, when the user clicks the DegF button, the application runs the event handler shown in Listing F.
Listing F: Setting the parameter and opening the query
// If the current query doesn't use any
// parameters, let the user know.
if ( !Query1->ParamCount )
{
Application->MessageBox(
"Set SQL statement first.",
"SQL Info", MB_OK |
MB_ICONEXCLAMATION );
return;
}
// Setup and execute the hard-coded Param value.
Query1->Close();
Query1->UnPrepare();
Query1->Params->ParamByName
( "Units" )->AsString = "DegF";
Query1->Prepare();
Query1->Open();
The code first checks to be sure the correct SQL statement has been loaded, in
case the All button's query is currently selected. Since that query doesn't
take any parameters, trying to set a parameter would produce a runtime error.
The rest of the code should look familiar from our discussion of Listing
D. At this point, the grid will display only those records in which the
Units field contains the value DegF.