Open Source RDBMS - Seamless, Scalable, Stable and Free

한국어 | Login |Register

Current Events
Join our developers event to win one of the valuable prizes!
posted 2 years ago
viewed 14439 times
Share this article

Things to Understand When Moving from MySQL to CUBRID

cubrid-vs-mysql.png

These days at NHN we use CUBRID for our services more often than MySQL. This article summarizes the differences between MySQL and CUBRID Database, the knowledge that several departments at NHN have obtained as they have changed their database from MySQL to CUBRID. This document is based on MySQL 5.5 and CUBRID 8.4.1.

The differences can be classified into three types:

  1. Column Types
  2. SQL Syntax
  3. Provided Functions

Differences in Column Types

Case-sensitiveness of Character Types

Basically, MySQL is not case-sensitive for character type values when the query is executed. Therefore, to make the character type be case-sensitive in MySQL, you should add an additional binary keyword when creating a table or a query statement. On the contrary, CUBRID is basically case-sensitive for character type values when the query is executed.

The following example shows how to indicate that a target column is BINARY when creating a table in MySQL.

CREATE TABLE tbl (name CHAR(10) BINARY);
INSERT INTO tbl VALUES('Charles'),('blues');
SELECT * FROM tbl WHERE name='CHARLES';
 
Empty set (0.00 sec)

This example shows how to indicate that a target column is BINARY when executing a SELECT statement in MySQL.

SELECT * FROM tbl WHERE BINARY name='Charles';
 
+---------+
| name |
+---------+
| Charles |
+---------+

To make CUBRID be case-insensitive, just like MySQL, apply the UPPER() function or the LOWER() function to the target column as shown in the following example.

SELECT * FROM tbl ORDER BY name;
 
name
======================
'Charles   '
'blues     '
 
 
SELECT * FROM tbl ORDER BY UPPER(name);
 
name
======================
'blues     '
'Charles   '

As shown in the example above, when UPPER() function is not applied the data is returned as they were inserted. When we apply UPPER() function to the ORDER BY column we can obtain case-insensitive results. In this case even if there was an index defined on the name column this index cannot be used by ORDER BY due to the existence of UPPER() function, thus no optimization can be applied. If there was no UPPER() function, ORDER BY would fetch the data in the order of the defined index. In order to optimize ORDER BY, you may consider creating a separate column that is totally upper-cased or lower-cased and configuring index in the column.

The following is an example of separately adding the sorting column name2 which is not case-sensitive.

ALTER TABLE tbl ADD COLUMN (name2 CHAR(10));
UPDATE tbl SET name2=UPPER(name);
CREATE INDEX i_tbl_name2 ON tbl(name2);
SELECT * FROM tbl ORDER BY name2;
 
name name2
============================================
'blues     ' 'BLUES     '
'Charles   ' 'CHARLES   '

This coming fall we will release a new version of CUBRID under the code name "Apricot" which will introduce function based indexes. Then, you will no longer need to create a separate column and create an index on it. You will be able to do the following:

CREATE INDEX idx_tbl_name_upper ON tbl (UPPER(name));
SELECT * FROM tbl WHERE UPPER(name) = 'CHARLES';

Automatic Type Conversion for Date Type

MySQL is very flexible in converting the type. It accepts character string input in the numeric type and vice versa (number input in the character string type). It also accepts numeric input in the date type.

From version 8.4.0, CUBRID supports flexible type conversion, allowing for character string input in the numeric type and number input in the character string type. However, unlike MySQL, CUBRID does not accept number input in the date type.

The following is an example of inputting numbers in the date type dt column in MySQL.

mysql> CREATE TABLE dt_tbl(dt DATE);
mysql> INSERT INTO dt_tbl VALUES (20120515);
mysql> SELECT * FROM dt_tbl;
 
+------------+
| dt |
+------------+
| 2012-05-15 |
+------------+
1 row in set (0.00 sec)

The following is an example of inputting numbers in the date type dt column in CUBRID. You can see that an error is returned as a result value when numbers are input in the date type.

csql> CREATE TABLE dt_tbl(dt DATE);
csql> INSERT INTO dt_tbl VALUES (20120515);
 
ERROR: before ' ); '
Cannot coerce 20120515 to type date.
 
csql> INSERT INTO dt_tbl VALUES ('20120515');
csql> SELECT * FROM dt_tbl;
 
dt
============
05/15/2012

When an error occurs as a result of executing the date function, MySQL returns NULL and CUBRID returns an error by default. To make CUBRID return NULL for such cases, set the value of return_null_on_function_errors system parameter to yes.

The following example shows that NULL is returned when an invalid parameter has been entered in the date function of MySQL.

mysql> SELECT YEAR('12:34:56');
 
+------------------+
| YEAR('12:34:56') |
+------------------+
| NULL |
+------------------+
1 row in set, 1 warning (0.00 sec)

The following example shows that an error is returned when an invalid parameter has been entered in the date function of CUBRID when the value of system parameter return_null_on_function_error has been set to no which is the default value.

csql> SELECT YEAR('12:34:56');
 
ERROR: Conversion error in date format.

The following example shows that NULL is returned when an invalid parameter has been entered in the date function of CUBRID when the value of system parameter return_null_on_function_errors has been changed to yes.

csql> SELECT YEAR('12:34:56');
 
year('12:34:56')
======================
NULL

Result Value Type of Integer-by-Integer Division

When integer-by-integer division is performed, MySQL prints the output value as a DECIMAL (m, n), but CUBRID prints it as a rounded INTEGER. This is because when each operand is of the same type, the result in CUBRID is printed as that same type. In this case to display the result value as a REAL number, apply the CAST() function to any or all operands in the fraction.

The following shows an example of executing integer-by-integer division in MySQL. The result value will be printed as a real number type.

mysql> SELECT 4/3;
 
+--------+
| 4/3 |
+--------+
| 1.3333 |
+--------+
 
mysql> SELECT 4/2;
 
+--------+
| 4/2 |
+--------+
| 2.0000 |
+--------+

The following shows an example of executing integer-by-integer division in CUBRID. The result value will be printed as an INTEGER type.

csql> SELECT 4/3;
 
4/3
=============
1
 
csql> SELECT 4/2;
 
4/2
=============
2

The following shows an example of executing integer-by-integer division by using the CAST() function in CUBRID. The result value will be printed as a real number type.

csql> SELECT CAST(4 AS DECIMAL(5,4))/CAST(3 AS DECIMAL(5,4));
 
cast(4 as numeric(5,4))/ cast(3 as numeric(5,4))
======================
1.333333333

The following shows an example of executing integer-by-real number division in the CUBRID. Since one of the input values is a real number type, the result value will be printed as a real number (DOUBLE) type .

csql> SELECT 4/3.0;
 
4/3.0
======================
1.333333333

Processing SUM result which is larger than the Maximum Value of Input Value Type

How will the result be printed if the result of SUM is larger than the maximum value of the input value type?

MySQL converts the result of SUM to a pre-defined large DECIMAL number type. However, CUBRID processes the result as an overflow error. It means that in CUBRID the type of the input column decides the result type. Therefore, to avoid overflow errors in CUBRID, you should convert (CAST()) the input column type to a type that can accept the SUM result value before executing operations.

Converting the type when executing the CAST() function incurs some additional cost, so I recommend to decide the column type considering the result value of functions you plan to use.

First, configure the same table in MySQL and the CUBRID as follows.

CREATE TABLE t (code SMALLINT);
INSERT INTO t VALUES(32767);
INSERT INTO t VALUES (32767);

MySQL successfully prints the value because the result value of executing SUM is smaller than the result type. However, as the print type is decided by the input type in CUBRID, an overflow error occurs because the resulting value of executing SUM is larger than the result type.

mysql> SELECT SUM(code) FROM t;
 
+-----------+
| sum(code) |
+-----------+
| 65534 |
+-----------+
 
mysql> SHOW COLUMNS FROM ttt;
 
+-----------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------------+------+-----+---------+-------+
| sum(code) | decimal(27,0) | YES | | NULL | |
+-----------+---------------+------+-----+---------+-------+
 
csql> SELECT SUM(code) FROM t;
 
ERROR: Overflow occurred in addition context.

The following is an example of converting the column type and then executing SUM in CUBRID. You can see that the value has been successfully printed.

csql> SELECT SUM(CAST (CODE AS INT)) FROM t;
 
sum( cast(code as integer))
======================
65534

The following is an example of executing SUM after deciding the column type by considering the SUM result value size in the stage of creating a table in the CUBRID. You can see that the value has been successfully printed.

csql> CREATE TABLE t (code INT);
csql> INSERT INTO t VALUES(32767);
csql> INSERT INTO t VALUES (32767);
csql> SELECT SUM(code) FROM t;
 
sum(code)
======================
65534

Result Value Type of VARCHAR Type

MySQL and CUBRID both allow for numerical operation when the value of the column that is VARCHAR type is a string consisting of numbers. In this case, the operation result type is DOUBLE for both (however, saving a value that needs numerical operation as a string type is not recommended; it is used just for explanation here).

The following is an example of comparing the query result type of the MySQL to that of CUBRID.

mysql> CREATE TABLE tbl(col VARCHAR(10));
mysql> INSERT INTO tbl VALUES('1'),('2'),('3'),('4'),('5');
mysql> CREATE TABLE ttbl AS SELECT SUM(col) FROM tbl;
mysql> SHOW COLUMNS FROM ttbl;
 
+----------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------+------+-----+---------+-------+
| SUM(col) | double | YES | | NULL | |
+----------+--------+------+-----+---------+-------+
 
 
csql> CREATE TABLE tbl(col VARCHAR(10));
csql> INSERT INTO tbl VALUES('1'),('2'),('3'),('4'),('5');
csql> CREATE TABLE ttbl AS SELECT SUM(col) FROM tbl;
csql> ;sc ttbl
 
<Class Name>
        ttbl
<Attributes>
        sum(col)         DOUBLE

In MySQL, if there is a character (not a number) that exists on the column value, the character is considered as 0 for operation. However, CUBRID prints an error that it cannot convert the character to double type. See the following example.

mysql> INSERT INTO tbl VALUES('a');
mysql> SELECT SUM(col) FROM tbl;
 
+--------+
| SUM(a) |
+--------+
| 15 |
+--------+
csql> INSERT INTO tbl VALUES('a');
csql> SELECT SUM(col) FROM tbl;
 
ERROR: Cannot coerce value of domain "character varying" to domain "double".

Difference of SQL Syntax

Supporting START WITH … CONNECT BY

CUBRID supports the START WITH … CONNECT BY syntax which can express the hierarchy that MySQL does not support as a query. This is a part of Oracle SQL compatibility syntax.

As an example, using the following data we will print "managers" and "juniors" by sorting the result values within the same level in the order of "join date". id is the employee ID of a junior staff and mgrid is the employee ID of a manager.

CREATE TABLE tree(id INT, mgrid INT, name VARCHAR(32), birthyear INT);
 
 
INSERT INTO tree VALUES (1,NULL,'Kim', 1963);
INSERT INTO tree VALUES (2,NULL,'Moy', 1958);
INSERT INTO tree VALUES (3,1,'Jonas', 1976);
INSERT INTO tree VALUES (4,1,'Smith', 1974);
INSERT INTO tree VALUES (5,2,'Verma', 1973);
INSERT INTO tree VALUES (6,2,'Foster', 1972);
INSERT INTO tree VALUES (7,6,'Brown', 1981);

MySQL does not support hierarchy statement. Therefore, to print the result value satisfying the above request, you should execute several query statements in the following order.

1) First, print a "level 1" employees whose mgrid is NULL.

SELECT id, mgrid, name, 1 AS level FROM tree WHERE mgrid IS NULL; 

2) Then print the "level 2" employees whose mgrid is 1.

SELECT id, mgrid, name, 2 AS level FROM tree WHERE mgrid=1;

3) Then print the "level 2" employees whose mgrid is 2.

SELECT id, mgrid, name, 2 AS level FROM tree WHERE mgrid=2;

4) Then print the level "3 employee" whose mgrid is 6.

SELECT id, mgrid, name, 3 AS level FROM tree WHERE mgrid=6;

On the contrary, as CUBRID supports the hierarchical queries, a single query statement can be created as follows.

SELECT id, mgrid, name, LEVEL
FROM tree
START WITH mgrid IS NULL
CONNECT BY PRIOR id=mgrid
ORDER SIBLINGS BY id;
 
id mgrid name level
===============================================
1 null Kim 1
3 1 Jonas 2
4 1 Smith 2
2 null Moy 1
5 2 Verma 2
6 2 Foster 2
7 6 Brown 3

The above code means that a parent node (manager) and the child node (junior staff) should be printed in order of the values of child nodes (junior staffs) with the same level in the order of the id.

Including Disaggregate Item in the SELECT LIST which Includes Aggregate Function

ONLY FULL GROUP BY

When executing the GROUP BY clause, both MySQL and CUBRID basically allow the disaggregate column that is not included in the GROUP BY clause to be included in the SELECT list. However, the disaggregate column not included in the GROUP BY clause selects the record value which is fetched among various values for the first time. Therefore, note that the value may be different according to the fetch orders of MySQL and CUBRID. For the disaggregate column, it is not clear which proper value should it select among several values. Therefore, enable the ONLY FULL GROUP BY function to not expose the column that is not included in the GROUP BY clause to the SELECT list.

To enable the ONLY FULL GROUP BY function, for MySQL set the sql_mode value in the my.conf configuration file to ONLY_FULL_GROUP_BY. For CUBRID, set the only_full_group_by value in the cubrid.conf configuration file to yes.

From version 8.3.0 CUBRID supports the only_full_group_by system parameter. Before 8.3.0, the ONLY FULL GROUP BY function always ran as if it was enabled.

The following example shows the result of executing the GROUP BY clause in MySQL and CUBRID while the ONLY FULL GROUP BY function is enabled.

CREATE TABLE sales_tbl
(dept_no int, name VARCHAR(20) PRIMARY KEY, sales_month int, sales_amount int DEFAULT 100);
INSERT INTO sales_tbl VALUES
(501, 'Stephan', 4, DEFAULT),
(201, 'George' , 1, 450),
(201, 'Laura' , 2, 500),
(301, 'Max' , 4, 300),
(501, 'Chang' , 5, 150),
(501, 'Sue' , 6, 150),
(NULL, 'Yoka' ,4, NULL);
 
SELECT dept_no, avg(sales_amount) FROM sales_tbl
GROUP BY dept_no ORDER BY dept_no;
 
dept_no avg(sales_amount)
================================
NULL NULL
201 475
301 300
501 133

When There Is No GROUP BY Clause, Is it possible to Query Even if the Disaggregate Item Exists in the SELECT LIST?

MySQL has an item that uses the aggregate function in the SELECT list and executes one value that has been fetched initially when there are several values for another item. However, in this case, CUBRID considers that it cannot decide the value and returns an error.

If there is a disaggregate column by the aggregate function in the SELECT LIST, it means that any value among various values for the column will be randomly selected. Therefore, it is recommended not to execute that kind of query.

Configure identical data in MySQL and CUBRID as shown below.

CREATE TABLE tbl(a int, b date);
INSERT INTO tbl VALUES (1,'20000101');
INSERT INTO tbl VALUES (2,'20000102');
INSERT INTO tbl VALUES (3,'20000103');
INSERT INTO tbl VALUES (4,'20000104');

In this case, the following query can be executed for both MySQL and CUBRID.

SELECT COUNT(a), DATE_ADD(MAX(b), INTERVAL 10 DAY) FROM tbl;
 
+----------+-----------------------------------+
| COUNT(a) | DATE_ADD(MAX(b), INTERVAL 10 DAY) |
+----------+-----------------------------------+
| 4 | 2000-01-14 |
+----------+-----------------------------------+

In MySQL, when there are several values for column a, the column value of the record which has been fetched for the first time is calculated. In this case, the value of column a is floating so this processing is not proper.

mysql> SELECT COUNT(a), DATE_ADD(MAX(b), INTERVAL a DAY) FROM tbl;
 
+----------+----------------------------------+
| COUNT(a) | DATE_ADD(MAX(b), INTERVAL a DAY) |
+----------+----------------------------------+
| 4 | 2000-01-05 |
+----------+----------------------------------+

In CUBRID, when there are several values for column a, CUBRID considers that it cannot determine which record column value must be calculated and returns an error.

csql> SELECT COUNT(a), DATE_ADD(MAX(b), INTERVAL a DAY) FROM tbl;
 
ERROR: tbl.a is not single valued. Attributes exposed in
aggregate queries must also appear in the group by clause.

When an error is returned as shown above, if you want to make it executable, change the a to MAX(a) or MIN(a) in order to get only one value for the INTERVAL a as shown below. If the value of a is always the same, the result of executing this query in MySQL before changing the a will be same as the result in CUBRID after changing the a.

csql> SELECT COUNT(a), DATE_ADD(MAX(b), INTERVAL MAX(a) DAY) FROM tbl;
 
count(a) date_add(max(b), INTERVAL max(a) DAY)
=====================================================
4 01/08/2000

Using a Reserved Word as Column Name and Table Name

Both MySQL and CUBRID do not allow reserved words for column name, table name, and alias. To use reserved words for identifiers such as column name, table name, and alias, the reserved words must be enclosed with quotes (" or `). In CUBRID, square brackets ([ ]) are allowed as well as quotes.

Each DBMS supports different reserved words. For example, MySQL does not use ROWNUM, TYPE, NAMES, FILE, and SIZE as reserved words, however, CUBRID uses them all. For more details on CUBRID reserved words, see the online manual CUBRID SQL Guide > Reserved Words.

The following example is using reserved words as column names in CUBRID.

CREATE TABLE `names` (`file` VARCHAR(255), `size` INT, `type` CHAR(10));
CREATE TABLE "names" ("file" VARCHAR(255), "size" INT, "type" CHAR(10));
CREATE TABLE [names] ([file] VARCHAR(255), [size] INT, [type] CHAR(10));
SELECT [file], [size], [type] FROM [names];

Functional Differences

Supporting Descending Index

Technically, in MySQL, you can create a descending (DESC) index. However, the descending index is not actually created. However, the descending index is actually created in CUBRID.

The following example shows creating the descending index for the ndate column in the CUBRID.

csql> CREATE INDEX ON test_tbl(no ASC, ndate DESC);

Note that you can create an index in CUBRID as using the REVERSE keyword. Then the index is created in the same order of having DESC in the column.

The following example shows how to use the REVERSE keyword and the DESC keyword in order to create indexes for the ndate column in CUBRID in descending order.

CREATE REVERSE INDEX ON test_tbl(ndate); 

... which is same as...

CREATE INDEX ON test_tbl(ndate DESC);

In MySQL, creating indexes in descending order is not allowed. Therefore, you can create an ascending index after adding an additional column in order to input values in the reverse order. For example, convert the value of DATE"2012-05-18" to the numeric value -20120518 (negative value) and then input an additional column.

The following example shows creating an ascending index for the reverse_ndate column in MySQL.

1)

CREATE TABLE test_tbl(no INT, ndate DATE, reverse_ndate INT);

2)

CREATE INDEX ON test_tbl(ndate ASC);

3)

UPDATE test_tbl SET reverse_ndate = -ndate;

4)

CREATE INDEX ON test_tbl(reverse_ndate ASC);

However, when you create a descending index in the ndate column, or when you create the reverse_ndate column with reverse order values and then create an ascending index, if one is UPDATE or DELETE and the other is SELECT, UPDATE, or DELETE and both of indexes are simultaneously scanned, the index scan will be made in reverse to each other. This case increases the possibility of a deadlock so you must note this while creating a descending index.

Both MySQL and CUBRID have a bi-directional link between index nodes. Therefore, the ascending index can be used when the query planner determines that the cost of reverse scan using an ascending index will be lower than the sequential scan using the descending index even if ORDER BY DESC is performed in the query, The reverse scan takes the advantage only when the number of records to be scanned is relatively small.

The following example shows using an ascending index when ordering in the descending order in CUBRID.

csql> CREATE TABLE test_tbl(a INT, b char(1024000));
csql> CREATE INDEX i_test_tbl_a ON test_tbl(a);
csql> INSERT INTO test_tbl (a, b) VALUES (10, 'a'), (20, 'b'), (30, 'c'), (40, 'd'), (50, 'e'), (60, 'f'), (70, 'g'), (80, 'h'), (90, 'i'), (100, 'j');
 
csql> SELECT a FROM test_tbl WHERE a > 70 ORDER BY a DESC;

Supporting ROWNUM

ROWNUM is a function to number the result rows of the SELECT query from 1 in the ascending order and used as a column of a table. With ROWNUM, you can add a serial number for printed records and limit the number of records of the query result by using the conditions of the WHERE clause.

CUBRID supports ROWNUM while MySQL does not. So if you want to add a serial number to the records in MySQL, you should use a session variable. Both MySQL and CUBRID support LIMIT … OFFSET in order to limit the number of records of the query result. However, the following discussion will address ROWNUM.

The following two examples are used to print serial numbers for the records of the query result.

In MySQL, process ROWNUM by using a session variable.

mysql> CREATE TABLE test_tbl(col CHAR(1));
mysql> INSERT INTO test_tbl VALUES ('a'), ('b'),('c'),('d');
mysql> SELECT @rownum := @rownum + 1 as rownum, col FROM test_tbl WHERE (@rownum := 0)=0;
+--------+------+
| rownum | col |
+--------+------+
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |
+--------+------+

In CUBRID, execute ROWNUM.

csql> SELECT ROWNUM, col FROM test_tbl;
 
rownum col
============================================
1 'a'
2 'b'
3 'c'
4 'd'

Oracle also supports ROWNUM. However, in Oracle, ROWNUM is executed first in the syntax including the GROUP BY clause and the ORDER BY clause, and then the GROUP BY clause and the ORDER BY clause are executed later. Therefore, the printed ROWNUM> is not in the sorting order. So, if you want to execute the query including the GROUP BY clause or the ORDER BY clause first, make the query as a subquery of the FROM clause and execute ROWNUM for that.

The following example shows printing ROWNUM in the order of ORDER BY in Oracle.

SELECT ROWNUM, contents FROM (SELECT contents ORDER BY date) AS subtbl;

CUBRID supports the GROUPBY_NUM() function and the ORDERBY_NUM() function which produces the result of GROUP BY and ORDER BY printed in the order.

In the example of MySQL, since there was no ROWNUM and a session variable @rownum has been used, the final order result was used to print the result even though there were the GROUP BY clause and the ORDER BY clause. However, since CUBRID uses ROWNUM, you should use the GROUPBY_NUM() function and the ORDERBY_NUM() function if you want to keep the order of printed numbers without using a subquery.

The following two examples show the difference between two cases; one uses GROUPBY_NUM() function while the other does not. Also it shows limiting the number of result records by using ROWNUM and then executing the GROUP BY order in the corresponding records.

csql> SELECT ROWNUM, host_year, MIN(score) FROM history
WHERE ROWNUM BETWEEN 1 AND 5 GROUP BY host_year;
 
rownum host_year min(score)
=========================================================
6 2000 '03:41.0'
6 2004 '01:45.0'

The following example shows limiting the number of result records by using GROUPBY_NUM() for the record set ordered by using GROUP BY.

csql> SELECT GROUPBY_NUM(), host_year, MIN(score) FROM history
GROUP BY host_year HAVING GROUPBY_NUM() BETWEEN 1 AND 5;
 
groupby_num() host_year min(score)
==================================================
1 1968 '8.9'
2 1980 '01:53.0'
3 1984 '13:06.0'
4 1988 '01:58.0'
5 1992 '02:07.0'

The following two examples show the difference between two cases; one uses the FOR ORDERBY_NUM() function while the other does not. Also it shows limiting the number of result records by using ROWNUM and then executing the ORDER BY order in the corresponding records.

SELECT athlete, score FROM history
WHERE ROWNUM BETWEEN 3 AND 5 ORDER BY score;
 
athlete score
============================================
'Thorpe Ian' '01:45.0'
'Thorpe Ian' '03:41.0'
'Hackett Grant' '14:43.0'

The following example shows limiting the number of the result records by using ORDERBY_NUM() for the record set ordered by using ORDER BY.

SELECT athlete, score FROM history
ORDER BY score FOR ORDERBY_NUM() BETWEEN 3 AND 5;
athlete score
============================================
'Luo Xuejuan' '01:07.0'
'Rodal Vebjorn' '01:43.0'
'Thorpe Ian' '01:45.0'

Difference of AUTO_INCREMENT

What should we do if we must know the primary key created after executing INSERT?

The LAST_INSERT_ID() function is used to get the last value which has been INSERTED to the column after INSERTING a value to the AUTO_INCREMENT attribute column within a program connected to the database. For example, you can use it when you want to INSERT a value in the table where the primary key is included and then insert a foreign key value from another table in the table as the value of the LAST_INSERT_ID() function. Both MySQL and CUBRID support this function. However, an error may occur in some specific cases, so I recommend you to directly get the AUTO_INCREMENT value rather than using the LAST_INSERT_ID() function in CUBRID. Below I will explain these specific cases.

In MySQL or CUBRID, when a record is INSERTED to the table which includes the AUTO_INCREMENT attribute column, the value of the column is automatically increased by 1.

CREATE TABLE tbl (col INT AUTO_INCREMENT PRIMARY KEY, col2 INT);
INSERT INTO tbl (col2) VALUES(1);
INSERT INTO tbl (col2) VALUES(2);
INSERT INTO tbl (col2) VALUES(3);
SELECT LAST_INSERT_ID();
 
+------------------+
| last_insert_id() |
+------------------+
| 3 |
+------------------+

After executing INSERT to Table A, if you are trying to INSERT a foreign key value to Table B (which refers to Table A) by using the LAST_INSERT_ID() function value and an application or a server is unexpectedly and abnormally terminated, what will happen? (on the assumption that the procedure is one transaction, of course)

In MySQL, the procedure, including all values increased by the AUTO_INCREMENT, is rolled back. If the AUTO_INCREMENT value of the record to execute INSERT newly was 3, the AUTO_INCREMENT value becomes 3 again after transaction rollback. However, in CUBRID, the increase of AUTO_INCREMENT value is not affected by transaction rollback. Therefore, the existing AUTO_INCREMENT value of the record was 3, the AUTO_INCREMENT value becomes 4 after transaction rollback.

In MySQL, you can get the value entered in the column specified as the AUTO_INCREMENT by using the LAST_INSERT_ID() function only. However, in CUBRID, you can directly get the AUTO_INCREMENT value without using the LAST_INSERT_ID() function. It is just like using the SERIAL object of CUBRID because AUTO_INCREMENT has been implemented as a SERIAL object in CUBRID. When an AUTO_INCREMENT column is created in CUBRID, the name of the SERIAL object of the column is specified as "<table name>_ai_<column name>" internally.

The following example shows calculating the next value and the current value on the AUTO_INCREMENT column in CUBRID.

SELECT <table name>_ai_<column name>.NEXT_VALUE;
SELECT <table name>_ai_<column name>.CURRENT_VALUE;

In the current CUBRID version, the LAST_INSERT_ID() function may malfunction in the following specific cases. Therefore, I recommend that you directly get the AUTO_INCREMENT value rather than using the LAST_INSERT_ID() function. The LAST_INSERT_ID() is one of the session variables that manage the values in each database connection unit. If a broker is switched in CUBRID or if a failover occurs because of master node switch in the HA environment, it may malfunction so the desired value may not be printed. It will be modified later.

In the following example, a session variable has been used as an intermediate storage for getting the next AUTO_INCREMENT value. For an application, use the variable in the program as an intermediate storage. Note that the SERIAL in CUBRID has the same function as the SEQUENCE in Oracle.

The following example shows executing INSERT the primary key and the foreign key respectively after getting the NEXT_VALUE value of the SERIAL.

csql> CREATE TABLE tblPK (col INT AUTO_INCREMENT PRIMARY KEY, col2 INT);
 
csql> CREATE TABLE tblFK (colfk INT AUTO_INCREMENT PRIMARY KEY, colfk2 INT, CONSTRAINT fk_col FOREIGN KEY (colfk2) REFERENCES tblPK (col));

At this time, the name of SERIAL created by the tblPK AUTO_INCREMENT is tblpk_ai_col. You can check that by executing:

 

SELECT name FROM db_serial WHERE class_name='tblpk';

Directly insert the tblPK AUTO_INCREMENT value in the tblPK primary key and the tblPK foreign key. You don't need to save the tblFK colfk in an additional variable since it is not referred to by the other tables.

csql> SET @a=(SELECT tblpk_ai_col.NEXT_VALUE);
csql> INSERT INTO tblPK VALUES (@a,10);
csql> INSERT INTO tblFK(colfk2) VALUES (@a);

The following example shows how to write the code that returns the primary key value when a value executes INSERT in the tblPK in the iBatis sqlmaps. For the tblFK foreign key, use the returned value from the "insert" object.

SELECT tblpk_ai_col.NEXT_VALUE


INSERT INTO tblPK(col, col2) VALUES (#id#, #col2#)

Length Difference of String Functions Such as CHAR_LENGTH

For a string function, MySQL calculates the length based on the number of characters. However, CUBRID calculates it based on the character byte length. This difference depends on the database support of the character set. MySQL supports the character set and calculates the length based on the character set. However, CUBRID does not support the character set and considers all data as bytes, so it calculates the length based on the byte length.

For UTF-8 character set, the byte length of one Korean character is 3 bytes. Therefore, the return value of the CHAR_LENGTH will be three times the number of characters in CUBRID in case of Korean characters. In CUBRID, the functions related to the character length, such as POSITION, RPAD and SUBSTR, use the byte length as the input parameter, not the character length. When specifying the length of CHAR or VARCHAR also as creating a table, the byte length is used.

In the second half of this year we will release a new version of CUBRID under the code name "Apricot" with full Unicode support where the calculation will be made based on the character length, not the byte length.

The following example shows the result of executing CHAR_LENGTH in the CUBRID.

SELECT CHAR_LENGTH('CUBRIDa');
 
char length('CUBRIDa')
==================
13

Cursor Holdability After Commit while the Record Set Fetch

Cursor holdability is that an application holds the record set of the query result to fetch the next record even after performing an explicit commit or an automatic commit. In this connection, the JDBC specification supports ResultSet.HOLD_CURSORS_OVER_COMMIT and ResultSet.CLOSE_CURSORS_AT_COMMIT. Both MySQL and CUBRID ignore the configuration that uses the conn.setHoldability() function. MySQL always runs as HOLD_CURSORS_OVER_COMMIT, and CUBRID always runs as CLOSE_CURSORS_AT_COMMIT.

In MySQL, when conn.getHoldability() is called, the CLOSE_CURSORS_AT_COMMIT value is returned, which is opposite to the current operation. Refer to the source code of mysql-connector-java-5.1.20.tar.gz of http://www.mysql.com/downloads/connector/j/.

In other words, MySQL holds the cursor even if a commit occurs during fetch, and CUBRID closes the cursor when a commit occurs during fetch. Therefore, to hold the cursor while fetching the record set SELECT in CUBRID 8.4.x or lower versions, set the auto commit mode to FALSE and fetch the record set while not committing a transaction. (In the new version of CUBRID "Apricot" to be released in the second half of this year, the cursor will be held regardless of commit unless the record set is closed.)

The following schema is used below to demonstrate how cursor holdability works.

CREATE TABLE tbl1(a INT);
INSERT INTO tbl1 VALUES (1),(2),(3),(4);
CREATE TABLE tbl2(a INT);

The following example shows a program that there are four data rows on Table tbl1 while the auto commit is set to true (autocommit=true) and SELECT and INSERT the data to Table tbl2. After executing that, MySQL holds the cursor even when auto commit is executed, so four data will be INSERT in Table tbl2. On the contrary, CUBRID closes the cursor because of auto commit when INSERT is executed. Therefore, no more fetch is executed and only one data will be INSERT in Table tbl2.

public static void executeTr(Connection conn) {
    stmt=conn.createStatement();
    stmt2=conn.createStatement();
    String q1 = "SELECT a FROM tbl1";
    String q2;
    ResultSet rs = stmt.executeQuery(q1);
    conn.commit();
    while (rs.next())
    {
        int a = rs.getInt("a");
        q2="INSERT INTO tbl2 VALUES ("+a+")";
        stmt2.executeUpdate(q2);
    }
    stmt.close();
    stmt2.close();
}

Conclusion

In this article I have reviewed many (but not all) differences between MySQL and CUBRID, which I recommend users to know before they switch from MySQL to CUBRID. It is important to fully understand these differences to successfully apply CUBRID to services.

By Donghyeon Lee, NHN Business Platform DBMS Development Lab

From the author:

"Is your baby a boy?" I am a father who is asked this question even though my little baby is a girl. I will do my best until the day CUBRID becomes a DBMS that is very familiar to users.

References

  1. MySQL 5.5 Reference Manual: http://dev.mysql.com/doc/refman/5.5/en/create-index.html
  2. MySQL Performance Blog: http://www.mysqlperformanceblog.com/2006/05/09/descending-indexing-and-loose-index-scan/
  3. Interface ResultSet (Java Platform SE 7): http://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html


comments powered by Disqus