|
Posted on 2008-11-11 18:55 大大毛 阅读(505) 评论(0) 编辑 收藏 所属分类: SQL
問題
這一次有同事在做一個小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
--
客戶其他詳細資料
);
示例數據
SET
NOCOUNT
ON
Declare
@iRowCount
int
Declare
@pID
varchar
(
10
),
@pName
nvarchar
(
20
),
@pDoing
varchar
(
20
),
@pSex
char
(
1
)
Declare
@i
int
,
@j
int
,
@k
int
--
生成tbMain記錄條數
Set
@iRowCount
=
1000
Select
@i
=
1
While
@i
<
@iRowCount
Begin
--
生成客戶ID
Set
@j
=
1
While
@j
>
0
Begin
Set
@pID
=
Left
(
NewID
(),
10
)
Select
@j
=
Count
(
*
)
From
tbDetail
Where
pID
=
@pID
End
--
生成客戶姓名
Set
@k
=
2
+
Round
(
3
*
Rand
(),
0
)
Select
@j
=
1
,
@pName
=
''
While
@j
<=
@k
Begin
Set
@pName
=
@pName
+
NChar
(
31000
+
Round
(
3000
*
Rand
(),
0
))
Set
@j
=
@j
+
1
End
--
生成客戶性別
If
@k
%
2
=
0
Set
@pSex
=
'
T
'
Else
Set
@pSex
=
'
F
'
--
添加資料
--
主檔添加筆數(1-5筆)
Set
@k
=
1
+
Round
(
5
*
Rand
(),
0
)
Set
@j
=
1
While
@j
<=
@k
Begin
Set
@i
=
@i
+
1
--
生成Doing欄位
Set
@pDoing
=
Cast
(
@j
As
Varchar
(
2
))
+
'''
th Doing
'
Insert
Into
tbMain
Values
(
@pID
,
@pName
,
@pDoing
)
Set
@j
=
@j
+
1
End
--
客戶資料明細檔
Insert
Into
tbDetail
Values
(
@pID
,
@pSex
)
End
--
隨機向剔除客戶檔添加1/10的客戶姓名
Set
@k
=
Round
(
@iRowCount
/
10
,
0
)
SET
ROWCOUNT
@k
Insert
Into
tbForeclose
Select
pName
From
tbMain
Group
By
pID,pName
Order
By
NewID
()
SET
ROWCOUNT
0
SET
NOCOUNT
OFF
嘗試及解決
就此需求表面來看, 實現起來很是簡單, 只要一條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
但此需求在開發時卻有遇到一些限制條件, 不能這樣去實現. 限制條件: 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) ); 2. 如果是需要多次RUN的話, 則要先Truncate Table tbTemp, 然後通過Insert Into tbTemp Select....在DB內部完成資料的篩選, 由於在客戶端不需要撈一筆資料, 所以耗時非常的短, 達到秒級. 3. 由於新增檔案是有KEY值(Identity), 所以只要拿到批次添加的最大/最小ID值即可通過循環來逐筆處理了. 最終解決方案如圖3
|