問題?
??????這一次有同事在做一個小CASE時遇到問題無法解決. 整個的需求比較簡單, 是需要從資料庫中撈資料出來進行列印, 需求如下:
??????1. 從tbMain中找符合條件的客戶.限定條件:只有一筆資料的客戶
??????2. 查詢tbForeclose, 剔除掉某些客戶.剔除條件:姓名存在於tbForeclose中的客戶即要從名單中剔除
??????3. 從tbDetail中撈客戶的明細資料出來進行列印.關聯條件:依tbMain中客戶ID直接帶出即可
--
主檔(同一客戶會有多筆資料)
Create ? Table ?tbMain?(
????aID? int ? identity ( 1 , 1 ),
????pID? varchar ( 10 )? not ? null ,???? -- 客戶ID
????pName? nvarchar ( 20 )? not ? null ,???? -- 客戶姓名
????pDoing? varchar ( 20 )? not ? null
);
-- 剔除客戶檔(僅存放客戶姓名)
Create ? Table ?tbForeclose?(
????pName? nvarchar ( 20 )? not ? null ???? -- 剔除客戶姓名
);
-- 客戶資料明細檔
Create ? Table ?tbDetail?(
????pID? varchar ( 10 )? primary ? key ,???? -- 客戶ID(KEY)
????pSex? char ( 1 )? not ? null ???????? -- 客戶其他詳細資料
);
Create ? Table ?tbMain?(
????aID? int ? identity ( 1 , 1 ),
????pID? varchar ( 10 )? not ? null ,???? -- 客戶ID
????pName? nvarchar ( 20 )? not ? null ,???? -- 客戶姓名
????pDoing? varchar ( 20 )? not ? null
);
-- 剔除客戶檔(僅存放客戶姓名)
Create ? Table ?tbForeclose?(
????pName? nvarchar ( 20 )? not ? null ???? -- 剔除客戶姓名
);
-- 客戶資料明細檔
Create ? Table ?tbDetail?(
????pID? varchar ( 10 )? primary ? key ,???? -- 客戶ID(KEY)
????pSex? char ( 1 )? not ? null ???????? -- 客戶其他詳細資料

);
?





















































嘗試及解決
??????就此需求表面來看, 實現起來很是簡單, 只要一條SQL就好:
Select
???????? *
???? From
????????tbDetail
???? Inner ? Join ?(
???????? Select
????????????????pID,pName
???????????? From
????????????????tbMain
???????????? Where
???????????????? Not ? Exists ?( Select ? 1 ? From ?tbForeclose? Where ?tbForeclose.pName = tbMain.pName)
???????????? Group ? By
????????????????pID,pName
???????????? Having
???????????????? Count ( * ) = 1
????)?tbMain1? On ?tbMain1.pID = tbDetail.pID
???????? *
???? From
????????tbDetail
???? Inner ? Join ?(
???????? Select
????????????????pID,pName
???????????? From
????????????????tbMain
???????????? Where
???????????????? Not ? Exists ?( Select ? 1 ? From ?tbForeclose? Where ?tbForeclose.pName = tbMain.pName)
???????????? Group ? By
????????????????pID,pName
???????????? Having
???????????????? Count ( * ) = 1
????)?tbMain1? On ?tbMain1.pID = tbDetail.pID
??????但此需求在開發時卻有遇到一些限制條件, 不能這樣去實現.
??????限制條件:
?????????1. tbMain,tbForeclose,tbDetail三表分別存在於不同的DB, 因DB所屬系統各不相同, 線上環境則會有可能布署在不同DBServer上.
?????????2. tbMain記錄筆數極大,索引效率低下.
?????????3. 三張TABLE是屬於其他的系統, 本次需求不允許進行UPDATE的操作.
??????因此, 需要對應解決.
??????對於限制條件1, 資料處理必須依3個步驟進行(見圖上A,B,C), 如圖1:
????????????????????????

??????對於限制條件2, 主要卡在步驟A的超時上(其後都僅處理到少量資料), 實測時在本機的查詢分析器RUN步驟A的SQL就要10分鍾,符合的資料筆數為百萬左右.除非是將ADO連線設為不超時, 否則一定會查詢失敗.
??????我首先的想法就是想要提高查詢效率.?
?????????. 經檢查tbMain發現pID列上有索引而pName上卻無, 所以將步驟A進行分解, 僅查詢符合的pID, 實測步驟A1可以在10分鍾以內完成, 見圖2.
???????????????????????????

?????????. 嘗試減少步驟A1的處理筆數, 比如先依pID的前兩碼進行分組, 再進行多次查詢, 實測資料量最大的一組pID約在2-3分鍾, 單次資料量減少到十萬.
??????通過測試的結果可以看到該問題僅通過改善/分解查詢已經不能實現解決.?
??????那麼是否能夠將步驟A的筆數減少到一筆呢?
?????????. 因三檔都不允許進行UPDATE的操作, 在處理完TOP 1的一筆後因無法對處理過的pID進行標識, 無法獲取下一筆的資料.
??????既然多種方法都不可行, 那麼就只有完全改變處理的流程, 通過多添加一張表(專為此開發而新增的TABLE)並將消耗最大的步驟A交由DB內部來完成, 最終解決方法步驟如下:
??????1. 先手工新增臨時TABLE
??????
--新增臨時檔
Create?Table?tbTemp?(
????aID?int?identity(1,1),
????pID?varchar(10)
);
Create?Table?tbTemp?(
????aID?int?identity(1,1),
????pID?varchar(10)
);
??????2. 如果是需要多次RUN的話, 則要先Truncate Table tbTemp, 然後通過Insert Into tbTemp Select....在DB內部完成資料的篩選, 由於在客戶端不需要撈一筆資料, 所以耗時非常的短, 達到秒級.
??????3. 由於新增檔案是有KEY值(Identity), 所以只要拿到批次添加的最大/最小ID值即可通過循環來逐筆處理了.
??????最終解決方案如圖3
??????????????????????????????
???????????????????????????