最近項目升級,需要把原來的oracle版本改為sql server版本。由於項目的分層設計,主要的修改內容也就是存儲過程,sql語句。如今改的七七八八,整理一下踩過的坑,備忘! ...
目錄
最近項目升級,需要把原來的oracle版本改為sql server版本。由於項目的分層設計,主要的修改內容也就是存儲過程,sql語句。如今改的七七八八,整理一下踩過的坑,備忘!
1.符號使用
1.1 :->@
帶參數的sql語句,oracle的參數標識使用首碼":",sql server首碼"@"。
- oracle版
SELECT * FROM table1 WHERE column1=:column1
- sql server版
SELECT * FROM table1 WHERE column1=@column1
帶參數的sql語句,在代碼中添加參數時,oracle同一個參數可以添加多次,sql server添加多次會報異常。
- oracle版本
...
db.AddInParameter(cmd, "column1", DbType.String,column1);
db.AddInParameter(cmd, "column2", DbType.String,column2);
...
db.AddInParameter(cmd, "column1", DbType.String,column1);
...
oracle可以像上面這樣,再次添加同名的參數,但是連接sql server資料庫時不允許。
1.2 mod()->%
求餘運算
1.3 ||->+
字元串連接符
oracle的||
專門用來做字元串拼接,因此類似下麵這樣的字元串和數字的拼接是可以成功的:
select 'abc'||123 from dual
但是在sql server中類似下麵的字元串和數字拼接是無法直接進行的:
select 'abc'+123
sql server傾向於將字元串轉為數字,因此報異常。如果要實現字元串拼接的效果,需要做數據類型轉換
select 'abc'+convert(varchar(10),123)
這種情況在存儲過程等複雜的操作中,做動態sql拼接時,最容易忽略。
1.4 off等表別名
oracle中的表別名off在sql server中是關鍵字,導致sql語句錯誤。因此,在使用表別名的時候我們還是儘量避免掉這種類似關鍵字的命名。
1.5 columnnum=1->top 1
oracle在排序時,可以使用columnnum=1獲取第一條記錄,sql server排序時可以使用top 1來獲取。
1.6 minus->except
oracle的minus對應sql server的except,直接替換
1.7 number->decimal
oracle中的number類型,即使設置了小數位數,比如decimal(18,2),但是資料庫不會對插入的數值補零,插入整數就是整數。但是sql server中會根據設置的數據類型,在數字末尾補零以補足小數位。這個差異會影響前端數字的格式化展示。
1.8 date -> datetime
oracle中date數據類型包括日期和時間兩部分。
sql server中date只包括日期部分,datetime包括日期和時間兩部分,timestamp遞增數字,與時間無關。
oracle中timestamp也包括日期和時間兩部分,與date的區別主要是相減時,timestamp相減結果單位是秒,date相減結果是天。兩個類型可以相互轉換。
C#中向sql server中插入日期類型時,C#預設日期是01/01/01 00:00:00,直接插入資料庫會導致數據一齣,因為C#向sql server中插入datetime數值類型時(DbType.SqlDateTime),有效日期範圍為1/1/1753 12:00:00 ~ 12/31/9999 11:59:59 PM。資料庫直接導入日期欄位,或者直接修改資料庫時,是可以設置為 1/1/1 00:00:00的。
2.函數轉換
2.1 nvl->isnull
這兩個函數完全等價,可以直接替換使用。
2.2 substr->substring
substr(str,startIndex,[length]) oracle中length參數可以省略,預設取到結尾;
substring(str,startIndex,length) sql server中length參數不能省略。
因此做替換時,要根據業務確定截取長度。
2.3 decode->case when end
oracle中的decode函數使用case when end語句替換,對於嵌套的decode函數我們可以通過組合when的邏輯表達式實現。
- oracle版
select decode(column1,0,'狀態1',1,'狀態2','其他') from table1;
select decode(column1,0,decode(column2,0,'狀態1','狀態2'),'其他') from table1;
- sql server版
select case column1
when 0 then '狀態1'
when 1 then '狀態2'
else '其他' end
from table1;
select case
when column1 = 0 and column2 = 0 then '狀態1'
when column1 = 0 then '狀態2'
else '其他' end
from table1;
2.4 數據類型轉換
- to_number(str)->convert(int,str)
- to_char(str)->convert(nvarchar(n),str)
- to_char(date,'yyyy-MM-dd')->convert(varchar(100),date,23)
- to_char(date,'yyyy-MM-dd hh24@mi@ss')->convert(varchar(100),date,20)
- to_char(date,'yyyyMMdd')->convert(varchar(100),date,112)
2.5 sq_executesql第一個參數必須是nvarchar類型
2.6 instr -> charindex
兩個函數的實現功能一樣,但是參數順序卻不同,一定要註意。
oracle版本
select instr('abcde','c') from dual
sql server版本
select charindex('c','abcde')
2.7 分組併合併列
原始數據結構
學號 | 名字 | 愛好
---|---|---
201012 | 張三 | 籃球
201012 | 張三 | 乒乓球
201013 | 李四 | 唱歌
201012 | 張三 | 羽毛球
201013 | 李四 | 羽毛球
201013 | 李四 | 繪畫
目標視圖
學號 | 名字 | 愛好
---|---|---
201012 | 張三 | 籃球,乒乓球,羽毛球
201013 | 李四 | 唱歌,羽毛球,繪畫
oracle 11g
select 學號,名字,listagg(愛好,',') within group(order by 愛好) from table1 group by 學號,名字
oracle 其他版本
select 學號,名字,wm_concat(愛好) from table1 group by 學號,名字
sql server
select 學號,名字,
stuff((select ','+愛好 from table1 for xml path('')),1,1,'') 愛好
from table1
group by 學號,名字
3. 語法規則
3.1 子查詢別名
oracle中子查詢作為from中查詢目標可以不使用別名,但是sql server中必須命名別名。
oracle版本
select column1,column2,column3,column4
from (select column1,column2,column3,column4 from table1);
sql server版本
select column1,column2,column3,column4
from (select column1,column2,column3,column4 from table1) a;
3.2 sql server樹形查詢
with定義的sql片段作為查詢對象,實現樹形查詢。
with cte(id,parentId,column1,column2,column3) as
(
select column1,column2,column3 from table1
--這裡可以接收查詢參數
--where column1 = ''
union all
select column1,column2,column3 from table1 inner join cte t1
on t1.id = t2.parentId --自下而上查詢
--on t1.parentId = t2.id --自上而下查詢
)
select * from cte;
3.3 sql server 使用merge info
項目中作為更新或插入場景使用。
merge into table1 t1
using (select @id id,@para1 column1,@para2 column2,@para3 column3) t2
on t1.id = t2.id
when matched then
update set t1.column1 = t2.column1,t1.column2 = t2.column2,t1.column3 = t2.column3
when not matched then
insert(id,column1,column2,column3) values(t2.column1,t2.column2,t2.column3)
其中oracle在一些沒有from目標的操作時,使用dual作為操作目標。sql server在這種情況下,直接不寫from部分即可。
3.4 inserted表與deleted表
The deleted table stores copies of the affected rows during DELETE and UPDATE statements. During the execution of a DELETE or UPDATE statement, rows are deleted from the trigger table and transferred to the deleted table. The deleted table and the trigger table ordinarily have no rows in common.
deleted表存儲受Delete和Update語句操作影響的行的副本。執行delete或update語句期間,受影響的行從觸發器所在表轉移到deleted表。deleted表和觸發器所在表通常沒有共同的行。
The inserted table stores copies of the affected rows during INSERT and UPDATE statements. During an insert or update transaction, new rows are added to both the inserted table and the trigger table. The rows in the inserted table are copies of the new rows in the trigger table.
inserted表存儲受insert和update語句操作影響的行的副本。執行insert或update事務時,新行被添加到inserted表和觸發器所在表。inserted表中的行是觸發器所在表新行的副本。
An update transaction is similar to a delete operation followed by an insert operation; the old rows are copied to the deleted table first, and then the new rows are copied to the trigger table and to the inserted table.
一個update事務類似於一個delete操作後跟一個insert操作。舊行先拷貝到deleted表,然後新行拷貝到觸發器所在表和inserted表。
Use the inserted and deleted Tables
3.5 insert\update\delete中使用output
利用inserted表和deleted表,輸出插入後自動生成的id等欄位,避免insert後,在執行select查詢的繁瑣。
insert into table1(column1,column2,column3)
output inserted.id,inserted.time into @para1,@para2
values(@column1,@column2,@column3)
3.6 sql語句或存儲過程資料庫執行很快,程式中執行很慢
3.6.1 程式傳遞參數類型與資料庫不一致
...
db.AddInParameter(cmd, "column1", DbType.Decimal,column1);
db.AddInParameter(cmd, "column2", DbType.String,column2);
db.AddInParameter(cmd, "column3", DbType.String,column3);
...
這裡的DbType指定的類型一定要與資料庫的數據類型保持一致,否則在執行大量數據查詢時,會存在數據類型轉換,嚴重影響sql語句執行效率。其中比較以忽略的類型
varchar -> DbType.AnsiString
nvarchar -> DbType.String
這一對是比較容易不一致的,我們一般給varchar欄位和nvarchar欄位都傳遞的DbType.String。這樣在數據量小時沒有問題,數據量大時會非常慢。
3.6.2 存儲過程執行計划過期
由於存儲過程是預編譯的,在第一次執行的時候,會生成執行計劃,以後執行的時候,會使用這個執行計劃(除非顯示指定重新編譯),而不是每次執行時都生成執行計劃。當存儲過程涉及的對象結構調整,或者相關的數據產生了很大的變化,這可能導致原來的計劃不適合當前的現狀(執行計划過期),這種情況下應該重新編譯存儲過程。
exec sp_recompile @objname='存儲過程名'