Can I write portable SQL statements?

No, not for any significant and practical amount of code anyway. You can try to hew to standards (e.g., use COALESCE rather than ISNULL) but there are too many differences, big and small. Off the top of my head:

  • SQL Server supports double quotes and square brackets for identifiers; MySQL uses backticks.
  • SQL Server supports TOP, most other databases use LIMIT.
  • PostgreSQL only relatively recently implemented stored procedures, though in practice you could use functions instead.
  • MySQL is case-sensitive for table names (but not field names) when running on UNIX, but not when running on Windows. SQL Server is case-insensitive either way (except when it is).
  • CTEs and windowed functions are not available on all systems, and are not always implemented the same.
  • SQL Server does not require a command delimiter (except when it does), but MySQL and Oracle do. MySQL also requires using an alternate delimiters when defining stored procedures; MS SQL does not support any such thing.
  • Security is almost always different for different vendors.
  • Error handling is always different.
  • All of the above can change, and may have since I last worked with those systems.

Many people have written software with the goal of letting one write RDBMS-agnostic queries. Most of these experiments failed, and a few broke out of the lab, lurching across the landscape spreading destruction in their wake. But even the best won't have the performance of code written with the target system in mind.


There are ANSI SQL standards, see for example the part on Interoperability and standardization in the Wikipedia article. Problem is, few actually follow these standards, which are often written and created post fact, when years of history has already tied the hands of various database products to do things differently.

Not all is lost, however. With modest goals, such as a web application with little need of complex queries and reporting, it is an achievable goal to have a list of database backends you support. For example, your list above. Only add minimum version numbers to it so you know what you actually aim to support and test for. Test, I'm afraid, you must.

In your application code, expect to limit yourself to very basic SELECTs, UPDATEs and INSERTs.

  • Do find a database abstraction layer that allows you to have parameterized, prepared queries. Escaping strings can vary wildly, even based on what settings are currently enabled in one given database product. If you must include a fixed string literal, ensure it's 'single-quoted' and can't contain control characters, nulls, backslashes, quotes and such.

  • Ensure all your identifiers - table, column names, aliases - cannot possibly be reserved keywords (from, select, left, count and so on), essentially avoiding all simple English words. Otherwise you'd need to quote them, and that's a can of worms. Best keep all of them all lowercase, but don't expect that you'll get them returned back with that casing.

  • Don't count on any SQL function other than the common aggregate functions in a GROUP BY query. Basically, COUNT(), MIN(), MAX(), SUM()

  • Addition, subtraction, multiplication of numbers are generally safe, discounting data type range limits. Don't expect to use division or modulus, and most especially don't try to concatenate strings on the SQL server side. All of them could do it, of course, but in slightly different ways.

  • Don't attempt to use the LIKE operator.

  • Expect to ORDER BY only plain numbers, and keep ordering by strings on the application side. Support for collations vary greatly. If the column you order by can contain NULLs, expect that those could either order up top or the very bottom.

  • If you must store binary data (BLOB, VARBINARY etc.) in the database, expect having to painstakingly test and handle differences between all your supported database backends one by one, both for retrieval and storage.

If you stick to these, then the bulk of your work will be on the DDL side, creating your database, defining your tables, hand-customized for each database you chose to support. Generally everything supports VIEWs these days, so you might even be able to abstract away differences in functions, operators, and provide consistent views to your application, that look to your code as if it were the same "table," despite variations in how you had to define it for each database.

Pain points to look out for:

  • Stick to signed, 32-bit or 64-bit integers and bigints. You'll need a lot of extra care if you must have decimal numbers. Not impossible though by any means with a defined list of backends you support.

  • Textual values character set and length. These days, you want to be able to correctly store and handle everything, including emojis. Test with those, and figure out what's needed. Eg. MySQL historically, and MariaDB still, calls what you need utf8mb4, and base utf8 won't do. In Microsoft SQL Server you want an _SC collation and only use NCHAR/NVARCHAR fields, alternatively starting with version 15 (2019) _SC_UTF8. Ensure you have made them large enough to hold as many characters as you expect. A utf-8 char(4) can only hold a single emoji (with no modifiers), not four. Be wary of large sizes though if you must have any kind of INDEX on a text column, as max limits can be painfully low.

  • Textual values, collation. Even if you took it to heart where I said previously, and don't rely on ordering by the database server, the collation still comes into play when determining equality. That matters both for selecting by value equivalence and unique keys! Always be cognizant and test that you're getting what you need. Case sensitivity or not, accent sensitivity, and so on. Achieving the desired result will vary greatly between various DBs, but is generally possible with some caveats. Do expect to spend plenty of time on this.

  • Obviously forget more esoteric types. Sets, enums, XML, arrays, whatnot.

  • Having a UNIQUE key on a NULLable column might allow for any number of NULL values, or precisely one, depending on database system. But you can retrofit and handle this in the database definition part to function the way you desire.

Also don't think you can as readily lump MariaDB and MySQL together these days. They have diverged in significant ways by now. Handle and test for them as if they were separate. Tools like dbfiddle are wonderfully useful.

Whether all the effort is worth it, in light of easily ending up with a lowest common denominator solution that does not really take advantage of the strengths of any specific database backend, is a question you have to answer for yourself. Many blogging, CMS and similar systems have found it useful to, for example, support both MySQL and PostgreSQL at least.


For sufficiently trivial statements - sure, yes.

SELECT field FROM table

should work everywhere, if you get your case right because some of those DBs are case-sensitive.

For anything that you are likely to need in an actual application - the other answers are spot-on.

Tags:

Migration