因為一早就接觸過SQL Server,所以對sql server的語法比較熟悉,后來轉學mysql,還有些不適應,所以大致總結了一些mysql和sql server語法不同的地方,其中借鑒了些此文中的內容。
因為本文是SQL server和mysql語法差異性的總結,所以內容比較雜,沒什么邏輯也不詳細,還請見諒。在后面的文章中將只說明MySQL的語法。
1. 和SQL server語法主要不同的地方
1.1 大小寫敏感性
MySQL和SQL Server一樣,對大小寫不敏感。但不同的是,在MySQL中對部分對象的引用是大小寫敏感的,如數據庫名、表名,但對字段、索引、函數、存儲過程等的引用不敏感。
1.2 注釋符
在MySQL中支持三種注釋方法:以下都可以是行內注釋。
- 使用
#
作為開頭,后面的全是注釋。 - 使用
--
作為注釋開頭,但要注意,MySQL中這種注釋方法和SQL Server等其他標准數據庫注釋語法稍有不同,MySQL要求第二個短線后面必須跟一個空白字符,如空格、制表符等。 - 使用
/**/
注釋符。
1.3 自增列
MySQL中設置自增列(auto_increment)的列必須是有索引的列,且創建表時要顯式指定的種子值需要在建表語句之后。另外MySQL一張表只能有一個自增列。且MySQL中向自增列插入數據時必須使用null來表示插入的是自增列,除非顯式指定插入列表中不包含自增列,而SQL Server向自增列插入數據時可以且必須無視該列,除非設置顯示插入模式。
-- SQL Server直接使用identity,但必須有非自增列之外的列才能插入,除非顯式開啟手動插入自增列
create table emp1(id int not null identity(1,2),name CHAR(20));
insert into emp1 VALUES('malongshuai');
insert into emp1 values('gaoxiaofang');
select * from emp1;
-- MySQL中自增列必須為索引列,並且只能設置種子值而不能直接設置步長
create table emp1(id int not null primary key AUTO_INCREMENT);
create table emp2(id int not null primary key AUTO_INCREMENT) auto_increment=100;
insert into emp1 values(null);
insert into emp2 values(null);
設置自增列的步長,分為全局級別和會話級別。但它們都是臨時生效的,重啟實例后效果就消失,要永久生效可以將其寫入配置文件中。如果是會話級別,那么當用戶新建一個會話的時候,那么步長又回到了全局級別。
mysql不能設置為表級別的步長!!
設置和查看全局和會話級別的變量時,分別使用如下語句:
set [session] auto_increment_increment=100; -- 會話級的步長設置
set global auto_increment_offset=12; -- 全局級的種子值
show [session] variables like 'auto_inc%';
show global variables like 'auto_inc%';
這兩個變量都有session級和global級。其中auto_increment_offset項為起始計算項,auto_increment_increment項為步長項。它們的處理模式和SQL server的處理方式相差甚遠。當同時設置了這兩個變量時,如果offsert設置的值大於increment的值,則offset將被忽略,且MySQL會以"offset+N\*increment"計算下一條插入的記錄值。例如,"offset=3、increment=5",當前表的最后一個自增列值為13,則下一條插入的自增值為18,因為"offset+N\*increment"將計算得到[3,8,13,18,23,28...]序列,所以從序列中挑出大於且最接近當前最后一個值13的項,即18。
以上言論為官方手冊上的解釋(原文:the next value inserted is the least value in the series that is greater than the maximum existing value in the AUTO_INCREMENT column),但實際上並不標准,更准確的說法是:根據當前offset和increment計算增長序列,並從中挑出大於或等於原序列的下一個值。例如上面offset=3,生成的序列為[3,8,13,18,23...],下一個要插入的值為18,但插入之前如果將offset改為4,則新的序列為[4,9,14,19,24],那么它將插入19,而不是14,盡管14大於當前最后一個記錄值13。同理,如果將offset改小,例如設置為2,則序列為[2,7,12,17,22],那么下一個插入的值將是22。同理,修改increment也是一樣計算的。
問:如果有一張表,里面有個字段為id的自增主鍵,當已經向表里面插入了10條數據之后,刪除了id為8、9、10的數據,再把mysql重啟,之后再插入一條數據,那么這條數據的id值應該是多少,是8還是11?
答:是11。但是在老版本中,innodb存儲引擎的表會是8,這是innodb的bug,在后來修復了,只是在mysql5.6中沒有了。更簡單地說,在未修復之前,auto_increment的值來自於內存中的自增計數器,當停止服務后,內存中的計數器就消失了,在重啟時,auto_increment的值會根據表中已有的值進行初始化。當修復該功能之后,auto_increment計數器的值會持久化。對於MariaDB而言,則是從MariaDB 10.2.4開始持久化的。
查看當前自增值的方法:
show table status like "table_name_string"; -- 查看某個表的下一個自增值
select last_insert_id(); -- 查看當前環境下最后一次自增列的插入值
關於"last_insert_id"函數,在下一篇文章"內置函數"中再做介紹。
1.4 查看表的屬性
-- SQL Server使用存儲過程sp_help
exec sp_help emp;
-- MySQL使用desc描述或者使用show
mysql> desc emp1;
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
+-------+---------+------+-----+---------+----------------+
mysql> show table status like 'emp1'\G
*************************** 1. row ***************************
Name: emp1
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 1
Avg_row_length: 16384
Data_length: 16384
Max_data_length: 0
Index_length: 0
Data_free: 0
Auto_increment: 2
Create_time: 2017-03-22 10:05:49
Update_time: NULL
Check_time: NULL
Collation: latin1_swedish_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.00 sec)
1.5 修改表名
-- SQL Server使用存儲過程sp_rename
EXEC sp_rename emp,emp2 [object]
-- mysql使用alter語句中的rename功能
alter table emp rename [to] emp3;
1.6 刪除表
在刪除表方面,MySQL比SQL Server要方便很多,判斷起來也方便很多。
-- SQL Server刪除表,每次只能刪除一張表
if object_id('table_name') is not null drop table table_name;
if exists(select object_id('table_name')) drop table table_name;
-- MySQL可以直接判斷,且一次可以刪除多表
drop table if exists table_name1,table_name2...
1.7 修改字段屬性
-- SQL Server只能修改字段屬性(數據類型、空性),不能修改約束類屬性,
-- 約束類屬性需要使用"alter table … add constraint"
alter table emp2 alter column id int not null;
alter table emp2 add gender char(2);
alter table emp2 add CONSTRAINT def_key DEFAULT('男') FOR gender;
alter table emp2 add constraint pk_key primary key clustered(id);
-- mysql修改字段屬性有幾種方法
alter table table_name | ALTER [COLUMN] col_name {SET DEFAULT string | DROP DEFAULT} | CHANGE [COLUMN] old_col_name new_col_name column_definition [FIRST|AFTER col_name] | MODIFY [COLUMN] col_name column_definition [FIRST | AFTER col_name]
也就是說,change和modify都可以修改列的定義,包括約束類的屬性、字段的位置,且change比modify更多一個重命名列的功能。而alter column則只能設置默認值和刪除默認值。注意,重命名和修改字段時,需要重新定義字段屬性。
1.8 添加、刪除字段和約束
首先需要說明的是,MySQL中任何存儲引擎都不支持check約束,官方手冊上說明了會對check語句進行讀取檢查,但是不會生效,也就是即使是對的check約束也是被忽略的。要在MySQL中實現check約束可以考慮使用觸發器或者通過數據類型來限制。
-- MySQL中添加、刪除字段和約束的語法
-- 添加字段
| ADD [COLUMN] col_name column_definition
[FIRST | AFTER col_name ]
| ADD [COLUMN] (col_name column_definition,...) -- 可以一次性添加多個字段
-- 刪除字段
| DROP [COLUMN] col_name -- 添加約束 | ADD [CONSTRAINT [symbol]] PRIMARY KEY [index_type] (index_col_name,...) [index_option] ... | ADD [CONSTRAINT [symbol]] FOREIGN KEY [index_name] (index_col_name,...) reference_definition | ADD [CONSTRAINT [symbol]] UNIQUE [INDEX|KEY] [index_name] [index_type] (index_col_name,...) [index_option] ... -- 刪除約束 | DROP PRIMARY KEY | DROP FOREIGN KEY fk_symbol | DROP {INDEX|KEY} index_name | DISABLE KEYS | ENABLE KEYS -- 添加和刪除默認值約束 | ALTER [COLUMN] col_name {SET DEFAULT literal | DROP DEFAULT}
對於MariaDB,從10.2.1開始,其支持DROP CONSTRAINT子句,並引入了DEFAULT約束,還支持check約束。且在MariaDB 10.0.2版本之后,操作字段時甚至支持if exists和if not exists。包括如下情況:
ADD COLUMN [IF NOT EXISTS]
ADD INDEX [IF NOT EXISTS]
ADD FOREIGN KEY [IF NOT EXISTS]
ADD PARTITION [IF NOT EXISTS]
CREATE INDEX [IF NOT EXISTS] DROP COLUMN [IF EXISTS] DROP INDEX [IF EXISTS] DROP FOREIGN KEY [IF EXISTS] DROP PARTITION [IF EXISTS] CHANGE COLUMN [IF EXISTS] MODIFY COLUMN [IF EXISTS] DROP INDEX [IF EXISTS]
對於SQL Server,不管是什么約束,都能使用下面的語句進行刪除,但MySQL有些麻煩,見下文具體說明:
alter table table_name drop constraint constraint_name
-
添加/刪除字段
-- SQL Server添加字段,只能一個字段一個字段添加 alter table emp add name char(20) -- MySQL添加字段,可以一次添加一個字段,也可以一次添加多個字段 ALTER TABLE emp ADD NAME CHAR(20) NOT NULL; ALTER TABLE emp ADD (gender CHAR(6) NOT NULL DEFAULT 'male',phone CHAR(11));
-
添加/刪除主鍵、外鍵約束
/*添加主鍵約束*/ -- SQL Server alter table emp10 add constraint pk_id primary key clustered(id); -- MySQL添加主鍵 ALTER TABLE emp10 ADD CONSTRAINT pk_id PRIMARY KEY (id); -- 添加外鍵約束,SQL Server和MySQL相同 ALTER TABLE emp10 ADD CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES emp20(id); -- MySQL刪除主鍵、外鍵(需要先刪除外鍵) alter table emp10 drop foreign key fk_id; alter table emp10 drop primary key;
MySQL在外鍵上和SQL Server以及Oracle都不同,MySQL在創建外鍵的時候,會自動在外鍵列上創建一個索引,且這個索引無法人為刪除。在表聯接的過程中因為會依賴性的對外表加上鎖,如果外鍵列上沒有索引,可能會加上表鎖降低並發且容易導致死鎖,如果有索引,將會進行范圍鎖定,增強並發性也減少了死鎖的出現幾率。在這一點上,MySQL比SQL Server做的要好。
-
添加唯一性約束
--SQL Server alter table emp10 add constraint uni_name unique nonclustered(name); -- MySQL添加唯一性約束 ALTER TABLE emp10 ADD CONSTRAINT uni_name UNIQUE KEY(`name`); -- MySQL刪除唯一性約束 alter table emp10 drop key uni_name
-
默認值的設置方法
SQL Server的默認值約束和MySQL的默認值約束設置方法相差很大,MySQL的默認值約束不能使用constraint來設置,只能通過修改列屬性來設置。另外,MySQL的default關鍵字后是不能加括號的,而SQL Server是無所謂的。-- SQL Server設置默認值時可有可沒有括號 create table emp10(name int not null default(12)); create table emp10(name int not null default 12); alter table emp10 add constraint def_name default 12 for name; -- MySQL設置默認值時不能使用括號 create table emp(id int not null default 12); alter table test.emp alter id set default 12; /*使用change和modify也行,但是要重定義列屬性*/ -- MySQL刪除默認值約束 alter table test.emp alter id drop default;
1.9 創建表模板
-- SQL Server
select * into table_name1 from table_name2; /*復制表結構和數據*/
select * into table_name1 from table_name2 where 1=0; /*只復制表結構*/
-- MySQL
create table table_name1 like table_name2; /*只復制表結構*/
create tbale table_name1 as select * from table_name2; /*復制表結構和數據*/
MySQL中復制表結構時不會復制主鍵、索引、自增列等任何屬性,僅僅只是簡單的建立一張表然后插入數據。但SQL Server復制表結構時會復制自增列屬性。
1.10 MySQL中的字段顯示寬度和zerofill
在MySQL中可以給整數數據類型指定結果的顯式寬度,如int(4)表示將顯示4位整數,如果實際值的位數小於顯示值寬度,則使用空格填充。而結果位數超出時將不影響顯示結果。一般該功能都會配合zerofill屬性用0代替空格填充,但是使用了zerofill后,該列就會自動變成無符號字段。
zerofill屬性的聲明必須緊跟在整數數據類型的后面,而不能跟在如not null這樣的屬性后面。
SQL Server中沒有該功能。
要注意的是顯示寬度和數據類型限制的字段寬度是不一樣的。顯示寬度不會影響字段的限制寬度,只是起一個顯示作用。
CREATE TABLE test3(id INT(2) ZEROFILL NOT NULL);
ALTER TABLE test3 MODIFY id INT(2) ZEROFILL NOT NULL;
ALTER TABLE test3 CHANGE id id INT(2) ZEROFILL NOT NULL;
INSERT INTO test3 VALUES(1),(2),(11),(111);
SELECT id FROM test3;
+-----+
| id |
+-----+
| 01 |
| 02 |
| 11 |
| 111 |
+-----+
4 rows in set (0.00 sec)
2. 數據類型
數據類型的范圍是根據bit位的數量值來計算的。4字節的int占用32bit,所以可以表示的范圍為0-2^32。
數值在存儲(或調入內存)時,以數值型方式存儲比字符型或日期時間類型更節省空間。在整數值存儲上,0-255之間的任意整數都只占一個字節,256-65535之間的任意整數都占2個字節,而占用4個字節時便可以代表幾十億個整數之間的任意一個,這顯然比字符型存儲時每個字符占用一個字節節省空間的多。例如值"100"存儲為字符型時占用三個字節,而存儲為數值型將只占用一個字節。因此數據庫默認將不使用引號包圍的值當作數值型,如果明確要存儲為字符型或日期時間型則應該使用引號包圍以避免歧義。
值 CHAR(4) 存儲需求 VARCHAR(4) 存儲需求
-----------------------------------------------------------
'' ' ' 4個字節 '' 1個字節
'ab' 'ab ' 4個字節 'ab ' 3個字節
'abcd' 'abcd' 4個字節 'abcd' 5個字節
'abcdefgh' 'abcd' 4個字節 'abcd' 5個字節
MySQL在檢索或操作char時會刪除尾隨空格,也就是說在where語句中name='gaoxiaofang '
和name='gaoxiaofang'
的結果是一樣的;若name='gaoxiaofang '
,那么concat(name,'x')
的結果將是gaoxiaofangx
。
而檢索或操作varchar時不會刪除尾隨空格。但是char類型的列和varchar類型的列進行比較會忽略尾隨空格,即 char:a
=varchar:a
。
mysql> create table test(a char(10),b varchar(10));
mysql> insert into test select 'a ','a ';
mysql> select concat(a,'x'),concat(b,'x'),a=b from test;
+---------------+---------------+-----+
| concat(a,'x') | concat(b,'x') | a=b |
+---------------+---------------+-----+
| ax | a x | 1 |
+---------------+---------------+-----+
1 row in set
關於char(M)和varchar(M),其長度是M個字符(MySQL早期版本是M字節),其字節數和字符集有關,例如latain1字符集下char(30)表示能存儲30個字符也就是30個字節,而utf8字符集下char(30)只能存儲30個字符(哪怕是英文字母),但該列將占用30*3=90個字節的空間。
mysql> create table test9(a char(2) charset utf8mb4,b char(2)) charset=latain1;
mysql> insert into test9 values('我是','wo'),('wo','wo');
mysql> select length(a),char_length(a),length(b),char_length(b) from test9;
+-----------+----------------+-----------+----------------+
| length(a) | char_length(a) | length(b) | char_length(b) |
+-----------+----------------+-----------+----------------+
| 6 | 2 | 2 | 2 |
| 2 | 2 | 2 | 2 |
+-----------+----------------+-----------+----------------+
2 rows in set (0.00 sec)'
varchar(M)的字節數還和存儲的字節數有關,每2^8次方字節增加一字節結束符。
關於日期時間的輸入方式是非常寬松的,以下幾種方式都是被允許的:任意允許的分隔符,建議使用4位的年份。
2011-01-01 18:40:20
2011/01/01 18-40-20
20110101184020
對於ENUM,插入數據時忽略大小寫。如果enum列是允許NULL的,則NULL值也是有效值。
對於SET類型,和enum類似,不區分大小寫,存儲時刪除尾隨空格,null也是有效值。但不同的是可以組合多個給出的值。如set('a','b','c','d')
可以存儲'a,b','d,b'
等,多個成員之間使用逗號隔開。所以,使用多個成員的時候,成員本身的值中不能出現逗號。並且存儲數據時忽略重復成員並按照枚舉時的順序存儲,如set('d','b','a')
,存儲'a,b,a','b,a,b'
的結果都是'b,a'
。使用find_in_set(set_value,set_column_name)
可以檢索出包含指定set值set_value的行。
SELECT * FROM test6 WHERE FIND_IN_SET('d',col)>0;
3. 數據類型屬性
3.1 unsigned
unsigned屬性就是讓數值類型的數據變得無符號化。使用unsigned屬性將會改變數值數據類型的范圍,例如tinyint類型帶符號的范圍是-128到127,而使用unsigned時范圍將變成0到255。同時unsigned也會限制該列不能插入負數值。
create table t(a int unsigned,b int unsigned);
insert into t select 1,2;
insert into t select -1,-2;
上面的語句中,在執行第二條語句准備插入負數時將會報錯,提示超出范圍。
使用unsigned在某些情況下確有其作用,例如一般的ID主鍵列不會允許使用負數,它相當於實現了一個check約束。
但是使用unsigned有時候也會出現些不可預料的問題:在進行數值運算時如果得到負數將會報錯。例如上面的表t中,字段a和b都是無符號的列,且有一行"a=1,b=2"。
mysql> select * from t;
+---+---+
| a | b |
+---+---+
| 1 | 2 |
+---+---+
1 row in set
此時如果計算"a-b"將會出錯,不僅如此,只要是unsigned列參與計算並將得到負數都會出錯。
mysql> select a-b from t;
1690 - BIGINT UNSIGNED value is out of range in '(`test`.`t`.`a` - `test`.`t`.`b`)'
mysql> select a-2 from t;
1690 - BIGINT UNSIGNED value is out of range in '(`test`.`t`.`a` - 2)'
而不是負數的結果將不會有影響。
mysql> select 2-a,a*3 from t;
+-----+-----+
| 2-a | a*3 |
+-----+-----+
| 1 | 3 |
+-----+-----+
1 row in set
這並不是MySQL中的bug,在C語言中的unsigned也一樣有類似的問題。這個問題在MySQL中設置set sql_mode='no_unsigned_subtraction'
即可解決。
3.2 zerofill
zerofill修飾字段后,不足字段顯示部分將使用0來代替空格填充,啟用zerofill后將自動設置unsigned。zerofill一般只在設置了列的顯示寬度后一起使用。關於列的顯示寬度在上文已經介紹過了。
mysql> create table t1(id int(4) zerofill);
mysql> select * from t1;
+-------+
| id |
+-------+
| 0001 |
| 0002 |
| 0011 |
| 83838 |
+-------+
4 rows in set (0.00 sec)
zerofill只是修飾顯示結果,不會改變存儲的數據值。
4. 操作符
只說明些SQL Server中沒有的運算符。詳細內容見官方手冊:函數和操作符。
4.1 安全等於運算符(<=>)
這個符號和"="進行相同的運算,但是它多出的一個功能是可以和NULL進行比較。
當比較的兩邊都是NULL時返回1而不是NULL,只有一邊是null時返回0而不是null,其余的時候和"="的結果一樣。
mysql> SELECT 1<=>NULL UNION ALL SELECT NULL<=>NULL UNION ALL SELECT 1=0 UNION ALL SELECT 1<=>0;
+----------+
| 1<=>NULL |
+----------+
| 0 |
| 1 |
| 0 |
| 0 |
+----------+
4 rows in set
4.2 正則表達式運算符(regexp或者rlike)
在SQL Server中沒有正則表達式運算符,而MySQL中有。格式:expression regexp reg_pattern
若expression滿足reg_pattern,則返回1,無法匹配成功則返回0。若expression或reg_pattern任意一方為null,則返回null。
MySQL實現的是擴展正則表達式。
mysql> SELECT 'basskd' REGEXP '^b','basskd' REGEXP 's.k','basskd' REGEXP NULL,NULL REGEXP '^b';
+----------------------+-----------------------+----------------------+------------------+
| 'basskd' REGEXP '^b' | 'basskd' REGEXP 's.k' | 'basskd' REGEXP NULL | NULL REGEXP '^b' |
+----------------------+-----------------------+----------------------+------------------+
| 1 | 1 | NULL | NULL |
+----------------------+-----------------------+----------------------+------------------+
1 row in set
4.3 連接操作符
在MySQL中,"+"不算是一種連接操作符。但是它的表達式是另有意義的:它會將兩邊的表達式嘗試轉換為數值型進行數值相加運算,如果轉換失敗的則表示為0。
mysql> select '12'+'34',12+'34','abc'+12,'a'+'abc';
+-----------+---------+----------+-----------+
| '12'+'34' | 12+'34' | 'abc'+12 | 'a'+'abc' |
+-----------+---------+----------+-----------+
| 46 | 46 | 12 | 0 |
+-----------+---------+----------+-----------+
1 row in set
要真正實現字符串連接,需要使用函數concat,參見字符串連接。
4.4 異或運算符XOR
異或運算符是用於比較兩邊值是否相同的。相同則返回0,不同則返回1,如果存在null,則直接返回null。
mysql> select 1 xor 1,0 xor 0,1 xor 0,1 xor null,null xor null;
+---------+---------+---------+------------+---------------+
| 1 xor 1 | 0 xor 0 | 1 xor 0 | 1 xor null | null xor null |
+---------+---------+---------+------------+---------------+
| 0 | 0 | 1 | NULL | NULL |
+---------+---------+---------+------------+---------------+
1 row in set
XOR運算符可以連接多次,順序是從前向后依次進行運算。
mysql> select 1 xor 1 xor 0,1 xor 1 xor 1;
+---------------+---------------+
| 1 xor 1 xor 0 | 1 xor 1 xor 1 |
+---------------+---------------+
| 0 | 1 |
+---------------+---------------+
1 row in set
4.5 MySQL中的轉義
在MySQL中某些符號需要使用反斜杠"\"來轉義。包括單引號(')、雙引號(")、反斜線(\)。
另外,如果對象名使用了關鍵字或特殊符號,也需要進行轉義,如列名使用了int關鍵字,表名使用了char關鍵字等。但是此時的轉義符號不是反斜線,而是反引號``或引號。
而在SQL Server中則不需要轉義,有歧義的時候只需使用引號即可。
mysql> INSERT INTO test7 VALUES('\\'),('\\\\'),('\''); mysql> select * from test7; +-----+ | col | +-----+ | \ | | \\ | | ' |
+-----+
3 rows in set
對於對象名的轉義,參考show create table table_name
的結果即可看到,處處使用了反引號防止歧義。例如:
mysql> show create table test7;
+-------+----------------------------------------------------------------------------+
| Table | Create Table |
+-------+----------------------------------------------------------------------------+
| test7 | CREATE TABLE `test7` (
`col` char(6) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+-------+----------------------------------------------------------------------------+
1 row in set
有時候,直接反斜線轉義單引號、雙引號是不可行的,這時可以將雙引號放在單引號中保留雙引號,或者單引號放在雙引號中保留單引號。
insert into t values('Tun"er'); # 保留雙引號 insert into t values("Tun'er"); # 保留單引號
如果情況還更復雜,則需要采用另一種機制保留單引號、雙引號:寫兩次表示轉義一個引號。例如:
insert into t values("Tun"'"er"); # 錯誤 insert into t values('Tun''er'); # 正確-->Tun'er insert into t values("Tun""er"); # 正確-->Tun"er