Helping ordinary people create extraordinary websites!

Creating a PostgreSQL and MySQL driver

By Tony Marston
2005-09-20

SQL queries

As well as differences in the syntax for creating tables there is also a difference in the syntax of various SQL queries.

CONCAT

The following in MySQL:

SELECT CONCAT(first_name, ' ', last_name) AS person_name FROM ....

has to be converted to the following in PostgreSQL:

SELECT first_name || last_name AS person_name FROM ....

I can deal with this difference by including a new function in the PostgreSQL class to perform the necessary conversion, thus enabling me to leave the original MySQL syntax intact.

JOIN

The following query produces ERROR: JOIN/ON clause refers to "a", which is not part of JOIN.

SELECT a.a_id, b.b_id, b.b_desc, x.a_id

FROM a, b
LEFT JOIN x ON (x.b_id = b.b_id AND x.a_id = a.a_id)
WHERE (a.a_id = 'whatever')

This was cured by changing it to the following:

SELECT a.a_id, b.b_id, b.b_desc, x.a_id

FROM a, b
LEFT JOIN x ON (x.b_id = b.b_id AND x.a_id = 'whatever')
WHERE (a.a_id = 'whatever')

This error seems rather dubious to me as it may not be possible to have every part of the ON clause limited to the two tables 'b' and 'x'. Provided that at least one JOIN condition specifies those two tables then any other JOIN condition should not be so limited. In the above example I was able to get around the problem by specifying a literal instead of a column name, but this may not be possible in all circumstances.

In another script I also found that changing the order of the table names in the FROM clause produced the same error. Thus FROM b,a fails while FROM a,b works. MySQL would appear to be more intelligent as it uses the ON conditions to work out how to read the joined table rather than insisting that any table name in the ON condition is either the table immediately following the JOIN clause or the table immediately preceding the JOIN clause. This means, for example, that using PostgreSQL I would not be unable to perform a JOIN on anything other than the last table specified in the FROM clause. The following code would therefore be invalid in PostgreSQL:

SELECT a.a_id, b.b_id, b.b_desc, x.a_id, z.z_desc  

FROM a, b
LEFT JOIN z ON (z.z_id = a.z_id)
LEFT JOIN x ON (x.b_id = b.b_id AND x.a_id = 'whatever')
WHERE (a.a_id = 'whatever')

However, this same code DOES work in MySQL, so it's a simple matter to convert all JOIN statements so they work with both databases.

GROUP BY

The following query produces ERROR: column "a.a_desc" must appear in the GROUP BY clause or be used in an aggregate function.

SELECT a.a_id, a.a_desc, COUNT(b.a_id) AS child_count 

FROM a
LEFT JOIN b ON (b.a_id=a.a_id)
GROUP BY a.a_id

This is perfectly valid in MySQL as it does not insist that the GROUP BY clause contains the names of ALL selected columns. This gives performance advantages for common queries. This seems reasonable to me as on table 'a' the column 'a_id' is the primary key while 'a_desc' is merely a textual description and therefore irrelevant as far as any grouping is concerned.

PostgreSQL is not so forgiving, therefore every column in the SELECT clause must also appear in the GROUP BY clause, as in the following:

SELECT a.a_id, a.a_desc, COUNT(b.a_id) AS b_count 

FROM a
LEFT JOIN b ON (b.a_id=a.a_id)
GROUP BY a.a_id, a.a_desc

Just as with the JOIN the solution is to amend the SQL statement until it works with both databases.

INSERT

In standard SQL there are two forms of the INSERT command:

INSERT INTO tablename VALUES ('value1','value2',...) 

INSERT INTO tablename (col1,col2,...) VALUES ('value1','value2',...)

What I like about MySQL is that it also allows UPDATE-style syntax, as follows:

UPDATE tablename SET col1='value1', col2='value2', ...

INSERT INTO tablename SET col1='value1', col2='value2', ...

I prefer this approach as it keeps column names and values together instead of in two separate lists. This seems such a common sense idea that I'm surprised it is not part of the standard already. Why allow 2 formats for INSERT and a completely different 3rd one for UPDATE?

Even though PostgreSQL is not so accommodating, the fact in my infrastructure all QUERY statements are constructed and issued within the relevant database driver means that I can customise each driver according to the whims of the individual database. The remainder of my infrastructure remains totally oblivious to the internal workings of any individual driver.

AUTO_INCREMENT

MySQL makes it very easy to use a technical key in any table. All the DBA need do is include the AUTO_INCREMENT keyword in the table definition, as in the following:

CREATE TABLE `test` (

`id` int(10) unsigned NOT NULL auto_increment,
`desc` varchar(40) default NULL,
PRIMARY KEY (`id`)
);

Although the term AUTO_INCREMENT does not exist in PostgreSQL, it has the equivalent in SERIAL, as in the following:

CREATE TABLE test (

id serial NOT NULL,
desc varchar(40),
PRIMARY KEY (id)
);

The only other difference is the method used to obtain the last number issued. In MySQL there is a simple function:

$id = mysql_insert_id($link);

With PostgreSQL the same can be achieved with:

$query = "SELECT currval('" .$tablename .'_' .$pkey ."_seq')";

$result = pg_query($link, $query) or trigger_error($this, E_USER_ERROR);
$id = pg_fetch_result($result, 0, 0);

This is because the SERIAL keyword makes use of a counter with the default name of <tablename>_<fieldname>_seq. This can be accessed using the currval() and nextval() functions.





Tutorial pages:
 5 Votes

You might also want to check these out:


Leave a Comment on "Creating a PostgreSQL and MySQL driver"
You must be logged in to post a comment.

Link to This Tutorial Page!


GET OUR NEWSLETTERS