Ở mức đơn giản nhất hàm kiểu bảng có thể chỉ là một câu lệnh SELECT đơn thuần. Ví dụ tôi cần viết một hàm lấy về thông tin của các sản phẩm dựa trên ModelID:
--ví dụ 1
USE AdventureWorks
GO
CREATE FUNCTION dbo.fn_ProductInfoByModelID(@p INT) RETURNS TABLE
AS
RETURN
SELECT P.ProductID, P.Name, P.ProductNumber
FROM Production.Product P
WHERE P.ProductModelID = @p
GO
-- gọi hàm trực tiếp
SELECT * FROM dbo.fn_ProductInfoByModelID(5)
--hoặc join với hàm
SELECT TH.*, P.Name
FROM Production.TransactionHistory TH
JOIN dbo.fn_ProductInfoByModelID(5) P ON TH.ProductID = P.ProductID
Với những tình huống phức tạp hơn, một câu lệnh duy nhất có thể không đủ để giải quyết được vấn đề. Lúc đó bạn cần viết hàm kiểu multi-statement. Ví dụ tôi cần một hàm chuyển đổi chuỗi phân cách bằng dấu phẩy (,) thành bảng:
--ví dụ 2
CREATE FUNCTION dbo.fnCSVStr2Table(@CSVStr VARCHAR(8000))
RETURNS @Tbl TABLE (ValueColumn VARCHAR(1000))
AS
BEGIN
DECLARE @SubStr VARCHAR(100), @i INT
SET @i = CHARINDEX(',', @CSVStr, 0)
WHILE @i > 0
BEGIN
SET @SubStr = LEFT(@CSVStr,@i-1)
INSERT INTO @Tbl
SELECT @SubStr
SET @CSVStr = SUBSTRING(@CSVStr, @i+1,8000)
SET @i = CHARINDEX(',', @CSVStr, 0)
END
INSERT INTO @Tbl
SELECT LTRIM(RTRIM(@CSVStr))
RETURN
END
Khác biệt giữa hàm in-line và hàm multi-statement
Tuy nhiên khác biệt quan trọng nhất giữa hai loại hàm trên là ở hiệu năng. Khi bạn JOIN với một hàm in-line, bộ Optimizer có thể truy nhập vào thông tin index và statistics của các bảng được sử dụng trong hàm khi lựa chọn phương án thực thi. Nó biết các bảng nào được sử dụng và vì chỉ có một lệnh được dùng trong hàm, nên có thể viết lại câu lệnh và gộp chung phương án thực thi của hàm vào phương án chung của cả câu lệnh. Điều này tương tự như khi bạn JOIN với một view, bộ Optimizer sẽ mở rộng câu lệnh dùng trong view, gộp chung với câu lệnh chính và tối ưu hóa cho cả câu lệnh to. Vì vậy kết quả sẽ là câu lệnh luôn có một phương án thực thi tối ưu.
Ngược lại, với hàm multi-statement, bộ Optimizer “mù tịt” với những gì xảy ra bên trong hàm. Nó vẫn thực hiện tối ưu hóa cho từng lệnh bên trong hàm, nhưng không thể “xé nhỏ” nó ra để tối ưu chung cho cả câu lệnh. Nói cách khác, phương án thực thi của hàm nằm độc lập với phương án thực thi của câu lệnh chính. Trên thực tế, bộ Optimizer luôn giả định tập kết quả do hàm trả về chứa 1 bản ghi khi thực hiện tối ưu câu lệnh. Giả định này là không đúng trong đại đa số trường hợp nên thường dẫn đến phương án thực thi dở tệ.
Ví dụ
--ví dụ 3
SELECT TH.*, P.Name
FROM Production.TransactionHistory TH
JOIN dbo.fn_ProductInfoByModelID(5) P ON TH.ProductID = P.ProductID
Bạn có thể thấy không có bước gọi đến hàm mà là hai bước Clustered Index Scan trên hai bảng và sau đó là Hash Match, giống như câu lệnh JOIN trực tiếp giữa hai bảng. Phương án thực thi này giống hệt với khi bạn thực hiện câu lệnh:
--ví dụ 4
SELECT *
FROM Production.TransactionHistory TH
JOIN Production.Product P ON TH.ProductID = P.ProductID
WHERE P.ProductModelID = 5
--ví dụ 5
CREATE FUNCTION dbo.fn_ProductInfoByModelID_MSTV(@p INT)
RETURNS @t TABLE(ProductID INT, Name NVARCHAR(50), ProductNumber NVARCHAR(25))
AS
BEGIN
INSERT INTO @t
SELECT P.ProductID, P.Name, P.ProductNumber
FROM Production.Product P
WHERE P.ProductModelID = @p
RETURN
END
--ví dụ 6
SELECT *
FROM Production.TransactionHistory TH
JOIN dbo.fn_ProductInfoByModelID_MSTV(5) P ON TH.ProductID = P.ProductID
Bây giờ bước thực hiện hàm đã trở thành một bước độc lập (trên cùng bên trái), và chi phí của nó được gán là 0% vì bộ Optimizer không có tí thông tin nào về bên trong của hàm. Khỏi cần nói phương án thực thi giờ đã trở nên rườm rà như thế nào. Và đây là thống kê vào/ra của hai lệnh gọi hàm:
Lệnh gọi hàm in-line (ví dụ 5):
Table ‘TransactionHistory’. Scan count 1, logical reads 792, physical reads 8, read-ahead reads 788.
Table ‘Product’. Scan count 1, logical reads 15, physical reads 3, read-ahead reads 14
Table ‘#0BC6C43E’. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0
Kết luận
Trong hai kiểu hàm in-line và multi-statement, trước hết bạn nên chọn viết kiểu in-line vì lý do khai báo đơn giản và có ưu thế về hiệu năng. Khi cần thiết phải dùng hàm multi-statement bạn nên khống chế số bản ghi trả về ở số lượng nhỏ.
Phiên bản áp dụng: SQL Server 2005 trở lên
0 nhận xét:
Post a Comment
Click to see the code!
To insert emoticon you must added at least one space before the code.