直方圖是一種統計報告,它可以顯示在某個最小值和最大值之間的值的等級或范圍內值出現的頻率。讓我們以學生的考試成績結果為例來進行說明。假定有 30 個學生參加考試,最低分為 51,最高分為 100。您希望在最低分和最高分之間生成若干個等級,并計算每個等級內的結果數目,從而了解整個班級的分數分布情況。如果您希望生成五個范圍大小相似的連續等級,則可得到如下的等級和范圍:50 到 60、60 到 70、70 到 80、80 到 90、90 到 100(下限不包括在內,但包括上限)。該直方圖包括各個等級以及每個等級內的結果數目。
同樣,利用直方圖,您可以分析網絡中在服務器上設置的性能計數器(例如,cpu 利用率、內存)的值的采樣。例如,假設您每隔五分鐘就記錄某一網絡服務器的 cpu 利用率,某天測得的值在 21% 到 100% 之間變動。您可以生成有四個等級的直方圖 — 20 到 40、40 到 60、60 到 80、80 到 100 — 從而得出屬于每個等級的樣本數。如果您的服務器在那一天超負荷地工作,那么大多數樣本會屬于第四個等級。
生成性能計數器直方圖
下面是一個涉及直方圖的問題;您可以看看自己是否能在讀到我的解決方案之前就解決它。一個已調度的 sql server 代理作業將在一個名為 samples 的表中定期記錄某網絡服務器的性能計數器的采樣。運行 清單 1 顯示的代碼,創建 samples 表并向其中填充數據。采樣 (dt) 后,該表的每一行都包含度量 id (measid)、度量值 (value) 和表示表中其他列的篩選列(100 字節)。(例如,該表通常包括一個 serverid 列,這樣您可以記錄多個服務器的度量樣本。考慮到這個問題,在此示例中,讓該表只包含一個服務器的數據。)
假設 measid 1 是 cpu 利用率百分比,measid 2 是以兆字節為單位的內存使用量。您的用戶需要直方圖來幫助他們分析在一段時期內某個度量的性能數據。用戶提供了以下參數:等級數 (@numsteps)、度量 id (@measid) 和日期范圍(@fromdt — 包括起始日期,@todt — 不包括終止日期)。您的任務是生成這些給定參數的直方圖。請注意,您不必在結果中包括樣本數為 0 的等級。例如,假設用戶提供了以下參數:
declare @numsteps int, @measid
int, @fromdt datetime, @todt
datetime
select @numsteps=5, @measid=1,
@fromdt='20030101', @todt='20030102'
在 samples 表中,在給定期間內,measid 1 的最小度量值是 26,最大度量值是 50。在 @numsteps 參數中請求的等級數為 5。首先,您需要計算五個等級內各范圍的下限和上限。因為范圍的下限不包括在內,所以經過計算可得到以下范圍:25 到 30、30 到 35、35 到 40、40 到 45、45 到 50。
您需要編寫能告訴您每個等級有多少度量的代碼 — 在本例中,您的代碼應產生與等級 1 相匹配的一個度量 (26)、與等級 2 相匹配的兩個度量(33、35)以及與等級 5 相匹配的兩個度量(47、50)。 以下是我針對此問題提供的一些解決方案。
解決方案 1:使用等級表
第一個解決方案涉及到編寫一個生成派生表 steps 的查詢,該表包含等級號和每個等級的值的范圍。生成這樣一個表后,該解決方案就顯得簡單了。您需要完成的操作是:根據屬于等級范圍內的 value 列將派生表 steps 聯接到 samples 表、根據等級號對結果進行分組、計算每組內的行數。這個解決方案中比較困難的地方就是編寫生成派生表的查詢。要生成等級號,可以使用一個名為 nums 的輔助表,在該表中填充有一個范圍為 1 到 的整數序列。運行 清單 2 中的腳本可創建 nums 輔助表,并向其中填充 1000 個整數。
要計算每個等級范圍的下限和上限,需要交叉連接兩個查詢的結果:一個是針對 nums 的查詢,以返回等級號,另一個是針對 samples 的查詢,以返回最小度量值和最大度量值。產生的查詢可能如 清單3 所示。該查詢對每個等級號返回相同的常規最小度量值和最大度量值。這只是該解決方案中的中間結果;稍后您會將常規最小值和最大值與等級號一起使用,以便計算等級的最小值和最大值。
現在,您需要用返回的表達式替換 select 列表中的星號,此外,還需要替換等級號以及等級范圍的下限(不包括)和上限(包括)。等級號很簡單,是 n。
以下代碼是構建計算下限的表達式的基礎:
mn + *(n-1) - 1
其中,mn 是常規最小度量值,n 是等級號,step_size 是每個等級所覆蓋范圍的大小。用于計算 step_size 的表達式如下所示:
(mx-mn+1)/@numsteps
通過將這兩個表達式結合在一起,您可以得到
mn + (mx-mn+1)/@numsteps*(n-1) - 1
如果您得到的值范圍總是能用等級數來整除,那么,使用上面這個表達式就可以了,但在實際應用中,情況卻并不總是如此。下面的表達式使用數值除法和舍入法,適用于不能用等級數整除的值范圍:
mn + cast(round(1.0*(mx-mn+1)/@numsteps*(n-1), 0) as int) - 1
您可以通過將除法運算的第一個操作數與數值 1.0(讀法是一點零)相乘來進行分數除法(而非整數除法),這意味著 sql server 不會截斷小數部分。然后,您需要對結果進行舍入,以便得到整個范圍的上限和下限。之后,sql server 將經過舍入的結果轉換為整數,以便去掉小數點后沒有意義的零。您可以用相似的方式來計算上限:
mn + cast(round(1.0*
(mx-mn+1)/@numsteps*n, 0)
as int) - 1
上述表達式與前面的表達式之間的唯一區別是它將等級大小與等級號 (n) 相乘,而不是與 n-1 相乘。要測試計算派生的 steps 表的完整查詢,請運行 清單 4 中的代碼;請注意,得到的結果與圖 1 相同。
請隨便試用此解決方案的代碼中的參數,以便測試一下更改等級數對結果會有什么影響,等等。請記住,返回等級表的查詢只是該解決方案的一部分。現在,您可以在下面的偽代碼中看到缺少的、被表示為 的查詢部分:
國內最大的酷站演示中心!select step, count(*) as cnt
from samples join () as steps
on value > f and value = @fromdt and dt
如果您像我一樣喜歡使用模塊化的開發方法來簡化代碼及其維護,則可以編寫一個用戶定義函數 (udf),該函數將等級數、度量 id 和日期范圍作為參數,并返回表 steps。運行 清單 6 顯示的代碼來創建 fn_steps 函數。
要測試該函數,請運行下面的代碼:
select * from fn_steps(1, 5, '20030101', '20030102')
現在,您可以使用 fn_steps 表,而不是派生表 steps,如 清單 7 中的代碼所示。
解決方案 2:實時計算等級號
這個難題還有另一個解決方案,該方案不生成等級表,而是實時計算等級號。利用此解決方案時,您無需使用等級號輔助表。請注意 清單 8 (第 18 頁)的標注 a 中的 from 子句。您將交叉連接兩個派生表 — 一個表名為 s,包含來自 samples 的、與提供的參數相匹配的行,另一個表名為 r,包含最小度量值和整個范圍的大小。下面的表達式(在 select 列表中進行編寫)用于計算等級號:
floor((value-mn) / (
新聞熱點
疑難解答