Summary:
See also: Connections, Transactions, Static SQL, Dynamic SQL, Result Sets, SQL Errors, Programs, ODI Guides.
The BDL library "fgldbutl.4gl" provides several utility functions. You will find this library in the FGLDIR/src directory. For example, this library implements a function to get the type of the database engine at runtime. See the source file for more details.
In BDL, the DATABASE statement can be used in two distinct ways, depending on the context of the statement within its source module :
A default database is almost always used, because many BDL applications contain DEFINE ... LIKE statements. A problem occurs when the production database name differs from the development database name, because the default database specification will result in an automatic connection (just after MAIN):
01
DATABASE stock_dev02
DEFINE03
p_cust RECORD LIKE customer.*04
MAIN05
DEFINE dbname CHAR(30)06
LET dbname = "stock1"07
DATABASE dbname08
END MAIN
In order to avoid the implicit connection, you can use the SCHEMA instruction instead of DATABASE:
01
SCHEMA stock_dev02
DEFINE03
p_cust RECORD LIKE customer.*04
MAIN05
DEFINE dbname CHAR(30)06
LET dbname = "stock1"07
DATABASE dbname08
END MAIN
This instruction will define the database schema for compilation only, and will not make an implicit connection at runtime.
A BDL program can become very complex if a lot of nested functions do SQL processing. When using a database supporting transactions, you must sometimes manage to execute all SQL statements in the same transaction block. This can be done easily by centralizing transaction control commands in wrapper functions.
The fgldbutl.4gl library contains special functions to manage the beginning and the end of a transaction with an internal counter, in order to implement nested function calls inside a unique transaction.
Example:
01
MAIN02
IF a() <> 0 THEN03
ERROR "..."04
END IF05
IF b() <> 0 THEN06
ERROR "..."07
END IF08
END MAIN09
10
FUNCTION a()11
DEFINE s INTEGER12
LET s = db_start_transaction( )13
UPDATE ...14
LET s = SQLCA.SQLCODE15
IF s = 0 THEN16
LET s = b( )17
END IF18
LET s = db_finish_transaction((s==0))19
RETURN s20
END FUNCTION21
22
FUNCTION b()23
DEFINE s INTEGER24
LET s = db_start_transaction( )25
UPDATE ...26
LET s = SQLCA.SQLCODE27
LET s = db_finish_transaction((s==0))28
RETURN s29
END FUNCTION
In this example, you see in the MAIN block that both functions a() and b() can be called separately. However, the transaction SQL commands will be used only if needed: When function a() is called, it starts the transaction, then calls b(), which does not start the transaction since it was already started by a(). When function b() is called directly, its starts the transaction.
The function db_finish_transaction()
is called with the expression (s==0)
,
which is evaluated before the call. This allows you to write in one line the
equivalent of the following IF statement:
01
IF s==0 THEN02
LET s = db_finish_transaction(1)03
ELSE04
LET s = db_finish_transaction(0)05
END IF
Although BDL allows you to write SQL statements directly in the program source as a part of the language (Static SQL), it is strongly recommended that you use Dynamic SQL instead when you are executing SQL statements within large program loops. Dynamic SQL allows you to PREPARE the SQL statements once and EXECUTE N times, improving performance.
To perform Static SQL statement execution, the database interface must use the basic API functions provided by the database vendor. These are usually equivalent to the PREPARE and EXECUTE instructions. So when you write a Static SQL statement in your BDL program, it is actually converted to a PREPARE + EXECUTE.
For example, the following BDL code:
01
FOR n=1 TO 10002
INSERT INTO tab VALUES ( n, c )03
END FOR
is actually equivalent to:
01
FOR n=1 TO 10002
PREPARE s FROM "INSERT INTO tab VALUES ( ?, ? )"03
EXECUTE s USING n, c04
END FOR
To improve the performance of the preceding code, use a PREPARE instruction before the loop and put an EXECUTE instruction inside the loop:
01
PREPARE s FROM "INSERT INTO tab VALUES ( ?, ? )"01
FOR n=1 TO 10003
EXECUTE s USING n, c04
END FOR
Although we recommend using Dynamic SQL to improve performance when an SQL statement is executed multiple times within a program loop, you may not wish to review all your code. You can improve program execution by enabling the STATIC SQL cache with the following FGLPROFILE entry:
dbi.sql.static.optimization.cache.size = <n>
This FGLPROFILE parameter defines the number of SQL statements that can be cached, for all active database connections. When this entry is set, the database interface caches Static SQL statements automatically to prepare them only when needed. By default this feature is disabled.
When you use an ANSI compliant RDBMS like Oracle or DB2, the database interface performs a COMMIT after each statement execution. This generates unnecessary database operations and can slow down big loops. To avoid this implicit COMMIT, you can control the transaction with BEGIN WORK / COMMIT WORK around the code containing a lot of SQL statement execution.
For example, the following loop will generate 2000 basic SQL operations ( 1000 INSERTs plus 1000 COMMITs ):
01
PREPARE s FROM "INSERT INTO tab VALUES ( ?, ? )"01
FOR n=1 TO 10003
EXECUTE s USING n, c -- Generates implicit COMMIT04
END FOR
You can improve performance if you put a transaction block around the loop:
01
PREPARE s FROM "INSERT INTO tab VALUES ( ?, ? )"02
BEGIN WORK03
FOR n=1 TO 10004
EXECUTE s USING n, c -- In transaction -> no implicit COMMIT05
END FOR06
COMMIT WORK
With this code, only 1001 basic SQL operations will be executed ( 1000 INSERTs plus 1 COMMIT ).
However, you must take care when generating large transactions because all modifications are registered in transaction logs. This can result in a lack of database server resources (INFORMIX transaction too long error, for example) when the number of operations is very big. If the SQL operation does not require a unique transaction for database consistency reasons, you can split the operation into several transactions, as in the following example:
01
PREPARE s FROM "INSERT INTO tab VALUES ( ?, ? )"02
BEGIN WORK03
FOR n=1 TO 10004
IF n MOD 10 == 0 THEN05
COMMIT WORK06
BEGIN WORK07
END IF08
EXECUTE s USING n, c -- In transaction -> no implicit COMMIT09
END FOR10
COMMIT WORK
Writing portable SQL is mandatory if you want to succeed with different kind of database servers. This section gives you some hints to solve SQL incompatibility problems in your programs. Read this section carefully and review your program source code if needed. You should also read carefully the ODI Adaptation Guides which contain detailed information about SQL compatibility issues.
To easily detect SQL statements with specific syntax, you can use the -W stdsql option of fglcomp:
$ fglcomp -W stdsql orders.4gl module.4gl:15: SQL Statement or language instruction with specific SQL syntax.
Remark: This compiler option can only detect non-portable SQL syntax in Static SQL statements.
Most database servers can handle multiple database entities (you can create multiple 'databases'), but this is not possible with all engines:
Database Server Type | Multiple Database Entities |
Adabas D | Yes |
IBM DB2 UDB (Unix) | Yes |
Informix | Yes |
Microsoft SQL Server | Yes |
MySQL | Yes |
Oracle Database Server | No |
PostgreSQL | Yes |
Sybase ASA | Yes |
When using a database server that does not support multiple database entities, you can emulate different databases with schemas, but this requires you to check for the database user definition. Each database user must have privileges to access any schema, and to see any table of any schema without needing to set a schema prefix before table names in SQL statements.
You can force the database driver to set a specific schema at connection with the following FGLPROFILE entry:
dbi.database.<dbname>.schema = "<schema-name>"
To get the benefit of the database server security features, you should identify each physical user as a database user.
According to the type of server, you must do the following steps to create a database user:
Each database server has its specific users management and data access privilege mechanisms. Check the vendor documentation for security features and make sure you can define the users, groups, and privileges in all database servers you want to use.
When using Data Definition Statements like CREATE TABLE, ALTER TABLE, DROP TABLE, only a limited SQL syntax works on all database servers. Most databases support NOT NULL, CHECK, PRIMARY KEY, UNIQUE, FOREIGN KEY constraints, but the syntax for naming constraints is different.
The following statement works with most database servers and creates a table with equivalent properties in all cases:
CREATE TABLE customer
(
cust_id INTEGER NOT NULL,
cust_name CHAR(50) NOT NULL,
cust_lastorder DATE NOT NULL,
cust_group INTEGER,
PRIMARY KEY (cust_id),
UNIQUE (cust_name),
FOREIGN KEY (cust_group) REFERENCES group (group_id)
)
When you want to create tables in programs using non-standard clauses (for example to define storage options), you must use Dynamic SQL and adapt the statement to the target database server.
The ANSI SQL specification defines standard data types, but for historical reasons most databases vendors have implemented native (non-standard) data types. You can usually use a synonym for ANSI types, but the database server always uses the native types behind the scenes. For example, when you create a table with an INTEGER column in Oracle, the native NUMBER data type is used. In your programs, avoid BDL data types that do not have a native equivalent in the target database. This includes simple types like floating point numbers, as well as complex data types like INTERVAL. Numbers may cause rounding or overflow problems, because the values stored in the database have different limits. For DECIMALs, always use the same precision and scale for the BDL variables and the database columns.
To write portable applications, we strongly recommend using the following BDL data types only:
BDL Data Type |
CHAR(n) |
VARCHAR(n) |
INTEGER |
SMALLINT |
DECIMAL(p,s) |
DATE |
DATETIME YEAR TO SECOND |
See the Adaptation Guides for more details about data type compatibility.
Data Concurrency is the simultaneous access of the same data by many users. Data Consistency means that each user sees a consistent view of the database. Without adequate concurrency and consistency controls, data could be changed improperly, compromising data integrity. To write interoperable BDL applications, you must adapt the program logic to the behavior of the database server regarding concurrency and consistency management. This issue requires good knowledge of multi-user application programming, transactions, locking mechanisms, isolation levels and wait mode. If you are not familiar with these concepts, carefully read the documentation of each database server which covers this subject.
Processes accessing the database can change transaction parameters such as the isolation level. The main problem is to find a configuration which results in similar behavior on every database engine. Existing BDL programs must be adapted to work with this new behavior. ALL programs accessing the same database must be changed.
The following is the best configuration to get a common behavior with all types of database engines :
Remarks: With this configuration, the locking granularity does not have to be at the row level. To improve performance with Informix databases, you can use the "LOCK MODE PAGE" locking level, which is the default.
SQLCA is the SQL Communication Area variable. SQLCA is a global record predefined by the runtime system, that can be queried to get SQL status information. After executing an SQL statement, members of this record contain execution or error data, but it is specific to Informix databases. For example, after inserting a row in a table with a SERIAL column, SQLCA.SQLERRD[2] contains the new generated serial number.
To write portable code, avoid using this variable. SQLCA.SQLCODE may be set to the Informix SQL error code, if the database driver can convert the native SQL error to an Informix SQL error. Additional members may be set, but that depends on the database server and database driver.
To identify SQL errors, you should use SQLSTATE: This variable holds normalized ISO SQL error codes. However, even if Genero database drivers are prepared to support this register, make sure the target databases you want to use do support standard SQLSTATE error codes:
Database Server Type | Supports SQLSTATE errors |
Adabas D | Yes, since version 12 |
IBM DB2 UDB (Unix) | Yes, since version 7.1 |
Informix | Not in version IDS 9.xx |
Microsoft SQL Server | Yes, since version 8 (2000) |
MySQL | Not in version 3.23.x |
Oracle Database Server | Not in version 9.2.x.x |
PostgreSQL | Yes, since version 7.4 |
Sybase ASA | Not in version 8.x |
This section describes how to implement optimistic locking mechanisms in BDL applications. Optimistic locking is a portable solution to control simultaneous modification of the same record by multiple users.
Usually, BDL applications use SELECT FOR UPDATE to control the modification of a record by multiple users. This is called "pessimistic locking". The SELECT FOR UPDATE is executed before the interactive part of the code, to set an exclusive lock on the record during the user input :
Unfortunately, this is not a portable solution. The lock wait mode should preferably be "WAIT" for portability reasons. Pessimistic locking is based on a "NOT WAIT" mode to return control to the program if a record is already locked by another process. Therefore, following the portable concurrency model, the pessimistic locking mechanisms must be replaced by optimistic locking.
Optimistic locking uses Before Modification Values (BMVs) to verify whether the record has been updated by another process:
(rec1.*==rec2.*)
To compare 2 records, simply write:
01
IF new_record.* != bmv_record.* THEN02
LET values_have_changed = TRUE03
END IF
The optimistic locking technique could be implemented with a unique SQL instruction: An UPDATE could compare the column values to the BMVs directly (UPDATE ... WHERE kcol = kvar AND col1 = bmv.var1 AND ...). But, this is not possible when BMVs can be NULL. The database engine always evaluates conditional expressions such as "col=NULL" to FALSE. Therefore, you must use "col IS NULL" when the BMV is NULL. This means dynamic SQL statement generation based on the DMV values. Additionally, to use the same number of SQL parameters (? markers), you would have to use "col=?" when the BMV is not null and "col IS NULL and ? IS NULL" when the BMV is null. Unfortunately, the expression " ? IS [NOT] NULL " is not supported by all database servers (DB2 raises error SQL0418N).
To simplify the test of BMVs, a version column method can be used: You must add a NOT NULL INTEGER column to each table, with a default value of 1. This new column represents the version of the record, based on the modifications. Implement an update trigger to increment that column automatically (make sure this trigger is raised for each row). Any UPDATE statement will modify the record version. To implement optimistic locking, programs would just have to verify the record version column directly in the UPDATE statement. The NULL problem is resolved because the version number should never be NULL.
This section describes how to implement auto-incremented fields for portability.
INFORMIX provides the SERIAL data type which can be emulated by the database interface with most non-INFORMIX database engines. But, this requires additional configuration and maintenance tasks. If you plan to review the architecture of your programs, you should use this portable implementation instead of SERIALs emulated by the connectors when "ifxemul.serial" is true.
Principle:
In accordance with the target database, you must use the appropriate native serial generation method. Get the database type with the db_get_database_type() function of fgldbutl.4gl and use the appropriate SQL statements to insert rows with serial generation.
Warning : Not all database engines provide a sequence generator. Check the documentation of your target database.
Implementation:
BDL example:
01
DEFINE dbtype CHAR(3)02
DEFINE t1rec RECORD03
id INTEGER,04
name CHAR(50),05
cdate DATE06
END RECORD07
08
LET dbtype = db_get_database_type()09
10
IF dbtype = "IFX" THEN11
INSERT INTO t1 ( id, name, cdate )12
VALUES ( 0, t1rec.name, t1rec.cdate )13
LET t1rec.id = SQLCA.SQLERRD[2]14
END IF15
IF dbtype = "ORA" THEN16
INSERT INTO t1 ( id, name, cdate )17
VALUES ( t1seq.nextval, t1rec.name, t1rec.cdate )18
SELECT t1seq.currval INTO t1rec.id FROM dual19
END IF20
IF dbtype = "MSV" THEN21
INSERT INTO t1 ( name, cdate )22
VALUES ( t1rec.name, t1rec.cdate )23
PREPARE s FROM "SELECT convert(integer,@@identity)"24
EXECUTE s INTO t1rec.id25
END IF
Purpose:
The goal is to generate unique INTEGER numbers. These numbers will usually be used for primary keys.
Prerequisites:
Principle:
A dedicated table named "SEQREG" is used to register sequence numbers. The key is the name of the sequence. This name will usually be the table name the sequence is generated for. In short, this table contains a primary key that identifies the sequence and a column containing the last generated number.
The uniqueness is granted by the concurrency management of the database server. The first executed instruction is an UPDATE that sets an exclusive lock on the SEQREG record. When two processes try to get a sequence at the same time, one will wait for the other until its transaction is finished.
Implementation:
The "fgldbutl.4gl" utility library implements a function called "db_get_sequence" which generates a new sequence. You must create the SEQREG table as described in the fgldbutl.4gl source and make sure that every user has the privileges to access and modify this table.
BDL example:
01
DEFINE rec RECORD02
id INTEGER,03
name CHAR(100)04
END RECORD05
BEGIN WORK06
LET rec.id = db_get_sequence( "CUSTID" )07
IF rec.id>0 THEN08
INSERT INTO CUSTOMER ( CUSTID, CUSTNAME )09
VALUES ( rec.id, rec.name )10
ELSE11
ERROR "cannot get new sequence number"12
END13
COMMIT WORK
INFORMIX allows you to create databases in ANSI mode, which is supposed to be closer to ANSI standard behavior. Other databases like ORACLE and DB2 are 'ANSI' by default.
If you are not using the ANSI mode with Informix, we suggest you keep the database as is, because turning an Informix database into ANSI mode can result in unexpected behavior of the programs.
Here are some ANSI mode issues extracted from the Informix books:
It will take more time to adapt the programs to the INFORMIX ANSI mode than using the database interface to simulate the native mode of INFORMIX.
The "WHERE CURRENT OF cursor-name" clause in UPDATE and DELETE statements is not supported by all database engines.
Database Server Type | WHERE CURRENT OF supported? |
Adabas D | Yes |
IBM DB2 UDB (Unix) | Yes |
Informix | Yes |
Microsoft SQL Server | Yes |
MySQL | Yes |
Oracle Database Server | No, emulated by driver with ROWIDs |
PostgreSQL | No, emulated by driver with OIDs |
Sybase ASA | Yes |
Some database drivers can emulate WHERE CURRENT OF mechanisms by using rowids, but this requires additional processing. You should review the code to disable this option.
The standard SQL solution is to use primary keys in all tables and write UPDATE / DELETE statements with a WHERE clause based on the primary key:
01
DEFINE rec RECORD02
id INTEGER,03
name CHAR(100)04
END RECORD05
BEGIN WORK06
SELECT CUSTID FROM CUSTOMER07
WHERE CUSTID=rec.id FOR UPDATE08
UPDATE CUSTOMER SET CUSTNAME = rec.name09
WHERE CUSTID = rec.id10
COMMIT WORK
However, with some database engines like Oracle, it may be faster to use the ROWID. That can be done specifically in parts of code that must execute rapidly. You must then adapt the update statement dynamically according to the database server type.
Some database servers like INFORMIX allow single and double quoted string literals in SQL statements, both are equivalent:
SELECT COUNT(*) FROM table
WHERE col1 = "abc'def""ghi"
AND col1 = 'abc''def"ghi'
Most database servers do not support this specific feature:
Database Server Type | Double quoted strings? | Single quoted strings? |
Adabas D | No | Yes |
IBM DB2 UDB (Unix) | No | Yes |
Informix | Yes | Yes |
Microsoft SQL Server | Yes | Yes |
MySQL | No | Yes |
Oracle Database Server | No | Yes |
PostgreSQL | No | Yes |
Sybase ASA | No | Yes |
The ANSI SQL standards define double quotes as database object names delimiters, while single quotes are dedicated to string literals:
CREATE TABLE "my table" ( "column 1" CHAR(10) )
SELECT COUNT(*) FROM "my table" WHERE "column 1" = 'abc'
If you want to write a single quote character inside a string literal, you must write 2 single quotes:
... WHERE comment = 'John''s house'
Any double quoted string literals in Static SQL statements in your programs are converted to ANSI single quoted string literals by the fglcomp compiler. However, Dynamic SQL statements are not parsed by the compiler and therefore need to use single quoted string literals.
We recommend that you ALWAYS use single quotes for string literals and if needed, double quotes for database object names.
Each type of database server has its own naming conventions for database objects (i.e. tables and columns):
Database Server Type | Naming Syntax |
Adabas D | [[schema.]owner.]identifier |
IBM DB2 UDB (Unix) | [[database.]owner.]identifier |
Informix | [database[@dbservername]:][owner.]identifier |
Microsoft SQL Server | [[[server.][database].][owner_name].]object_name |
MySQL | [database.]identifier |
Oracle Database Server | [schema.]identifier[@database-link] |
PostgreSQL | [owner.]identifier |
Sybase ASA | [database.]identifier |
Most database engines have case-sensitive object identifiers. In most cases, when you do not specify identifiers in double quotes, the SQL parser automatically converts names to uppercase or lowercase, so that the identifiers match if the objects are also created without double quoted identifiers.
CREATE TABLE Customer ( cust_ID INTEGER )
In Oracle, the above statement would create a table named "CUSTOMER
"
with a "CUST_ID
" column.
The following table shows the case sensitivity features of each kind of database engine:
Database Server Type | Case sensitive names? | Not-quoted names converted to ... |
Adabas D | Yes | Uppercase |
IBM DB2 UDB (Unix) | Yes | Uppercase |
Informix (1) | No | No |
Microsoft SQL Server (2) | Yes | Not converted |
MySQL | Yes | Not converted |
Oracle Database Server | Yes | Uppercase |
PostgreSQL | No | Lowercase |
Sybase ASA | No | Lowercase |
(1) If not ANSI database mode.
(2) Global parameter set at installation.
CREATE TABLE customer ( cust_id INTEGER ) -- first table
CREATE TABLE Customer ( cust_id INTEGER ) -- second table
The maximum size of a table or column name depends on the database server type. Some database engines allow very large names (256c), while others support only short names (30c max). Therefore, using short names is required for writing portable SQL. Short names also simplify SQL programs.
We recommend that you use simple and short (<30c) database object names, without double quotes and without schema/owner prefix:
CREATE TABLE customer ( cust_id INTEGER )
SELECT customer.cust_id FROM table
You may need to set the database schema after connection, so that the current database user can see the application tables without specifying the owner/schema prefix each time.
Tip: Even if all database engines do not required unique column names for all tables, we recommend that you define column names with a small table prefix (for example, CUST_ID in CUSTOMER table).
Not all database servers support temporary tables. The engines supporting this feature often provide it with a specific table creation statement:
Database Server Type | Temp table creation syntax | Local to SQL session? |
Adabas D | CREATE TABLE TEMP.tablename
( column-defs ) |
Yes |
IBM DB2 UDB (Unix) | DECLARE GLOBAL TEMPORARY
TABLE tablename ( column-defs ) |
Yes |
Informix | CREATE TEMP TABLE tablename
( column-defs ) |
Yes |
Microsoft SQL Server | CREATE TABLE #tablename ( column-defs
) |
Yes |
MySQL | CREATE TEMPORARY TABLE
tablename ( column-defs ) |
Yes |
Oracle Database Server | CREATE GLOBAL TEMPORARY
TABLE tablename ( column-defs ) |
No: only data is local to session |
PostgreSQL | CREATE TEMP TABLE tablename
( column-defs ) |
Yes |
Sybase ASA | CREATE GLOBAL TEMPORARY
TABLE tablename ( column-defs ) |
Yes |
Some database even have a different behavior when using temporary tables. For example, Oracle 9i supports a kind of temporary table, but it must be created as a permanent table. The table is not specific to a SQL session: it is shared by all processes - only the data is local to a database session.
You must review the programs using temporary tables, and adapt the code to use database specific temporary tables.
Old INFORMIX SQL outer joins specified with the OUTER keyword in the FROM part are not standard:
SELECT * FROM master, OUTER ( detail )
WHERE master.mid = detail.mid
AND master.cdate IS NOT NULL
Most recent database servers now support the standard ANSI outer join specification:
SELECT * FROM master LEFT OUTER JOIN detail ON (master.mid = detail.mid)
WHERE master.cdate IS NOT NULL
You should use recent database servers and use ANSI outer joins only.
Only INFORMIX supports sub-string specification with square brackets:
SELECT * FROM item WHERE item_code[1,4] = "XBFG"
However, most database servers support a function that extracts sub-strings from a character string:
Database Server Type | Supports col[x,y] sub-strings? | Provides sub-string function? |
Adabas D | No | SUBSTR(expr,start,length) |
IBM DB2 UDB (Unix) | No | SUBSTR(expr,start,length) |
Informix (1) | Yes | SUBSTR(expr,start,length) |
Microsoft SQL Server (2) | No | SUBSTRING(expr,start,length) |
MySQL | No | SUBSTR(expr,start,length) |
Oracle Database Server | No | SUBSTRING(expr,start,length) |
PostgreSQL | No | SUBSTRING(expr FROM
start FOR length ) |
Sybase ASA | No | SUBSTR(expr,start,length) |
UPDATE tab
SET col[1,2] ='ab'
). This is not possible with other databases.You must review the SQL statements using sub-string expressions and use the database specific sub-string function.
You could also create your own SUBSTRING() user function in all databases that do not support this function, to have a common way to extract sub-strings. In Microsoft SQL Server, when you create a user function, you must specify the owner as prefix when using the function. Therefore, you should create a SUBSTRING() user function instead of SUBSTR().
Rowids are implicit primary keys generated by the database engine. Not all database servers support rowids:
Database Server Type | Rowid keyword? | Rowid type? |
Adabas D | SYSKEY |
CHAR(8) |
IBM DB2 UDB (Unix) | none |
none |
Informix (1) | ROWID |
INTEGER |
Microsoft SQL Server (2) | none |
none |
MySQL | _rowid |
INTEGER |
Oracle Database Server | ROWID |
CHAR(18) |
PostgreSQL | OID |
internal type |
Sybase ASA | none |
none |
You must search for ROWID and SQLCA.SQLERRD[3] in your code and review the code to remove the usage of rowids.
The MATCHES operator allows you to scan a string expression:
SELECT * FROM customer WHERE customer_name MATCHES "A*[0-9]"
The MATCHES operator is specific to INFORMIX SQL. There is an equivalent
standard operator: LIKE. MATCHES uses *
and ?
as
wildcards. The equivalent wildcard in the LIKE operator are %
and _
.
Character ranges [a-z]
are not supported by the LIKE operator.
Search for MATCHES in your SQL statements and replace it by a standard LIKE expression.
Remark: The BDL language provides a MATCHES operator which is part of the runtime system. Do not confuse this with the SQL MATCHES operator, used in SQL statements. There is no problem in using the MATCHES operator of the BDL language.
SELECT
a, b, sum(c) FROM table GROUP BY 1,2
This is not possible with all database servers:
Database Server Type | GROUP BY colindex, ... ? |
Adabas D | No |
IBM DB2 UDB (Unix) | No |
Informix (1) | Yes |
Microsoft SQL Server (2) | No |
MySQL | Yes |
Oracle Database Server | No |
PostgreSQL | Yes |
Sybase ASA | No |
Search for GROUP BY in your SQL statements and use explicit column names.
Not all database servers support the LENGTH() function, and some have specific behavior:
Database Server Type | Length function? | Counts trailing blanks for CHAR() columns? | Return value when NULL |
Adabas D | LENGTH(expr) | Yes | NULL |
IBM DB2 UDB (Unix) | LENGTH(expr) | Yes | NULL |
Informix (1) | LENGTH(expr) | No | NULL |
Microsoft SQL Server (2) | LEN(expr) | No | NULL |
MySQL | LENGTH(expr) | No | NULL |
Oracle Database Server | LENGTH(expr) | Yes | NULL |
PostgreSQL | LENGTH(expr) | Yes | NULL |
Sybase ASA | LENGTH(expr) | No | NULL |
Search for LENGTH in your SQL statements and review the code of the database specific function. You could also define your own LEN() user function to have a common function in all databases. In Microsoft SQL Server, when you create a user function, you must specify the owner as prefix when using the function. Therefore, you should create a LEN() user function instead of LENGTH().
Remark: The BDL language provides a LENGTH built-in function which is part of the runtime system. Do not confuse this with the SQL LENGTH() function, used in SQL statements. There is no problem in using the LENGTH() function of the BDL language. However, the LENGTH() function of the language returns zero when the string expression is NULL.