本文主要对使用原生 SQL 操作数据库时如何同时兼容以下多种数据库进行说明。
SQL Server
: https://learn.microsoft.com/zh-cn/sql/sql-server/PostgreSQL
: https://www.postgresql.org/docs/current/
KingBaseES
: https://help.kingbase.com.cn/v8/index.htmlMySQL
: https://dev.mysql.com/doc/refman/8.3/en/
Oracle
: https://docs.oracle.com/en/database/oracle/oracle-database/index.htmlDM8
: https://www.dameng.com/DM8.htmlSQLite
: https://www.sqlite.org/doclist.html在不同的数据库系统中,数据库 database
、模式 schema
和表 table
之间的关系可能存在一些差异。
需要注意的是,对于数据库、模式和表的命名规范和处理方式可能会有所不同。
在不同数据库中执行相关操作时,需要特别留意这些差异,以确保操作的正确性和一致性。
数据库类型 | 默认 database |
跨 database 操作方式 |
默认 schema |
自建 schema |
---|---|---|---|---|
SQL Server | master |
使用完整标识符 database .schema .table 直接进行跨库数据交互 |
dbo |
支持 |
PostgreSQL | postgres |
使用 dblink 扩展模块或外部数据包装器进行数据交互 |
public |
支持 |
MySQL | mysql |
使用联合查询、存储过程等方法进行数据交互 | 与当前数据库同名,如 mysql |
支持 |
Oracle | - | 使用数据库链接进行数据交互 | 与当前用户同名,如 SYSTEM |
支持 |
达梦 DM8 | - | 使用分布式事务或数据库链接进行数据交互 | 与当前用户同名,如 SYSDBA |
支持 |
SQLite | 文件名 | 不支持 | 无此概念 | 不支持 |
如上表所示,不同类型的数据库对于库 database
和模式 schema
的支持情况存在一些差异。
由于存在 schema
概念的数据库基本都支持跨 schema
操作,而有些数据库无法或很难进行跨数据库操作,
对于跨数据库操作需求,尽量使用多个 schema
而不是多个 database
,以确保跨数据库操作的一致性。
在 SQL Server
数据库中开发时,有时可能会在 SQL 语句中显式指定默认模式 schema
名称,如:
SELECT * FROM dbo.table_name
为兼容多种数据库,如果使用默认模式,应该去掉模式名称,直接使用表名:
SELECT * FROM table_name
在不同的数据库系统中,标识符(如表名、列名等)的长度限制可能存在差异。
一些数据库系统可能对标识符的长度有严格的限制,而另一些则可能较为宽松。
不同数据库的标识符长度限制如下所示:
序号 | 数据库类型 | 标识符限制长度 |
---|---|---|
1. | SQL Server | 128 |
2. | PostgreSQL | 63 |
3. | MySQL | 64 |
4. | Oracle | 30 |
5. | 达梦 DM8 | 128 |
6. | SQLite | 无硬性规定 |
由于以上数据库标识符限制长度最小的为 30
个字符,为了在不同数据库中保持一致的标识符长度,
建议在创建表和列时使用不超过 30
个字符的标识符命名,以确保在不同数据库中都能够正常使用。
因此,在编写 SQL 语句时,需要注意目标数据库系统的标识符长度限制,以确保标识符的命名不会超出限制,避免出现命名截断或错误。
某些数据库系统对于标识符的大小写是敏感的,在编写 SQL 语句时,应该考虑目标数据库系统的大小写敏感性,以确保使用正确的大小写命名对象。
否则,在敏感大小写的数据库系统中可能会出现无法找到对象或语法错误的问题。
例如,在 PostgreSQL
中,标识符 "tb_example"
和 "TB_EXAMPLE"
是两个不同的表,而在 SQL Server
中这两个标识符则指向同一个表:
-- DROP TABLE "tb_example";
CREATE TABLE "tb_example" (
"id" int primary key
);
SELECT * FROM "tb_example";
-- DROP TABLE "TB_EXAMPLE";
CREATE TABLE "TB_EXAMPLE" (
"ID" int primary key
);
SELECT * FROM "TB_EXAMPLE";
在标识符区分大小写的数据库系统中,使用双引号 ""
包裹标识符用于明确指定标识符的大小写,不加双引号的标识符被视为不区分大小写。
在没有明确指定标识符的大小写时:
PostgreSQL
数据库都会当作小写标识符来处理;Oracle
数据库都会当作大写标识符来处理。例如,未使用双引号 ""
包裹的标识符 tb_example
和 TB_EXAMPLE
,
在 PostgreSQL
中都指向 "tb_example"
这张表,
而在 Oracle
中都指向 "TB_EXAMPLE"
这张表:
-- 在 PostgreSQL 中都指向 "tb_example",在 Oracle 中都指向 "TB_EXAMPLE"
SELECT * FROM tb_example;
SELECT * FROM TB_EXAMPLE;
明确指定标识符,还可以防止标识符被识别为数据库关键字或保留字,避免因使用关键字作为标识符导致的语法错误。
下面是在各个数据库中如何明确指定标识符(例如表名、列名等)的语法示例:
序号 | 数据库类型 | 明确指定标识符的语法 |
---|---|---|
1. | SQL Server | 使用双引号 "" 或方括号 [] 包裹标识符 |
2. | PostgreSQL | 使用双引号 "" 包裹标识符 |
3. | MySQL | 使用反引号 `` 包裹标识符 |
4. | Oracle | 使用双引号 "" 包裹标识符 |
5. | 达梦 DM8 | 使用双引号 "" 包裹标识符 |
6. | SQLite | 使用双引号 "" 或反引号 `` 包裹标识符 |
对于同一个字段,在不同的数据库中可能需要使用不同的数据类型,如:
SQL Server
数据库中可以使用 [n]varchar(max)
或 [n]text
,而在 Oracle 数据库中则需要使用 [n]clob
;SQL Server
数据库中使用 bit
,在 Oracle 数据库中则需要使用 number[(1)]
、在其他数据库中则需要使用 boolean
。符类型在不同的数据库系统中可能有不同的命名和定义方式。
一些常见的字符类型包括字符、字符串、文本等,但具体的长度限制、字符集支持和存储方式可能会因数据库系统而异。
在设计数据库表结构时,需要考虑目标数据库系统对字符类型的支持情况,以及字符数据在不同数据库系统之间的转换和兼容性。
以下是不同数据库中支持的字符类型:
序号 | 数据库类型 | 支持的字符类型 |
---|---|---|
1 | SQL Server | char , varchar , varchar(max) , nchar , nvarchar , nvarchar(max) , text , ntext |
2 | PostgreSQL | char , varchar , bpchar , text |
3 | MySQL | char , varchar , text |
4 | Oracle | char , nchar , varchar2 , nvarchar2 , clob , nclob |
5 | 达梦 DM8 | char , varchar , text , clob |
6 | SQLite | nchar , varchar , nvarchar , text , clob |
以上所有数据库都支持的字符类型有 varchar
和 text
,因此在原生 SQL 语句中应该尽量使用这些类型。
不同的数据库系统对于数字类型的定义和支持可能存在差异。
一些常见的数字类型包括整数、小数、浮点数等,但具体的数据范围、精度和存储方式可能因数据库系统而异。
因此,在设计数据库结构时,需要根据目标数据库系统的要求选择合适的数字类型,以确保数据的准确性和完整性。
以下是不同数据库中支持的数字类型:
序号 | 数据库类型 | 支持的数字类型 |
---|---|---|
1 | SQL Server | bigint , int , smallint , tinyint , decimal (numeric ), float , real , money , smallmoney |
2 | PostgreSQL | smallint , integer , bigint , decimal (numeric ), real , double precision , smallserial , serial , bigserial , money |
3 | MySQL | tinyint , smallint , mediumint , int (integer ), bigint , decimal (numeric ), float , double , real |
4 | Oracle | number (smallint , integer ), long , binary_float , binary_double , real |
5 | 达梦 DM8 | number (decimal , numeric ), int (integer ), bigint , smallint , tinyint , float , double , real |
6 | SQLite | integer (tinyint , smallint , mediumint , int , bigint ), real (float , double ), numeric (decimal ) |
以上所有数据库都支持的数字类型有 bigint
, int/integer
, smallint
, tinyint
, decimal
, float
, real
,因此在原生
SQL 语句中应该尽量使用这些类型。
布尔类型用于表示逻辑值,通常只有两个取值,如真(true
)和假(false
)。
虽然大多数数据库系统都支持布尔类型,但某些数据库系统可能使用不同的布尔类型表示真和假,如 0
和 1
、true
和 false
等。
因此,在进行跨数据库操作时,需要注意目标数据库系统对布尔类型的定义和支持,以确保数据的一致性和正确性。
以下是不同数据库中支持的布尔类型:
序号 | 数据库类型 | 支持的布尔类型 |
---|---|---|
1 | SQL Server | bit |
2 | PostgreSQL | boolean |
3 | MySQL | tinyint (bool , boolean ) |
4 | Oracle | number(1) |
5 | 达梦 DM8 | bit , bool , boolean |
6 | SQLite | numeric (bit , boolean ) |
从上表中可以看出,只有 SQL Server 和 PostgreSQL 数据库原生支持布尔类型数据,
其他数据库通过数字类型的 0
和 1
来代表布尔类型的 true
和 false
,在跨数据库使用布尔类型的数据时可能需要额外的判断处理。
不同数据库系统对于日期时间类型的存储格式、精度和时区处理可能存在差异,一些常见的日期时间类型包括日期、时间、日期时间、时间戳等。
在设计数据库表结构时,需要考虑目标数据库系统对日期时间类型的支持情况,以及日期时间数据在不同数据库系统之间的转换和兼容性。
以下是不同数据库中支持的日期时间类型:
序号 | 数据库类型 | 支持的日期时间类型 |
---|---|---|
1 | SQL Server | date , time , datetime , datetime2 , datetimeoffset , smalldatetime |
2 | PostgreSQL | date , time , timestamp , timestamp with time zone , time with time zone , interval |
3 | MySQL | date , time , datetime , timestamp , year |
4 | Oracle | date , timestamp , timestamp with time zone , timestamp with local time zone , interval day to second , interval year to month |
5 | 达梦 DM8 | date , time , datetime (timestamp ), timestamp with time zone , timestamp with local time zone , time with time zone , interval |
6 | SQLite | numeric (date , datetime ) |
从上表可以看出,不同类型的数据库对日期时间类型的支持差异比较大,但 date
、time
、datetime
和 timestamp
是被支持最多的日期时间类型,
应该尽可能使用这几种类型。
对于在不同数据库系统中数据类型差异较大的情况,开发过程中使用 ORM
(对象关系映射框架)来操作数据库比使用原生 SQL
具有更好的兼容性。
在编写 SQL 查询语句时,使用表别名可以提高语句的可读性和性能。需要注意不同数据库系统对于表别名的语法和规范可能存在差异,
需要根据目标数据库的要求进行调整和适配。
例如,在 SQL Server
数据库中查询数据时指定别名的语法如下:
select "id" AS identifier, "data_table_name" AS table_name
from "tb_example" AS T
where T."id" = '1';
但在 Oracle
数据库中执行以上 SQL 语句时,将会报 ORA-00933: SQL 命令未正确结束
错误,
是因为 Oracle
数据库在指定表别名时不能使用 AS
关键字,而 SQL Server
数据库也支持在指定表别名时不使用 AS
关键字,
所以去掉 AS
关键字后,以下语句将同时支持在 SQL Server
和 Oracle
数据库中执行:
select "id" AS identifier, "data_table_name" AS table_name
from "tb_example" T
where T."id" = '1';
结论:
AS
关键字进行连接,以便兼容多种数据库!AS
关键字进行连接可以兼容多种数据库。不同数据库系统可能支持不同的内置函数,并且函数的语法和参数也可能有所不同。在使用内置函数时,需要查阅目标数据库的文档并注意语法的兼容性。
CAST()
:
在进行数据类型转换时使用 CAST()
函数,而不是某些数据库中特有的 CONVERT
函数:
SELECT CAST(1 AS CHAR);
-- Oracle
SELECT CAST(1 AS CHAR) FROM DUAL;
COALESCE()
:
在字段值为 NULL
时使用另一个值代替应该使用通用的 COALESCE()
函数,而不是某些数据库中特有的 ISNULL
函数:
SELECT COALESCE(NULL, 1);
-- Oracle
SELECT COALESCE(NULL, 1) FROM DUAL;
结合使用 CAST
和 COALESCE
函数以及通用 DECIMAL
数据类型,可以实现一个兼容多种数据库的将指定字段转换为数字类型的操作:
CAST(COALESCE(column_name, '0') AS DECIMAL(38, 0))
CURRENT_TIMESTAMP
:
在 SQL Server
中通常使用 GETDATE()
函数来获取当前时间,但此函数仅能在 SQL Server
中使用,可以使用通用的 CURRENT_TIMESTAMP
函数来代替 GETDATE()
函数或其他数据库中的 now()
函数:
SELECT CURRENT_TIMESTAMP;
-- Oracle
SELECT CURRENT_TIMESTAMP FROM DUAL;
在 PostgreSQL
数据库中,使用 LIKE
关键字进行模糊查询是区分大小写的,而其他数据基本是不区分大小写的。
若需要 SQL 兼容多种数据库,为了使不同数据库中的查询效果一致,在 PostgreSQL
数据库中应该使用 ILIKE
关键字:
-- PostgreSQL:LIKE 关键字区分大小写,ILIKE 关键字不区分大小写
WHERE "column_name" iLIKE '%关键字%'
-- SQL Server、Oracle、DM8
WHERE "column_name" LIKE '%关键字%'
-- MySQL、SQLite
WHERE `column_name` LIKE '%关键字%'
对于分页查询,SQL Server
和 Oracle
数据库跟其他数据库的查询方式差异较大,且不同版本之间可能也有不同的查询方式,
不同数据库对应的分页查询语法如下所示:
通过 TOP
和 ROW_NUMBER
函数进行分页查询:
SELECT TOP(10) * FROM (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) NUM, * FROM table_name) TB WHERE NUM > 0
通过 ORDER BY <column> [OFFSET <offset> ROWS] FETCH NEXT <limit> ROWS ONLY
子句进行分页查询:
SELECT * FROM table_name ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY
通过 ROW_NUMBER()
函数进行分页查询:
SELECT * FROM (SELECT T.*, ROW_NUMBER() OVER (ORDER BY column) AS ROW_NUM FROM table_name T)
WHERE ROW_NUM BETWEEN <offset>+1 AND <offset>+<limit>
通过 OFFSET <offset> ROWS FETCH NEXT <limit> ROWS ONLY
子句进行分页查询:
SELECT * FROM table_name ORDER BY (SELECT NULL FROM DUAL) OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY
通过 LIMIT <limit> OFFSET <offset>
子句进行分页查询:
SELECT * FROM table_name LIMIT 10 OFFSET 0
[!Tip]
有些数据库在分页查询时可以不显式指定排序字段,但建议
在分页查询时一定要显式指定排序字段,以确保分页查询正确、高效执行以及查询结果的顺序一致性。
若要拼接多个字段为一个字符串,并自动转换非字符类型字段为字符类型,可以使用 CONCAT()
函数或 ||
双竖线符号进行处理:
PostgreSQL
、SQL Server
、MySQL
和达梦 DM8
数据库支持使用 CONCAT()
函数拼接多个字段为一个字符串;PostgreSQL
、SQLite
和达梦 DM8
数据库支持使用 ||
双竖线符号拼接多个字段为一个字符串;PostgreSQL
和达梦 DM8
数据库两种方式都支持,如下所示:CONCAT()
函数-- PostgreSQL、SQL Server、MySQL、DM8
SELECT CONCAT('Hello ', 'World!', 123);
||
双竖线符号-- PostgreSQL、DM8、SQLite
SELECT 'Hello ' || 'World!' || 123;
-- Oracle、DM8
SELECT 'Hello ' || 'World!' || 123 FROM DUAL;
另外,
SQL Server
中纯字符串类型的拼接可以直接使用+
加号进行处理。
SQL Server 和 DM8 可通过 STUFF
和 FOR XML PATH('')
使用指定分隔符拼接多个字段为一个字符串:
/* SQL Server */
SELECT STUFF((SELECT ',' + "name" FROM sys.tables FOR XML PATH('')), 1, 1, '') table_names
-- RESULT: spt_fallback_db,spt_fallback_dev,spt_fallback_usg,spt_monitor,MSreplication_options
/* DM8 */
SELECT STUFF((SELECT ',' || "NAME" FROM SYS.SYSOBJECTS WHERE TYPE$ = 'SCH' FOR XML PATH('')), 1, 1, '') table_names
-- RESULT: CTISYS,DMHR,OTHER,PERSON,PRODUCTION,PURCHASING,RESOURCES,SALES,SYS,SYSAUDITOR,SYSDBA,SYSSSO
STRING_AGG
函数PostgreSQL 和 SQLite 数据库可通过 STRING_AGG()
函数使用指定分隔符拼接多个字段为一个字符串:
/* PostgreSQL */
SELECT STRING_AGG("table_name", ',') FROM "information_schema"."tables" WHERE "table_schema"='information_schema' AND "table_type"='BASE TABLE';
-- RESULT: sql_features,sql_implementation_info,sql_sizing
/* SQLite */
SELECT STRING_AGG("name", ',') FROM "sqlite_master" WHERE "type"='table';
-- RESULT: table_name_1,table_name_2,table_name_3
LISTAGG
函数Oracle 数据库可通过 LISTAGG()
函数使用指定分隔符拼接多个字段为一个字符串:
SELECT LISTAGG(TABLE_NAME, ',') WITHIN GROUP (ORDER BY TABLE_NAME) FROM ALL_TABLES WHERE LENGTH(TABLE_NAME) < 5 AND REGEXP_LIKE(TABLE_NAME, '^[a-zA-Z]+$');
-- RESULT: DUAL,GSM,HELP,VNCR
GROUP_CONCAT
函数MySQL 数据库可通过 GROUP_CONCAT()
函数使用指定分隔符拼接多个字段为一个字符串:
SELECT GROUP_CONCAT(`SCHEMA_NAME` ORDER BY `SCHEMA_NAME` SEPARATOR ',') FROM `information_schema`.`SCHEMATA`
-- RESULT: information_schema,mysql
未完待续...
内容声明 | |
---|---|
标题: Go 语言使用原生 SQL 兼容多种数据库 | |
链接: https://zixizixi.cn/go-sql-for-compatibility-with-multiple-databases | 来源: iTanken |
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可,转载请保留此声明。
|