One of the most crucial problems in programming applications for several databases is that SQL syntax can be different in many situations. This article demonstrates how dotConnect Universal helps to overcome this issue.
Database applications operate data using SQL statements. Unless entered directly by the user, the statements can be constructed in one of two ways, either hard-coded during development, or constructed at run time. The first way is very convenient for developer, while the second way is far more flexible. dotConnect Universal allows to take best from both approaches: you can hard-code SQL statements that are transformed into appropriate syntax in run time.
Universal capabilities of dotConnect Universal are based on the following features:
Knowing this, you can write truly database-independent SQL code interpreted in run time.
Macro is a set of name, condition and value. Macro evaluates to its value if underlying condition is enabled, or to empty string if the condition is not enabled. Conditions are enabled or disabled depending on DBMS server the UniConnection is connected to, and data provider used. For example, if you connect to Oracle server using dotConnect for Oracle, the following conditions will be enabled:
Consequently, all macros that base on Oracle and OraDirect conditions return their value when used in SQL statements; all other macros return empty string.
For list of available conditions (in other words, predefined macros) refer to the Macros Reference.
From API point of view, macros are represented as UniMacro class. Collections of macros are organized into UniMacroCollection, which can be accessed through Macros property of UniConnection. Each connection has individual set of macros.
The following examples demonstrate usage of macros:
Now suppose we need to do the same on an Oracle server. Due to usage of UniSQL the only thing to add is another macro:
As you see, it is very easy to control SQL statements transformation. Now let's take a look at another example that demonstrates a whole pack of important features:
Supposed that in this sample connection is made to MySQL server, the executed statement would be
SELECT EName, Job, Sal FROM emp LIMIT 0,5
Note: you can use DBMonitor application to see what your query turns into on execution.
A step-by step analysis of the sample reveals following important notes:
dotConnect Universal has set of useful predefined macros that help you write universal statements. Please refer to Macros Reference for more information.
dotConnect Universal provides universal syntax for dates, timestamps, intervals LIKE expressions and quoted identifiers. Its usage is similar to usage of macros. Note that this functionality is not available for OLE DB, ODBC, and DB2 data providers.
Date and time constants
In date/time constants parts of date are separated with hyphen, time parts are separated with colon, and space is expected between the two parts. The following table illustrates date/time format:
Literal type | Format | Example |
---|---|---|
date | yyyy-mm-dd | {date '2006-12-31'} |
time | hh:mm:ss | {time '23:59:59'} |
timestamp | yyyy-mm-dd hh:mm:ss | {timestamp '2006-12-31 23:59:59'} |
The following SQL statement:
SELECT * FROM emp WHERE HIREDATE>{date '1982-01-15'}
in MySQL evaluates to
SELECT * FROM emp WHERE HIREDATE>CAST('1982-01-15' AS DATETIME)
and in Oracle it turns to
SELECT * FROM emp WHERE HIREDATE>TO_DATE('1982-01-15', 'YYYY-MM-DD')
Intervals
Date/time intervals are formatted similar to date/time constants. The following examples demonstrate how to add interval to or subtract from a date:
SELECT HIREDATE+{interval '4' DAY} FROM emp; SELECT HIREDATE+{interval '4 10:20:30' DAY TO SECOND} FROM emp; SELECT HIREDATE-{interval '1-2' YEAR TO MONTH} FROM emp;
The first statement adds 4 days to selected field; the second statement adds 4 days, 10 hours, 20 minutes and 30 seconds; the last statement subtracts 14 months from the field. Note that you cannot combine MONTH or YEAR with any other qualifier in the same interval. Use two intervals instead, or express it in smaller units.
Universal quoting of identifiers
All database servers support quoting for identifiers that contain special symbols like spaces or dots. dotConnect Universal allows to wrap identifiers universally so that quotation is appropriate for every database server. Use the following syntax:
"identifier"
For example, expression "table1"."field1" turns into "table1"."field1" in Oracle and PostgreSQL, into [table1].[field1] in MS SQL Server, and into `table1`.`field1` in MySQL server. Do not confuse with single quotes, which are intended to wrap string constants.
LIKE Escape symbol
dotConnect Universal allows to perform database-independent escaping in LIKE expressions. To do this, add the following declaration after LIKE expression: {escape 'esc_symbol'} where esc_symbol is the symbol that precedes the special character. For example the following query
SELECT * FROM emp WHERE EName LIKE 'Mike\_S%' {escape '\'}
orders server to return all rows that have EName field beginning with Mike_S constant. Here the underscore symbol is not treated as "any character", while the last symbol is interpreted as wildcard.
Comments are inserted in UniSQL with two hyphens (comments out the text till the end of current line). For multiline comment, wrap it into /*...*/ sequences. Example:
--This is a single-line comment /*This one spans over several lines*/
For the purpose of extra flexibility UniSQL supports conditional inclusion of SQL code into resulting statements. This is as simple as that:
{if my_macro} STATEMENT_1 {else} STATEMENT_2 {endif}
If macro my_macro is defined, the STATEMENT_1 is returned, otherwise STATEMENT_2 is the result of the expression. For instance:
{if Oracle} SELECT * FROM dept {else} SELECT * FROM test.dept {endif}
The {else} clause can be omitted. Here is a bit more sophisticated example:
SELECT {if Oracle}RowId, {endif} DeptNo, DName FROM dept
Note that you can use nested {if...} constructs to continue branching.
In some old versions of Oracle server SQL syntax for OUTER JOINs differs from common standard. The problem can be solved using conditional execution, but UniSQL allows to do it even easier with {oj ...} construct:
{oj Outer_Join_Expression}
In other words, if you wrap the OUTER JOIN clause in {oj ... }, and current database server is Oracle (version earlier than 9), the clause is translated to appropriate syntax. For all other servers the OUTER JOIN clause is left as is. The following example demonstrates the rule:
SELECT dept.DName, emp.EName, emp.Job FROM {oj dept LEFT OUTER JOIN emp ON dept.DeptNo = emp.DeptNo}
In Oracle, this query can be transformed to the following one:
SELECT dept.DName, emp.EName, emp.Job FROM dept, emp WHERE dept.DeptNo = emp.DeptNo (+)
In MySQL, the query is not much modified:
SELECT dept.DName, emp.EName, emp.Job FROM dept LEFT OUTER JOIN emp ON dept.DeptNo = emp.DeptNo
Note that for OLE DB and ODBC the transformation does not take place, even if the connected server is Oracle 8.x.
dotConnect Universal introduces standard for calling common SQL functions. This is set of function names with fixed meaning. In run time the function is transformed either to corresponding native function, or to equivalent expression (for example, several functions). The construct syntax is
{fn Function_Name(parameter1 [,parameter2 ... ])}
For example, the following fragment
SELECT {fn TRIM(EName)} FROM emp
evaluates to
SELECT TRIM(EName) FROM emp
in MySQL, because there is the counterpart in the DBMS. But in MS SQL Server there is no single corresponding function, so the expression evaluates to
SELECT LTRIM(RTRIM(EName)) FROM emp
The following table lists unified functions and describes them briefly.
Function name | Description |
---|---|
System routines | |
USER | Returns current user name. |
String routines | |
CHAR_LENGTH(string_exp) | Returns length of string expression in characters. |
LOCATE(string_exp1, string_exp2) | Finds first occurrence of substring string_exp1 in string expression string_exp2. |
SUBSTRING(string_exp, start, length) | Returns substring from specified string string_exp. |
CONCAT(string_exp1, string_exp2) | Concatenates several string expressions. |
CHAR(code) | Converts integer values into characters. |
TRIM(string_exp) | Removes leading and trailing spaces from a string. |
Number routines | |
TRUNCATE(numeric_exp, integer_exp) | Returns numeric_exp truncated to integer_exp places right of the decimal point. |
CEILING(numeric_exp) | Returns the smallest integer value not less than numeric_exp. |
Date and time routines | |
CURRENT_DATE | Returns date part of current timestamp, that is, year, month and day. |
YEAR(date_exp) | Extracts year part of a timestamp. |
MONTH(date_exp) | Extracts month part of a timestamp. |
DAY(date_exp) | Extracts day part of a timestamp. |
DATEADD(datepart, number, date) | Returns a new datetime value based on adding an interval to the specified date.
The interval is formed as number of datepart units. The following example
adds two years to HireDate field: SELECT {fn DATEADD(year,2,HireDate)} FROM emp |
DATEDIFF (datepart, startdate, enddate) | Returns the number of date and time boundaries crossed between two specified dates. |
Conversion routines | |
TODATE(string_exp) | Converts value to date format. |
TOCHAR(any_type_exp) | Converts value to string format. |
TONUMBER(string_exp) | Converts value to number format. |
Note that function mapping is not available for OLE DB, ODBC, and DB2 data providers.
The following table enumerates names of predefined macros that are enabled depending on DBMS server connected and provider used.
DBMS | Macro name | Data Provider | Macro name |
---|---|---|---|
Oracle | Oracle | Oracle Data Provider for .NET | OracleClient |
dotConnect for Oracle Data Provider | OraDirect | ||
MySQL | MySQL | dotConnect for MySQL Data Provider | MyDirect |
PostgreSQL | PostgreSQL | dotConnect for PostgreSQL Data Provider | PostgreSQLDirect |
SQLite | SQLite | dotConnect for SQLite Data Provider | SQLiteDirect |
MS SQL Server | SQL Server | .NET Framework Data Provider for SQL Server | SQLClient |
any | .NET Framework Data Provider for OLE DB | OLEDB | |
any | .NET Framework Data Provider for ODBC | ODBC |
There are also predefined macros that help to solve most common differences in SQL syntax. The following table enumerates them and gives translation for some databases.
Macro name | Remarks | Oracle | MySQL | PostgreSQL | SQLite | MS SQL |
---|---|---|---|---|---|---|
VARCHAR | Evaluates to database type that represents string values. Used mainly in CAST expressions. | VARCHAR2 | VARCHAR | VARCHAR | TEXT | VARCHAR |
DOUBLE | Evaluates to database type that represents floating point values. Used mainly in CAST expressions. | NUMBER | DOUBLE | DOUBLE PRECISION | REAL | FLOAT(53) |
DATETIME | Evaluates to database type that represents date and time values. Used mainly in CAST expressions. | DATETIME | DATETIME | TIMESTAMP | TEXT (ISO8601), INTEGER (Unix Time) | DATETIME |
AS | Used to define alias names for tables in FROM clauses | empty string | AS | AS | AS | AS |
EXCEPT | Used in compound queries to exclude a subset of data | MINUS | Not supported | EXCEPT | EXCEPT | EXCEPT (not supported in versions prior to 2005) |
ServerVersion | Returns version of currently connected server. | Depends on database server and data provider |
UniMacro Class | UniConnection Class | dotConnect Universal articles