36 |
kaklik |
1 |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
|
|
2 |
|
|
|
3 |
<html>
|
|
|
4 |
<head>
|
|
|
5 |
<title>Tips on Writing Portable SQL for Multiple Databases for PHP</title>
|
|
|
6 |
</head>
|
|
|
7 |
|
|
|
8 |
<body bgcolor=white>
|
|
|
9 |
<table width=100% border=0><tr><td><h2>Tips on Writing Portable SQL </h2></td><td>
|
|
|
10 |
<div align=right><img src="cute_icons_for_site/adodb.gif"></div></td></tr></table>
|
|
|
11 |
<p>Updated 18 Sep 2003. Added Portable Native SQL section.
|
|
|
12 |
<p>
|
|
|
13 |
|
|
|
14 |
If you are writing an application that is used in multiple environments and
|
|
|
15 |
operating systems, you need to plan to support multiple databases. This article
|
|
|
16 |
is based on my experiences with multiple database systems, stretching from 4th
|
|
|
17 |
Dimension in my Mac days, to the databases I currently use, which are: Oracle,
|
|
|
18 |
FoxPro, Access, MS SQL Server and MySQL. Although most of the advice here applies
|
|
|
19 |
to using SQL with Perl, Python and other programming languages, I will focus on PHP and how
|
|
|
20 |
the <a href="http://adodb.sourceforge.net/">ADOdb</a> database abstraction library
|
|
|
21 |
offers some solutions.<p></p>
|
|
|
22 |
<p>Most database vendors practice product lock-in. The best or fastest way to
|
|
|
23 |
do things is often implemented using proprietary extensions to SQL. This makes
|
|
|
24 |
it extremely hard to write portable SQL code that performs well under all conditions.
|
|
|
25 |
When the first ANSI committee got together in 1984 to standardize SQL, the database
|
|
|
26 |
vendors had such different implementations that they could only agree on the
|
|
|
27 |
core functionality of SQL. Many important application specific requirements
|
|
|
28 |
were not standardized, and after so many years since the ANSI effort began,
|
|
|
29 |
it looks as if much useful database functionality will never be standardized.
|
|
|
30 |
Even though ANSI-92 SQL has codified much more, we still have to implement portability
|
|
|
31 |
at the application level.</p>
|
|
|
32 |
<h3><b>Selects</b></h3>
|
|
|
33 |
<p>The SELECT statement has been standardized to a great degree. Nearly every
|
|
|
34 |
database supports the following:</p>
|
|
|
35 |
<p>SELECT [cols] FROM [tables]<br>
|
|
|
36 |
[WHERE conditions]<br>
|
|
|
37 |
[GROUP BY cols]<br>
|
|
|
38 |
[HAVING conditions] <br>
|
|
|
39 |
[ORDER BY cols]</p>
|
|
|
40 |
<p>But so many useful techniques can only be implemented by using proprietary
|
|
|
41 |
extensions. For example, when writing SQL to retrieve the first 10 rows for
|
|
|
42 |
paging, you could write...</p>
|
|
|
43 |
<table width="80%" border="1" cellspacing="0" cellpadding="0" align="center">
|
|
|
44 |
<tr>
|
|
|
45 |
<td><b>Database</b></td>
|
|
|
46 |
<td><b>SQL Syntax</b></td>
|
|
|
47 |
</tr>
|
|
|
48 |
<tr>
|
|
|
49 |
<td>DB2</td>
|
|
|
50 |
<td>select * from table fetch first 10 rows only</td>
|
|
|
51 |
</tr>
|
|
|
52 |
<tr>
|
|
|
53 |
<td>Informix</td>
|
|
|
54 |
<td>select first 10 * from table</td>
|
|
|
55 |
</tr>
|
|
|
56 |
<tr>
|
|
|
57 |
<td>Microsoft SQL Server and Access</td>
|
|
|
58 |
<td>select top 10 * from table</td>
|
|
|
59 |
</tr>
|
|
|
60 |
<tr>
|
|
|
61 |
<td>MySQL and PostgreSQL</td>
|
|
|
62 |
<td>select * from table limit 10</td>
|
|
|
63 |
</tr>
|
|
|
64 |
<tr>
|
|
|
65 |
<td>Oracle 8i</td>
|
|
|
66 |
<td>select * from (select * from table) where rownum <= 10</td>
|
|
|
67 |
</tr>
|
|
|
68 |
</table>
|
|
|
69 |
<p>This feature of getting a subset of data is so useful that in the PHP class
|
|
|
70 |
library ADOdb, we have a SelectLimit( ) function that allows you to hide the
|
|
|
71 |
implementation details within a function that will rewrite your SQL for you:</p>
|
|
|
72 |
<pre>$connection->SelectLimit('select * from table', 10);
|
|
|
73 |
</pre>
|
|
|
74 |
<p><b>Selects: Fetch Modes</b></p>
|
|
|
75 |
<p>PHP allows you to retrieve database records as arrays. You can choose to have
|
|
|
76 |
the arrays indexed by field name or number. However different low-level PHP
|
|
|
77 |
database drivers are inconsistent in their indexing efforts. ADOdb allows you
|
|
|
78 |
to determine your prefered mode. You set this by setting the variable $ADODB_FETCH_MODE
|
|
|
79 |
to either of the constants ADODB_FETCH_NUM (for numeric indexes) or ADODB_FETCH_ASSOC
|
|
|
80 |
(using field names as an associative index).</p>
|
|
|
81 |
<p>The default behaviour of ADOdb varies depending on the database you are using.
|
|
|
82 |
For consistency, set the fetch mode to either ADODB_FETCH_NUM (for speed) or
|
|
|
83 |
ADODB_FETCH_ASSOC (for convenience) at the beginning of your code. </p>
|
|
|
84 |
<p><b>Selects: Counting Records</b></p>
|
|
|
85 |
<p>Another problem with SELECTs is that some databases do not return the number
|
|
|
86 |
of rows retrieved from a select statement. This is because the highest performance
|
|
|
87 |
databases will return records to you even before the last record has been found.
|
|
|
88 |
</p>
|
|
|
89 |
<p>In ADOdb, RecordCount( ) returns the number of rows returned, or will emulate
|
|
|
90 |
it by buffering the rows and returning the count after all rows have been returned.
|
|
|
91 |
This can be disabled for performance reasons when retrieving large recordsets
|
|
|
92 |
by setting the global variable $ADODB_COUNTRECS = false. This variable is checked
|
|
|
93 |
every time a query is executed, so you can selectively choose which recordsets
|
|
|
94 |
to count.</p>
|
|
|
95 |
<p>If you prefer to set $ADODB_COUNTRECS = false, ADOdb still has the PO_RecordCount(
|
|
|
96 |
) function. This will return the number of rows, or if it is not found, it will
|
|
|
97 |
return an estimate using SELECT COUNT(*):</p>
|
|
|
98 |
<pre>$rs = $db->Execute("select * from table where state=$state");
|
|
|
99 |
$numrows = $rs->PO_RecordCount('table', "state=$state");</pre>
|
|
|
100 |
<p><b>Selects: Locking</b> </p>
|
|
|
101 |
<p>SELECT statements are commonly used to implement row-level locking of tables.
|
|
|
102 |
Other databases such as Oracle, Interbase, PostgreSQL and MySQL with InnoDB
|
|
|
103 |
do not require row-level locking because they use versioning to display data
|
|
|
104 |
consistent with a specific point in time.</p>
|
|
|
105 |
<p>Currently, I recommend encapsulating the row-level locking in a separate function,
|
|
|
106 |
such as RowLock($table, $where):</p>
|
|
|
107 |
<pre>$connection->BeginTrans( );
|
|
|
108 |
$connection->RowLock($table, $where); </pre>
|
|
|
109 |
<pre><font color=green># some operation</font></pre>
|
|
|
110 |
<pre>if ($ok) $connection->CommitTrans( );
|
|
|
111 |
else $connection->RollbackTrans( );
|
|
|
112 |
</pre>
|
|
|
113 |
<p><b>Selects: Outer Joins</b></p>
|
|
|
114 |
<p>Not all databases support outer joins. Furthermore the syntax for outer joins
|
|
|
115 |
differs dramatically between database vendors. One portable (and possibly slower)
|
|
|
116 |
method of implementing outer joins is using UNION.</p>
|
|
|
117 |
<p>For example, an ANSI-92 left outer join between two tables t1 and t2 could
|
|
|
118 |
look like:</p>
|
|
|
119 |
<pre>SELECT t1.col1, t1.col2, t2.cola <br> FROM t1 <i>LEFT JOIN</i> t2 ON t1.col = t2.col</pre>
|
|
|
120 |
<p>This can be emulated using:</p>
|
|
|
121 |
<pre>SELECT t1.col1, t1.col2, t2.cola FROM t1, t2 <br> WHERE t1.col = t2.col
|
|
|
122 |
UNION ALL
|
|
|
123 |
SELECT col1, col2, null FROM t1 <br> WHERE t1.col not in (select distinct col from t2)
|
|
|
124 |
</pre>
|
|
|
125 |
<p>Since ADOdb 2.13, we provide some hints in the connection object as to legal
|
|
|
126 |
join variations. This is still incomplete and sometimes depends on the database
|
|
|
127 |
version you are using, but is useful as a general guideline:</p>
|
|
|
128 |
<p><font face="Courier New, Courier, mono">$conn->leftOuter</font>: holds the
|
|
|
129 |
operator used for left outer joins (eg. '*='), or false if not known or not
|
|
|
130 |
available.<br>
|
|
|
131 |
<font face="Courier New, Courier, mono">$conn->rightOuter</font>: holds the
|
|
|
132 |
operator used for right outer joins (eg '=*'), or false if not known or not
|
|
|
133 |
available.<br>
|
|
|
134 |
<font face="Courier New, Courier, mono">$conn->ansiOuter</font>: boolean
|
|
|
135 |
that if true means that ANSI-92 style outer joins are supported, or false if
|
|
|
136 |
not known.</p>
|
|
|
137 |
<h3><b>Inserts</b> </h3>
|
|
|
138 |
<p>When you create records, you need to generate unique id's for each record.
|
|
|
139 |
There are two common techniques: (1) auto-incrementing columns and (2) sequences.
|
|
|
140 |
</p>
|
|
|
141 |
<p>Auto-incrementing columns are supported by MySQL, Sybase and Microsoft Access
|
|
|
142 |
and SQL Server. However most other databases do not support this feature. So
|
|
|
143 |
for portability, you have little choice but to use sequences. Sequences are
|
|
|
144 |
special functions that return a unique incrementing number every time you call
|
|
|
145 |
it, suitable to be used as database keys. In ADOdb, we use the GenID( ) function.
|
|
|
146 |
It has takes a parameter, the sequence name. Different tables can have different
|
|
|
147 |
sequences. </p>
|
|
|
148 |
<pre>$id = $connection->GenID('sequence_name');<br>$connection->Execute("insert into table (id, firstname, lastname) <br> values ($id, $firstname, $lastname)");</pre>
|
|
|
149 |
<p>For databases that do not support sequences natively, ADOdb emulates sequences
|
|
|
150 |
by creating a table for every sequence.</p>
|
|
|
151 |
<h3><b>Binding</b></h3>
|
|
|
152 |
<p>Binding variables in an SQL statement is another tricky feature. Binding is
|
|
|
153 |
useful because it allows pre-compilation of SQL. When inserting multiple records
|
|
|
154 |
into a database in a loop, binding can offer a 50% (or greater) speedup. However
|
|
|
155 |
many databases such as Access and MySQL do not support binding natively and
|
|
|
156 |
there is some overhead in emulating binding. Furthermore, different databases
|
|
|
157 |
(specificly Oracle!) implement binding differently. My recommendation is to
|
|
|
158 |
use binding if your database queries are too slow, but make sure you are using
|
|
|
159 |
a database that supports it like Oracle. </p>
|
|
|
160 |
<p>ADOdb supports portable Prepare/Execute with:</p>
|
|
|
161 |
<pre>$stmt = $db->Prepare('select * from customers where custid=? and state=?');
|
|
|
162 |
$rs = $db->Execute($stmt, array($id,'New York'));</pre>
|
|
|
163 |
<p>Oracle uses named bind placeholders, not "?", so to support portable binding, we have Param() that generates
|
|
|
164 |
the correct placeholder (available since ADOdb 3.92):
|
|
|
165 |
<pre><font color="#000000">$sql = <font color="#993300">'insert into table (col1,col2) values ('</font>.$DB->Param('a').<font color="#993300">','</font>.$DB->Param('b').<font color="#993300">')'</font>;
|
|
|
166 |
<font color="#006600"># generates 'insert into table (col1,col2) values (?,?)'
|
|
|
167 |
# or 'insert into table (col1,col2) values (:a,:b)</font>'
|
|
|
168 |
$stmt = $DB->Prepare($sql);
|
|
|
169 |
$stmt = $DB->Execute($stmt,array('one','two'));
|
|
|
170 |
</font></pre>
|
|
|
171 |
<a name="native"></a>
|
|
|
172 |
<h2>Portable Native SQL</h2>
|
|
|
173 |
<p>ADOdb provides the following functions for portably generating SQL functions
|
|
|
174 |
as strings to be merged into your SQL statements (some are only available since
|
|
|
175 |
ADOdb 3.92): </p>
|
|
|
176 |
<table width="75%" border="1" align=center>
|
|
|
177 |
<tr>
|
|
|
178 |
<td width=30%><b>Function</b></td>
|
|
|
179 |
<td><b>Description</b></td>
|
|
|
180 |
</tr>
|
|
|
181 |
<tr>
|
|
|
182 |
<td>DBDate($date)</td>
|
|
|
183 |
<td>Pass in a UNIX timestamp or ISO date and it will convert it to a date
|
|
|
184 |
string formatted for INSERT/UPDATE</td>
|
|
|
185 |
</tr>
|
|
|
186 |
<tr>
|
|
|
187 |
<td>DBTimeStamp($date)</td>
|
|
|
188 |
<td>Pass in a UNIX timestamp or ISO date and it will convert it to a timestamp
|
|
|
189 |
string formatted for INSERT/UPDATE</td>
|
|
|
190 |
</tr>
|
|
|
191 |
<tr>
|
|
|
192 |
<td>SQLDate($date, $fmt)</td>
|
|
|
193 |
<td>Portably generate a date formatted using $fmt mask, for use in SELECT
|
|
|
194 |
statements.</td>
|
|
|
195 |
</tr>
|
|
|
196 |
<tr>
|
|
|
197 |
<td>OffsetDate($date, $ndays)</td>
|
|
|
198 |
<td>Portably generate a $date offset by $ndays.</td>
|
|
|
199 |
</tr>
|
|
|
200 |
<tr>
|
|
|
201 |
<td>Concat($s1, $s2, ...)</td>
|
|
|
202 |
<td>Portably concatenate strings. Alternatively, for mssql use mssqlpo driver,
|
|
|
203 |
which allows || operator.</td>
|
|
|
204 |
</tr>
|
|
|
205 |
<tr>
|
|
|
206 |
<td>IfNull($fld, $replaceNull)</td>
|
|
|
207 |
<td>Returns a string that is the equivalent of MySQL IFNULL or Oracle NVL.</td>
|
|
|
208 |
</tr>
|
|
|
209 |
<tr>
|
|
|
210 |
<td>Param($name)</td>
|
|
|
211 |
<td>Generates bind placeholders, using ? or named conventions as appropriate.</td>
|
|
|
212 |
</tr>
|
|
|
213 |
<tr><td>$db->sysDate</td><td>Property that holds the SQL function that returns today's date</td>
|
|
|
214 |
</tr>
|
|
|
215 |
<tr><td>$db->sysTimeStamp</td><td>Property that holds the SQL function that returns the current
|
|
|
216 |
timestamp (date+time).
|
|
|
217 |
</td>
|
|
|
218 |
</tr>
|
|
|
219 |
<tr>
|
|
|
220 |
<td>$db->concat_operator</td><td>Property that holds the concatenation operator
|
|
|
221 |
</td>
|
|
|
222 |
</tr>
|
|
|
223 |
<tr><td>$db->length</td><td>Property that holds the name of the SQL strlen function.
|
|
|
224 |
</td></tr>
|
|
|
225 |
|
|
|
226 |
<tr><td>$db->upperCase</td><td>Property that holds the name of the SQL strtoupper function.
|
|
|
227 |
</td></tr>
|
|
|
228 |
<tr><td>$db->random</td><td>Property that holds the SQL to generate a random number between 0.00 and 1.00.
|
|
|
229 |
</td>
|
|
|
230 |
</tr>
|
|
|
231 |
<tr><td>$db->substr</td><td>Property that holds the name of the SQL substring function.
|
|
|
232 |
</td></tr>
|
|
|
233 |
</table>
|
|
|
234 |
<p> </p>
|
|
|
235 |
<h2>DDL and Tuning</h2>
|
|
|
236 |
There are database design tools such as ERWin or Dezign that allow you to generate data definition language commands such as ALTER TABLE or CREATE INDEX from Entity-Relationship diagrams.
|
|
|
237 |
<p>
|
|
|
238 |
However if you prefer to use a PHP-based table creation scheme, adodb provides you with this feature. Here is the code to generate the SQL to create a table with:
|
|
|
239 |
<ol>
|
|
|
240 |
<li> Auto-increment primary key 'ID', </li>
|
|
|
241 |
<li>The person's 'NAME' VARCHAR(32) NOT NULL and defaults to '', </li>
|
|
|
242 |
<li>The date and time of record creation 'CREATED', </li>
|
|
|
243 |
<li> The person's 'AGE', defaulting to 0, type NUMERIC(16). </li>
|
|
|
244 |
</ol>
|
|
|
245 |
<p>
|
|
|
246 |
Also create a compound index consisting of 'NAME' and 'AGE':
|
|
|
247 |
<pre>
|
|
|
248 |
$datadict = <strong>NewDataDictionary</strong>($connection);
|
|
|
249 |
$flds = "
|
|
|
250 |
<font color="#660000"> ID I AUTOINCREMENT PRIMARY,
|
|
|
251 |
NAME C(32) DEFAULT '' NOTNULL,
|
|
|
252 |
CREATED T DEFTIMESTAMP,
|
|
|
253 |
AGE N(16) DEFAULT 0</font>
|
|
|
254 |
";
|
|
|
255 |
$sql1 = $datadict-><strong>CreateTableSQL</strong>('tabname', $flds);
|
|
|
256 |
$sql2 = $datadict-><strong>CreateIndexSQL</strong>('idx_name_age', 'tabname', 'NAME,AGE');
|
|
|
257 |
</pre>
|
|
|
258 |
|
|
|
259 |
<h3>Data Types</h3>
|
|
|
260 |
<p>Stick to a few data types that are available in most databases. Char, varchar
|
|
|
261 |
and numeric/number are supported by most databases. Most other data types (including
|
|
|
262 |
integer, boolean and float) cannot be relied on being available. I recommend
|
|
|
263 |
using char(1) or number(1) to hold booleans. </p>
|
|
|
264 |
<p>Different databases have different ways of representing dates and timestamps/datetime.
|
|
|
265 |
ADOdb attempts to display all dates in ISO (YYYY-MM-DD) format. ADOdb also provides
|
|
|
266 |
DBDate( ) and DBTimeStamp( ) to convert dates to formats that are acceptable
|
|
|
267 |
to that database. Both functions accept Unix integer timestamps and date strings
|
|
|
268 |
in ISO format.</p>
|
|
|
269 |
<pre>$date1 = $connection->DBDate(time( ));<br>$date2 = $connection->DBTimeStamp('2002-02-23 13:03:33');</pre>
|
|
|
270 |
<p>We also provide functions to convert database dates to Unix timestamps:</p>
|
|
|
271 |
<pre>$unixts = $recordset->UnixDate('#2002-02-30#'); <font color="green"># MS Access date => unix timestamp</font></pre>
|
|
|
272 |
<p>The maximum length of a char/varchar field is also database specific. You can
|
|
|
273 |
only assume that field lengths of up to 250 characters are supported. This is
|
|
|
274 |
normally impractical for web based forum or content management systems. You
|
|
|
275 |
will need to be familiar with how databases handle large objects (LOBs). ADOdb
|
|
|
276 |
implements two functions, UpdateBlob( ) and UpdateClob( ) that allow you to
|
|
|
277 |
update fields holding Binary Large Objects (eg. pictures) and Character Large
|
|
|
278 |
Objects (eg. HTML articles):</p>
|
|
|
279 |
<pre><font color=green># for oracle </font>
|
|
|
280 |
$conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1,empty_blob())');
|
|
|
281 |
$conn->UpdateBlob('blobtable','blobcol',$blobvalue,'id=1');
|
|
|
282 |
|
|
|
283 |
<font color=green># non-oracle databases</font>
|
|
|
284 |
$conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
|
|
|
285 |
$conn->UpdateBlob('blobtable','blobcol',$blobvalue,'id=1');
|
|
|
286 |
</pre>
|
|
|
287 |
<p>Null handling is another area where differences can occur. This is a mine-field,
|
|
|
288 |
because 3-value logic is tricky.
|
|
|
289 |
<p>In general, I avoid using nulls except for dates and default all my numeric
|
|
|
290 |
and character fields to 0 or the empty string. This maintains consistency with
|
|
|
291 |
PHP, where empty strings and zero are treated as equivalent, and avoids SQL
|
|
|
292 |
ambiguities when you use the ANY and EXISTS operators. However if your database
|
|
|
293 |
has significant amounts of missing or unknown data, using nulls might be a good
|
|
|
294 |
idea.
|
|
|
295 |
<p>
|
|
|
296 |
ADOdb also supports a portable <a href=http://phplens.com/adodb/reference.functions.concat.html#ifnull>IfNull</a> function, so you can define what to display
|
|
|
297 |
if the field contains a null.
|
|
|
298 |
<h3><b>Stored Procedures</b></h3>
|
|
|
299 |
<p>Stored procedures are another problem area. Some databases allow recordsets
|
|
|
300 |
to be returned in a stored procedure (Microsoft SQL Server and Sybase), and
|
|
|
301 |
others only allow output parameters to be returned. Stored procedures sometimes
|
|
|
302 |
need to be wrapped in special syntax. For example, Oracle requires such code
|
|
|
303 |
to be wrapped in an anonymous block with BEGIN and END. Also internal sql operators
|
|
|
304 |
and functions such as +, ||, TRIM( ), SUBSTR( ) or INSTR( ) vary between vendors.
|
|
|
305 |
</p>
|
|
|
306 |
<p>An example of how to call a stored procedure with 2 parameters and 1 return
|
|
|
307 |
value follows:</p>
|
|
|
308 |
<pre> switch ($db->databaseType) {
|
|
|
309 |
case '<font color="#993300">mssql</font>':
|
|
|
310 |
$sql = <font color="#000000"><font color="#993333">'<font color="#993300">SP_RUNSOMETHING</font>'</font></font>; break;
|
|
|
311 |
case '<font color="#993300">oci8</font>':
|
|
|
312 |
$sql =
|
|
|
313 |
<font color="#993300"> </font><font color="#000000"><font color="#993300">"declare RETVAL integer;begin :RETVAL := </font><font color="#000000"><font color="#993333"><font color="#993300">SP_RUNSOMETHING</font></font></font><font color="#993300">(:myid,:group);end;";
|
|
|
314 |
</font> break;</font>
|
|
|
315 |
default:
|
|
|
316 |
die('<font color="#993300">Unsupported feature</font>');
|
|
|
317 |
}
|
|
|
318 |
<font color="#000000"><font color="green"> # @RETVAL = SP_RUNSOMETHING @myid,@group</font>
|
|
|
319 |
$stmt = $db->PrepareSP($sql); <br> $db->Parameter($stmt,$id,'<font color="#993300">myid</font>');
|
|
|
320 |
$db->Parameter($stmt,$group,'<font color="#993300">group</font>');
|
|
|
321 |
<font color="green"># true indicates output parameter<br> </font>$db->Parameter($stmt,$ret,'<font color="#993300">RETVAL</font>',true);
|
|
|
322 |
$db->Execute($stmt); </font></pre>
|
|
|
323 |
<p>As you can see, the ADOdb API is the same for both databases. But the stored
|
|
|
324 |
procedure SQL syntax is quite different between databases and is not portable,
|
|
|
325 |
so be forewarned! However sometimes you have little choice as some systems only
|
|
|
326 |
allow data to be accessed via stored procedures. This is when the ultimate portability
|
|
|
327 |
solution might be the only solution: <i>treating portable SQL as a localization
|
|
|
328 |
exercise...</i></p>
|
|
|
329 |
<h3><b>SQL as a Localization Exercise</b></h3>
|
|
|
330 |
<p> In general to provide real portability, you will have to treat SQL coding
|
|
|
331 |
as a localization exercise. In PHP, it has become common to define separate
|
|
|
332 |
language files for English, Russian, Korean, etc. Similarly, I would suggest
|
|
|
333 |
you have separate Sybase, Intebase, MySQL, etc files, and conditionally include
|
|
|
334 |
the SQL based on the database. For example, each MySQL SQL statement would be
|
|
|
335 |
stored in a separate variable, in a file called 'mysql-lang.inc.php'.</p>
|
|
|
336 |
<pre>$sqlGetPassword = '<font color="#993300">select password from users where userid=%s</font>';
|
|
|
337 |
$sqlSearchKeyword = "<font color="#993300">SELECT * FROM articles WHERE match (title,body) against (%s</font>)";</pre>
|
|
|
338 |
<p>In our main PHP file:</p>
|
|
|
339 |
<pre><font color=green># define which database to load...</font>
|
|
|
340 |
<b>$database = '<font color="#993300">mysql</font>';
|
|
|
341 |
include_once("<font color="#993300">$database-lang.inc.php</font>");</b>
|
|
|
342 |
|
|
|
343 |
$db = &NewADOConnection($database);
|
|
|
344 |
$db->PConnect(...) or die('<font color="#993300">Failed to connect to database</font>');
|
|
|
345 |
|
|
|
346 |
<font color=green># search for a keyword $word</font>
|
|
|
347 |
$rs = $db->Execute(sprintf($sqlSearchKeyWord,$db->qstr($word)));</pre>
|
|
|
348 |
<p>Note that we quote the $word variable using the qstr( ) function. This is because
|
|
|
349 |
each database quotes strings using different conventions.</p>
|
|
|
350 |
<p>
|
|
|
351 |
<h3>Final Thoughts</h3>
|
|
|
352 |
<p>The best way to ensure that you have portable SQL is to have your data tables designed using
|
|
|
353 |
sound principles. Learn the theory of normalization and entity-relationship diagrams and model
|
|
|
354 |
your data carefully. Understand how joins and indexes work and how they are used to tune performance.
|
|
|
355 |
<p> Visit the following page for more references on database theory and vendors:
|
|
|
356 |
<a href="http://php.weblogs.com/sql_tutorial">http://php.weblogs.com/sql_tutorial</a>.
|
|
|
357 |
Also read this article on <a href=http://phplens.com/lens/php-book/optimizing-debugging-php.php>Optimizing PHP</a>.
|
|
|
358 |
<p>
|
|
|
359 |
<font size=1>(c) 2002-2003 John Lim.</font>
|
|
|
360 |
|
|
|
361 |
</body>
|
|
|
362 |
</html>
|