mysql和mssql對比


由於工作原因,程序需要適配兩種類型的數據庫,所以把一些sql語句寫法對比總結一下
本篇及后續隨筆都將使用一個極其簡單的場景(課室,學生,1對多)來演示,請先創建表
mysql

CREATE TABLE IF NOT EXISTS `class` (
  `Id` int(11) NOT NULL,
  `Name` varchar(50) NOT NULL DEFAULT '0',
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `student` (
  `Id` int(11) NOT NULL,
  `ClassId` int(11) NOT NULL,
  `Name` varchar(50) NOT NULL DEFAULT '0',
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

mssql

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Class](
	[Id] [int] NOT NULL,
	[Name] [nvarchar](10) NULL,
 CONSTRAINT [PK_Class] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Student](
	[Id] [int] NOT NULL,
	[ClassId] [int] NULL,
	[Name] [nvarchar](10) NULL,
 CONSTRAINT [PK_Student] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

數據的插入語句mysql和mssql一樣

INSERT Class (Id, Name) VALUES (1, '1年3班');
INSERT Class (Id, Name) VALUES (2, '1年1班');
INSERT Class (Id, Name) VALUES (3, '1年4班');
INSERT Class (Id, Name) VALUES (4, '1年2班');
INSERT Student (Id, ClassId, Name) VALUES (1, 3, '小a');
INSERT Student (Id, ClassId, Name) VALUES (2, 1, '小b');
INSERT Student (Id, ClassId, Name) VALUES (3, 2, '小c');
INSERT Student (Id, ClassId, Name) VALUES (4, 1, '小d');
INSERT Student (Id, ClassId, Name) VALUES (5, 2, '小e');
INSERT Student (Id, ClassId, Name) VALUES (6, 3, '小f');
INSERT Student (Id, ClassId, Name) VALUES (7, 2, '小h');
INSERT Student (Id, ClassId, Name) VALUES (8, 3, '小i');
INSERT Student (Id, ClassId, Name) VALUES (9, 4, '小j');
INSERT Student (Id, ClassId, Name) VALUES (10, 3, '小k');
INSERT Student (Id, ClassId, Name) VALUES (11, 3, '小l');
INSERT Student (Id, ClassId, Name) VALUES (12, 3, '小m');
INSERT Student (Id, ClassId, Name) VALUES (13, 4, '小n');
INSERT Student (Id, ClassId, Name) VALUES (14, 1, '小o');
INSERT Student (Id, ClassId, Name) VALUES (15, 4, '小p');
INSERT Student (Id, ClassId, Name) VALUES (16, 2, '小q');

  

p.s. mysql和mssql的表名不區分大小寫,但是避免混淆,演示時mssql會首字母大寫

一、分頁寫法

假設一頁5條數據,取第1頁

1.1 主流寫法

mysql

select * from student limit 0, 5

mssql(分頁前必須要order by 一下,否則報錯,mysql沒有這限制)

select * from Student order by Id offset(5 * 0) rows fetch next 5 rows only

1.2 用行號來分頁 

mysql:通過自定義變量使用行號

SELECT * from (SELECT @row:=@row+1 AS row, student.* FROM student, (select @row := 0) r ORDER BY student.Id) student WHERE row > (0 * 5)  AND row <= ((0+1) * 5)

mssql:通過系統函數來使用行號

select * from (select ROW_NUMBER() over(order by Id) as row, * from Student) as Student where row > (0 * 5) and row <= ((0+1) * 5)

好像還有between and 的寫法也行

 

二、取前幾條數據

mysql沒有top的寫法,只能用limit 0, 1

select * from student limit 0, 1

mssql則有top

select top 1 * from Student

p.s. 鑒於mssql有top可以用,所以mssql可以改上面的行號分頁方式,去掉row <= ((0+1) * 5)條件,使用top 5

 

三、插入數據時如果有條件,且數據源不來自舊表,mysql需要加上from dual而mssql則不用

給1年1班新增一個學生,但不要重復新增

mysql

INSERT INTO student(Id, ClassId, Name) SELECT 17, 1, '小r' FROM dual WHERE NOT EXISTS(SELECT 1 FROM student WHERE ClassId = 1 AND NAME = '小r')

 mssql

INSERT INTO student(Id, ClassId, Name) SELECT 17, 1, '小r' WHERE NOT EXISTS(SELECT 1 FROM student WHERE ClassId = 1 AND NAME = '小r')

 

四、判斷結果是否為null,若為null則用別的代替

mysql:IFNULL([可能為null的字段], 默認值)

mssql:ISNULL([可能為null的字段], 默認值)

 

五、mysql不支持in查詢里面分頁,但是mssql支持

SELECT * FROM student WHERE Id IN (SELECT Id FROM class LIMIT 0, 1)

mysql會報錯“This version of MariaDB doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'”

 

六、mysql和mssql都有distinct和group by來去重,但是mssql的order by 后面的字段必須是distinct或group by中出現的字段,而mysql無此限制

SELECT DISTINCT class.* FROM class JOIN student ON class.Id = student.ClassId ORDER BY student.Name
SELECT class.* FROM class JOIN student ON class.Id = student.ClassId GROUP BY class.Id, class.Name ORDER BY student.Name

如上語句mysql正確執行,而mssql會報錯“ORDER BY 子句中的列 "student.Name" 無效,因為該列沒有包含在聚合函數或 GROUP BY 子句中。”

mssql可以用以下語句實現同等效果

SELECT class.* FROM class JOIN student ON class.Id = student.ClassId GROUP BY class.Id, class.Name ORDER BY (select top 1 Name from Student where ClassId = class.Id)

注意,用distinct去重時(如下語句),連這樣改排序條件都不行,會報錯“如果指定了 SELECT DISTINCT,那么 ORDER BY 子句中的項就必須出現在選擇列表中。”[mssql真坑]

SELECT DISTINCT class.* FROM class JOIN student ON class.Id = student.ClassId ORDER BY (select top 1 Name from Student where ClassId = class.Id)

這里總結為:多表查詢只查主表,排序依據用子表的問題,mysql可以直接寫,而mssql就必須轉換一下,且用distinct還無法實現需求。

☆☆☆個人推測:在mysql中order by的執行順序是在distinct和group by之前(網上寫的都是之后,我認為是錯的,各位看官可以查“mysql語句執行循序”),而mssql則是在這兩之后,不然怎么會一定要求排序條件要是去重的字段呢(雖然這兩個誰前誰后最終效果是一樣的)。☆☆☆

*(代碼場景)通常排序依據都是外部傳入的,查詢方法本身不能知道排序依據會不會有子表的,有多少個,這里若用mssql作為數據倉儲就需要多這一步額外的判斷,程序相對就復雜了。[mssql真坑]

 

七、多表查詢,主表分頁
查詢:班級及班級所有學生的分頁,假設分頁大小是2,取第2頁,要求按學生名字排序

查詢出來的主表的數據應該是2條,但由於join了,所以總數據行數應該是多於2行的
主表class

子表student

按照學生名字靠前的排序情況下,班級的順序應該是“4班”、“3班”、“1班”、“2班”

7.1 join多1張表來限定主表的范圍
mysql

SELECT class.*, student.* FROM class JOIN student ON class.Id = student.ClassId JOIN (
SELECT class.Id FROM class WHERE EXISTS(SELECT 1 FROM class JOIN student ON class.Id = student.ClassId WHERE 1=1) ORDER BY (select Name from student WHERE ClassId = class.Id LIMIT 0, 1) LIMIT 2, 2
) temp ON class.Id = temp.Id

 

 mssql

select Class.*, Student.* from Class join Student on Class.Id = Student.ClassId join (
select Class.Id from Class where exists(select 1 from Class join Student on Class.Id = Student.ClassId where 1=1) order by (select top 1 Name from Student where ClassId = Class.Id) offset (1 * 2) rows fetch next 2 rows only
) Temp on Class.Id = Temp.Id

 

 

7.2 使用distinct或者group by去重,效率較低,除非迫不得已,否則不用

mysql

SELECT class.*, student.* FROM 
(
SELECT DISTINCT class.* FROM class JOIN student ON class.Id = student.ClassId ORDER BY student.Name LIMIT 2, 2
) AS class JOIN student ON class.Id = student.ClassId

 

mssql

select Class.*, Student.* from
(
SELECT Class.* FROM Class JOIN Student ON Class.Id = Student.ClassId GROUP BY Class.Id, Class.Name ORDER BY (select top 1 Name from Student where ClassId = Class.Id) offset(1 * 2) rows fetch next 2 rows only 
) as Class join Student on Class.Id = Student.ClassId
ORDER BY (select top 1 Name from Student where ClassId = Class.Id)

結果為

 總結:7.1的寫法較優,也是筆者推薦寫法,當然,使用in (子查詢查主表Id范圍)的做法在mssql也是異曲同工之妙,但為了mysql也適用,還是用join多一張表的做法吧。7.2的相比於7.1而言,效率低了很多(數據量少時體驗差不多的,只有數據量大和表的字段多時才是天差地別),要說優點的話,只有mysql的沒有上面第六點那里的*(代碼場景)的不便之處。

關於排序,還有一個細節,細心的讀者會發現mysql和mssql查詢的結果是有一點排序的差別的,雖然數據都是一樣的;這是兩種數據庫引擎的默認排序問題,這里不深究,推薦做法是在最外層的查詢中再寫一次排序條件,如上面mssql的order by寫法那樣

 

八、多表更新

mysql的多表更新要把查詢用到的表寫在set前面,更新內容可以是任意一個表的字段

UPDATE class JOIN student ON class.Id = student.ClassId SET student.NAME = '小a', class.Name = '1年4班' WHERE student.name = '小a'

注意:多表更新時mysql不能更新查詢子表,也就是如下這樣寫是錯誤的,會提示“The target table c2 of the UPDATE is not updatable”,更新c1還是可以的

update Class c1 JOIN (SELECT * FROM class where NAME = '不存在' order by Id LIMIT 0, 5) c2 ON c1.Id = c2.Id SET c2.Id = 100

mysql可以使用limit限定一次更新多少行,limit 寫在最后面

update Class c1 JOIN (SELECT * FROM class where NAME = '不存在') c2 ON c1.Id = c2.Id SET c1.Id = 100 LIMIT 100

 

mssql的多表更新則是只能更新其中一個表,查詢用到的表寫在set后面,還要有from

UPDATE Student SET NAME = '小a' from Class JOIN Student ON Class.Id = Student.ClassId WHERE Student.Name = '小a'

注意:多表更新時mssql可以更新查詢子表,但有兩個限制
1、不能更新派生字段和常量派生字段就是對某一個或幾個字段進行了運算,如下會報錯“派生表 'c2' 不可更新,因為派生表中的某一列是派生的或是常量。”

update c2 set c2.Id = 1 from Class c1 JOIN (SELECT (Id * 100) as Id FROM class where NAME = '1年3班' order by Id offset(5 * 0) rows fetch next 5 rows only) c2 ON c1.Id = c2.Id

2、不能更新多個表的字段,如下會報錯“派生表 'temp' 不可更新,因為修改會影響多個基表。”

update temp set temp.cId = 100, temp.Id = 100 from (select Class.Id cId, Class.Name cName, Student.* from Class join Student on Class.Id = Student.ClassId) temp where temp.cName = '不存在'

mssql只能寫查詢子表來實現限定一次更新幾行的效果

update c1 set c1.Name = '1年3班' from Class c1 JOIN (SELECT Id FROM class where NAME = '1年30班' order by Id offset(5 * 0) rows fetch next 5 rows only) c2 ON c1.Id = c2.Id

總結:mysql在更新方面比mssql要靈活

 

九、多表刪除
兩者的多表刪除語句以及限制跟多表更新一樣,還有個不值得一提的是mssql單表刪除時,可以不寫from

mysql

DELETE class, student FROM class JOIN student ON class.Id = student.ClassId WHERE student.name = '不存在'

注意:mysql雖然可以多表更新時用limit,但多表刪除時卻不能用limit(單表刪除可以用),這有點奇怪。此時就必須像mssql那樣使用查詢子表的方式來“limit”了

mssql

DELETE c1 FROM class c1 JOIN (SELECT * FROM class where NAME = '不存在' order by Id offset(5 * 0) rows fetch next 5 rows only) c2 ON c1.Id = c2.Id

 

 十、更新或插入

假設Id就是學生的學號,每個班的學號是不能重復的,現插入小s,班級是2班(班級Id=4),學號(學生Id)是18,此數據的寫入處於一種高並發場景,比如某教職工快速點擊插入(只是打個比喻,不要較真)

mysql
需要在建表時創建唯一索引,然后插入或更新時,只能根據唯一索引來判斷是否同一條數據

INSERT INTO Student(Id, ClassId, NAME) SELECT 18, 4, '小s' ON DUPLICATE KEY UPDATE ClassId = 4, NAME = '小s';

注1:update 多個字段時,必須要用英文逗號隔開,用and隔開的會更新失敗且MySQL不會有語法錯誤提示
mssql

不需要在建表時指定創建唯一索引,插入或更新時,再根據指定的幾個字段判斷是否是同一條數據(mssql終於有地方比mysql好的了)

merge Student with(HOLDLOCK) as target
using (select 18 Id, 4 ClassId, '小s' Name) as source
on (target.Name = source.Name) -- 
when matched then update set ClassId = 4, Name = '小s'
when not matched then insert (Id, ClassId, Name) values(18, 4, '小s');

詳細參見微軟官方文檔

十一、跨庫查詢的表名寫法
mysql:`庫名`.`表名`

mssql:[庫名]..[表名]

p.s. mysql中的對象用``(Esc鍵下面那個)包起來,mssql中的對象用[]包起來,對於某些特殊名稱的對象不包起來會報錯,如time、key等,還有數據庫名稱本身就包含了.的,不包起來就會截斷錯誤


十二、獲取行號
在1.2分頁的寫法中,也介紹了行號的使用,這里重復說一下
mysql:用過英文逗號加一張沒關系的表,表中是一個變量,每次打印行數據時,變量+1顯示

SELECT @row:=@row+1 AS row, student.* FROM student, (select @row := 0) r

mssql:

select ROW_NUMBER() over(order by Id) as row, * from Student


十三、通過使用行號,偽造snowflakeId插入數據


p.s. 此隨筆只要筆者踩到新坑就會更新


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM