︿
Top

2016年1月8日 星期五

SQL Server 資料庫版本控管 (Part 1) : Schema Compare


因為專案需求, 前一陣子在進行 SQL Server 的 Tables 設計, 及 Functions, Stored Procedures 的撰寫; 發現版本控管是一件很重要的事情, 萬一沒有作好, 就可能發生以下狀況, 例如:
(1) AP 與 DB 的版本無法相符, 導致程式掛掉.
(2) 個人開發境 DB, 整合測試環境 DB, 正式環境 DB 的資料庫物件, 或多或少存在一些差異, 要如何作同步?

本系列文章, 主要係參考 Will 保哥在 Slide Share 及 Channel 9 分享的 Slides影片; 再加上筆者以往的開發經驗彙整而成.

另外, 相關的方案/專案, 均是採用 Visual Studio Online 的 TFS 作為版控服務, 而不是採用 Git.



名詞定義
茲定義一些名詞或縮寫, 供後續篇章使用.


  • SSDT : SQL Server Data Tools
說明: 此為微軟所提供的 Visual Studio 套件, 以便在 Visual Studio 進行 SQL Server 資料庫的 設計, 開發, 建置, 測試, 發行, 就像在 Visual Studio 中開發應用程式一樣容易。可至此下載


  • SSMS: SQL Server Management Studio
說明: 此為 SQL Server 的前端管理, 開發整合環境.


  • Visual Studio Online
說明: 此為微軟提供的程式開發協同合作服務, 在 Visual Studio 2013 的年代, 稱為 Visual Studio Online; 在 Visual Studio 2015 的年代, 改稱為 Visual Studio Team Services. 其實, 其本質均相同; 本文一律採用 Visual Studio Online 這個名稱.


資料庫環境說明

如下圖:
  • 共有 3 位開發人員, 每位在其各自的筆電都安裝有 SQL Express
  • 開發及單元測試完成後, 要將修改過的資料庫物件 (ex: Tables, Views …) 同步到 MyDB_Test 進行整合測試
  • 整合測試完成,  要將修改過的資料庫物件 (ex: Tables, Views …) 同步到 MyDB_Prod 上線
  • 因為設備不足, 本文係以同一個 DB Server 的 3個 不同資料庫來呈現 ( MyDB, MyDB_Test, MyDB_Prod )
  • 假設目前 MyDB 及 MyDB_Test 均內含 2 個 Table, 4 個 Function, 1 個 Stored Procedure; 相關細節, 請參考 <附錄一>




應用程式說明

範例應用程式, 係為一個 Console 應用程式, 外加一個 SQL 子資料夾, 內含以下 .sql 檔; 同時加入 Visual Studio Online 進行版控.
  • Tables.sql
  • Functions.sql
  • Stored Procedures.sql


加入至 Visual Studio Online 版控

連結至您的 Visual Studio Online 主頁, 建立 TEAM 專案

https://<youridentifier>.visualstudio.com/#


將 Visual Studio 2013 的方案, 加入到剛才建立的 TEAM 專案



C:\Users\JASPER~1\AppData\Local\Temp\SNAGHTML86284a.PNG






檢視版更記錄









開始進行異動

CASE 1: 加入 Stored Procedure

加入 usp_get_all_emps 這個 stored procedure, 以取得所有的員工資料及所屬部門名稱
C:\Users\JASPER~1\AppData\Local\Temp\SNAGHTML129d129.PNG


CASE 2: 修改 Table Layout

將 DeptName 及 EmpName 由 nvarchar(10) 改為 nvarchar(20)


C:\Users\JASPER~1\AppData\Local\Temp\SNAGHTML126399b.PNG


開發環境與整合測試環境同步



在前一個部份, 我們在開發環境調整了一些資料庫的物件, 但要如何異動至整合測試環境呢?


最初想到的, 當然是自己手工寫相關的異動 Script (ex: ALTER TABLE …), 但這樣也太辛苦了, 萬一異動的內容不少, 很容易漏掉, 也很容易寫錯.


這裡就會用到 SSDT 的功能了.


我們先把目前修改後的版本, 以 SSMS 執行, 並簽入至 Visual Studio Online.


利用 SSDT 提供的功能, 同時連結開發環境與整合測試環境, 再進行比對


下一個頁面, 要特別注意 …
  • 來源: 係指較新的版本; 亦即要被採用的版本
  • 目標: 係指較舊的版本; 亦即要被蓋掉的版本


目前尚未建立至 MyDB, MyDB_Test 的連接, 所以要先建立一下.


來源資料庫設定



C:\Users\JASPER~1\AppData\Local\Temp\SNAGHTML13a3078.PNG
C:\Users\JASPER~1\AppData\Local\Temp\SNAGHTML13cdd88.PNG
C:\Users\JASPER~1\AppData\Local\Temp\SNAGHTML13d56bd.PNG


目標資料庫設定

C:\Users\JASPER~1\AppData\Local\Temp\SNAGHTML13eafa5.PNG
C:\Users\JASPER~1\AppData\Local\Temp\SNAGHTML13f6ebf.PNG
C:\Users\JASPER~1\AppData\Local\Temp\SNAGHTML13fe287.PNG


進行比較



下圖顯示了有 3 個地方有差異, 可以選擇 [更新] 或 [產生指令碼]


[產生指令碼] 產生後, 可以用 SSMS 到整合測試環境執行, 但要注意, 此時 SSMS 必須採用 SQLCmd 模式.




[更新] 就是直接把異動套用到目標, 亦即整合測試環境; 作這個動作要小心, 萬一 來源與目標剛好選反了, 那就 …


總結

採用 SQL Script 的文字檔案作為資料庫物件的描述, 看來不錯; 但總少了一些東西, 例如: 在撰寫 SQL Script 時, 沒有像 SSMS 一樣, 可以提供 intellisense 的功能.


因此, SSDT 提供了另外一種 Visual Studio 的專案類型, 稱之為 SQL Server 資料庫專案, 這也是下一篇文章要說明的部份.




參考文件





<附錄一> 相關SQL Script參考


-- =========================================================
-- Tables.sql
-- =========================================================

-- ---------------------------------------------------------
-- [00] 刪除檔案
-- ---------------------------------------------------------

-- 員工資料檔
IF EXISTS (
  SELECT *
 FROM sys.tables
 JOIN sys.schemas
   ON sys.tables.schema_id = sys.schemas.schema_id
   WHERE sys.schemas.name = N'dbo'
  AND sys.tables.name = N'Emp'
)
  DROP TABLE dbo.Emp
GO

-- 部門資料檔
IF EXISTS (
  SELECT *
 FROM sys.tables
 JOIN sys.schemas
   ON sys.tables.schema_id = sys.schemas.schema_id
   WHERE sys.schemas.name = N'dbo'
  AND sys.tables.name = N'Dept'
)
  DROP TABLE dbo.Dept
GO

-- ---------------------------------------------------------
-- [01] 部門資料檔
-- ---------------------------------------------------------
CREATE TABLE dbo.Dept
(   DeptId                  int             not null  -- 部門代碼 PKEY
,   DeptName                nvarchar(10)    not null default ''     -- 部門名稱
    CONSTRAINT PK_Dept PRIMARY KEY CLUSTERED ( DeptId )
);
GO

-- ---------------------------------------------------------
-- [02] 員工資料檔
-- ---------------------------------------------------------
CREATE TABLE dbo.Emp
(   EmpId                   int             not null  -- 員工代碼 PKEY
,   EmpName                 nvarchar(10)    not null default ''   -- 員工名稱
,   DeptId                  int             not null                -- 部門代碼 FKEY
    CONSTRAINT PK_Emp PRIMARY KEY CLUSTERED ( EmpId )
    CONSTRAINT FK_Emp_Dept FOREIGN KEY ( DeptId ) REFERENCES Dept ( DeptId ) 
);
GO

-- =========================================================
-- Functions.sql
-- =========================================================

-- ---------------------------------------------------------
-- [00] 刪除
-- ---------------------------------------------------------

-- ufn_get_date
IF OBJECT_ID (N'dbo.ufn_get_date') IS NOT NULL
   DROP FUNCTION dbo.ufn_get_date
GO

-- ufn_tomorrow
IF OBJECT_ID (N'dbo.ufn_tomorrow') IS NOT NULL
   DROP FUNCTION dbo.ufn_tomorrow
GO

-- ufn_yesterday
IF OBJECT_ID (N'dbo.ufn_yesterday') IS NOT NULL
   DROP FUNCTION dbo.ufn_yesterday
GO

-- ufn_today
IF OBJECT_ID (N'dbo.ufn_today') IS NOT NULL
   DROP FUNCTION dbo.ufn_today
GO

-- ---------------------------------------------------------
-- [01] ufn_today : 取得系統日期
-- ---------------------------------------------------------
CREATE FUNCTION dbo.ufn_today( )
RETURNS date
AS
BEGIN
    RETURN DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0);
END
GO

-- ---------------------------------------------------------
-- [02] ufn_yesterday : 取得昨天日期
-- ---------------------------------------------------------
CREATE FUNCTION dbo.ufn_yesterday( )
RETURNS date
AS
BEGIN
    RETURN DATEADD(day, DATEDIFF(day, 0, GETDATE()), -1);
END
GO

-- ---------------------------------------------------------
-- [03] ufn_tomorrow : 取得明天日期
-- ---------------------------------------------------------
CREATE FUNCTION dbo.ufn_tomorrow( )
RETURNS date
AS
BEGIN
    RETURN DATEADD(day, DATEDIFF(day, 0, GETDATE()), 1);
END
GO

-- ---------------------------------------------------------
-- [04] ufn_get_date : 取得 n 天 的日期
-- ---------------------------------------------------------
CREATE FUNCTION dbo.ufn_get_date(@pi_n int = 0)
RETURNS date
AS
BEGIN
    RETURN DATEADD(day, DATEDIFF(day, 0, GETDATE()), @pi_n);
END
GO

-- =========================================================
-- Stored Procedures.sql
-- =========================================================

-- ---------------------------------------------------------
-- [00] 刪除
-- ---------------------------------------------------------
IF EXISTS (
  SELECT *
    FROM INFORMATION_SCHEMA.ROUTINES
   WHERE SPECIFIC_SCHEMA = N'dbo'
     AND SPECIFIC_NAME = N'usp_get_all_depts'
)
   DROP PROCEDURE dbo.usp_get_all_depts
GO

-- ---------------------------------------------------------
-- [01] usp_get_all_depts: 取得所有部門資料
-- ---------------------------------------------------------

CREATE PROCEDURE dbo.usp_get_all_depts
AS
    SELECT  * 
      FROM  Dept;


    RETURN 0;
GO



沒有留言:

張貼留言