Cách hiệu quả nhất để gọi cùng một Hàm có giá trị bảng trên nhiều cột trong Truy vấn


8

Tôi đang cố gắng điều chỉnh một truy vấn trong đó cùng một hàm có giá trị bảng (TVF) được gọi trên 20 cột.

Điều đầu tiên tôi làm là chuyển đổi hàm vô hướng thành hàm có giá trị bảng nội tuyến.

Là sử dụng cách CROSS APPLYthực hiện tốt nhất để thực hiện cùng một chức năng trên nhiều cột trong một truy vấn?

Một ví dụ đơn giản:

SELECT   Col1 = A.val
        ,Col2 = B.val
        ,Col3 = C.val
        --do the same for other 17 columns
        ,Col21
        ,Col22
        ,Col23
FROM t
CROSS APPLY
    dbo.function1(Col1) A
CROSS APPLY
    dbo.function1(Col2) B
CROSS APPLY
    dbo.function1(Col3) C
--do the same for other 17 columns

Có những lựa chọn thay thế tốt hơn?

Hàm tương tự có thể được gọi trong nhiều truy vấn đối với số cột X.

Đây là chức năng:

CREATE FUNCTION dbo.ConvertAmountVerified_TVF
(
    @amt VARCHAR(60)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
    WITH cteLastChar
    AS(
        SELECT LastChar = RIGHT(RTRIM(@amt), 1)
    )
    SELECT
        AmountVerified  = CAST(RET.Y AS NUMERIC(18,2))
    FROM (SELECT 1 t) t
    OUTER APPLY (
        SELECT N =
                CAST(
                    CASE 
                        WHEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, '{ABCDEFGHI}', 0) >0
                            THEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, '{ABCDEFGHI}', 0)-1
                        WHEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, 'JKLMNOPQR', 0) >0
                            THEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, 'JKLMNOPQR', 0)-1
                        WHEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, 'pqrstuvwxy', 0) >0
                            THEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, 'pqrstuvwxy', 0)-1
                        ELSE 
                            NULL
                    END
                AS VARCHAR(1))
        FROM
            cteLastChar L
    ) NUM
    OUTER APPLY (
        SELECT N =
            CASE 
                WHEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, '{ABCDEFGHI}', 0) >0
                    THEN 0
                WHEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, 'JKLMNOPQRpqrstuvwxy', 0) >0
                    THEN 1
                ELSE 0
            END
        FROM cteLastChar L
    ) NEG
    OUTER APPLY(
        SELECT Amt= CASE
                        WHEN NUM.N IS NULL
                            THEN @amt 
                        ELSE
                            SUBSTRING(RTRIM(@amt),1, LEN(@amt) - 1) + Num.N
                    END
    ) TP
    OUTER APPLY(
        SELECT Y =  CASE
                        WHEN NEG.N = 0
                            THEN (CAST(TP.Amt AS NUMERIC) / 100)
                        WHEN NEG.N = 1
                            THEN (CAST (TP.Amt AS NUMERIC) /100) * -1
                    END
    ) RET
) ;

GO

Đây là phiên bản hàm vô hướng mà tôi được thừa hưởng, nếu có ai quan tâm:

CREATE   FUNCTION dbo.ConvertAmountVerified 
(
    @amt VARCHAR(50)
)
RETURNS NUMERIC (18,3)  
AS
BEGIN   
    -- Declare the return variable here
    DECLARE @Amount NUMERIC(18, 3);
    DECLARE @TempAmount VARCHAR (50);
    DECLARE @Num VARCHAR(1);
    DECLARE @LastChar VARCHAR(1);
    DECLARE @Negative BIT ;
    -- Get Last Character
    SELECT @LastChar = RIGHT(RTRIM(@amt), 1) ;
    SELECT @Num = CASE @LastChar  collate latin1_general_cs_as
                        WHEN '{'  THEN '0'                                  
                        WHEN 'A' THEN '1'                       
                        WHEN 'B' THEN '2'                       
                        WHEN 'C' THEN '3'                       
                        WHEN 'D' THEN '4'                       
                        WHEN 'E' THEN '5'                       
                        WHEN 'F' THEN '6'                       
                        WHEN 'G' THEN '7'                       
                        WHEN 'H' THEN '8'                       
                        WHEN 'I' THEN '9'                       
                        WHEN '}' THEN '0'   
                        WHEN 'J' THEN '1'
                        WHEN 'K' THEN '2'                       
                        WHEN 'L' THEN '3'                       
                        WHEN 'M' THEN '4'                       
                        WHEN 'N' THEN '5'                       
                        WHEN 'O' THEN '6'                       
                        WHEN 'P' THEN '7'                       
                        WHEN 'Q' THEN '8'                       
                        WHEN 'R' THEN '9'

                        ---ASCII
                        WHEN 'p' Then '0'
                        WHEN 'q' Then '1'
                        WHEN 'r' Then '2'
                        WHEN 's' Then '3'
                        WHEN 't' Then '4'
                        WHEN 'u' Then '5'
                        WHEN 'v' Then '6'
                        WHEN 'w' Then '7'
                        WHEN 'x' Then '8'
                        WHEN 'y' Then '9'

                        ELSE ''

                END 
    SELECT @Negative = CASE @LastChar collate latin1_general_cs_as
                        WHEN '{' THEN 0         

                        WHEN 'A' THEN 0                 
                        WHEN 'B' THEN 0                     
                        WHEN 'C' THEN 0                     
                        WHEN 'D' THEN 0                     
                        WHEN 'E' THEN 0                     
                        WHEN 'F' THEN 0                     
                        WHEN 'G' THEN 0                     
                        WHEN 'H' THEN 0                     
                        WHEN 'I' THEN 0                     
                        WHEN '}' THEN 1 

                        WHEN 'J' THEN 1                     
                        WHEN 'K' THEN 1                     
                        WHEN 'L' THEN 1                     
                        WHEN 'M' THEN 1                 
                        WHEN 'N' THEN 1                     
                        WHEN 'O' THEN 1                     
                        WHEN 'P' THEN 1                     
                        WHEN 'Q' THEN 1                     
                        WHEN 'R' THEN 1

                        ---ASCII
                        WHEN 'p' Then '1'
                        WHEN 'q' Then '1'
                        WHEN 'r' Then '1'
                        WHEN 's' Then '1'
                        WHEN 't' Then '1'
                        WHEN 'u' Then '1'
                        WHEN 'v' Then '1'
                        WHEN 'w' Then '1'
                        WHEN 'x' Then '1'
                        WHEN 'y' Then '1'
                        ELSE 0
                END 
    -- Add the T-SQL statements to compute the return value here
    if (@Num ='')
    begin
    SELECT @TempAmount=@amt;
    end 
    else
    begin
    SELECT @TempAmount = SUBSTRING(RTRIM(@amt),1, LEN(@amt) - 1) + @Num;

    end
    SELECT @Amount = CASE @Negative
                     WHEN 0 THEN (CAST(@TempAmount AS NUMERIC) / 100)
                     WHEN 1 THEN (CAST (@TempAmount AS NUMERIC) /100) * -1
                     END ;
    -- Return the result of the function
    RETURN @Amount

END

Dữ liệu thử nghiệm mẫu:

SELECT dbo.ConvertAmountVerified('00064170')    --  641.700
SELECT * FROM dbo.ConvertAmountVerified_TVF('00064170') --  641.700

SELECT dbo.ConvertAmountVerified('00057600A')   --  5760.010
SELECT * FROM dbo.ConvertAmountVerified_TVF('00057600A')    --  5760.010

SELECT dbo.ConvertAmountVerified('00059224y')   --  -5922.490
SELECT * FROM dbo.ConvertAmountVerified_TVF('00059224y')    --  -5922.490

Câu trả lời:


8

FIRST: cần đề cập rằng phương pháp hoàn toàn nhanh nhất để có được kết quả mong muốn là làm như sau:

  1. Di chuyển dữ liệu vào các cột mới hoặc thậm chí một bảng mới:
    1. Cách tiếp cận cột mới:
      1. Thêm các cột mới {name}_newvào bảng với DECIMAL(18, 3)kiểu dữ liệu
      2. Thực hiện di chuyển dữ liệu một lần từ các VARCHARcột cũ sang các DECIMALcột
      3. đổi tên các cột cũ thành {name}_old
      4. đổi tên các cột mới thành {name}
    2. Cách tiếp cận bảng mới:
      1. Tạo bảng mới khi {table_name}_newsử dụngDECIMAL(18, 3) kiểu dữ liệu
      2. Thực hiện di chuyển dữ liệu một lần từ bảng hiện tại sang mới DECIMAL bảng dựa trên cơ sở .
      3. đổi tên bảng cũ thành _old
      4. xóa _newkhỏi bảng mới
  2. Cập nhật ứng dụng, vv để không bao giờ chèn dữ liệu được mã hóa theo cách này
  3. sau một chu kỳ phát hành, nếu không có vấn đề gì, hãy bỏ các cột hoặc bảng cũ
  4. bỏ TVF và UDF
  5. Không bao giờ nói về điều này một lần nữa!

RATNG SAU: Bạn có thể loại bỏ rất nhiều mã đó vì nó phần lớn là không cần thiết. Ngoài ra, có ít nhất hai lỗi khiến đầu ra đôi khi không chính xác hoặc đôi khi gây ra lỗi. Và những lỗi đó đã được sao chép vào mã của Joe vì nó tạo ra kết quả tương tự (bao gồm cả lỗi) như mã của OP. Ví dụ:

  • Các giá trị này tạo ra một kết quả chính xác:

    00062929x
    00021577E
    00000509H
  • Các giá trị này tạo ra kết quả không chính xác:

    00002020Q
    00016723L
    00009431O
    00017221R
  • Giá trị này tạo ra lỗi:

    00062145}
    anything ending with "}"

So sánh tất cả 3 phiên bản với 450.740 hàng sử dụng SET STATISTICS TIME ON;, tất cả chúng đều chạy chỉ trong hơn 5000 ms thời gian đã trôi qua. Nhưng đối với thời gian CPU, kết quả là:

  • TVF của OP: 7031 ms
  • TVF của Joe: 3734 ms
  • TVF của Solomon: 1407 ms

CÀI ĐẶT: DỮ LIỆU

Sau đây tạo ra một bảng và điền vào nó. Điều này sẽ tạo cùng một bộ dữ liệu trên tất cả các hệ thống chạy SQL Server 2017 vì chúng sẽ có cùng một hàng trongspt_values . Điều này giúp cung cấp cơ sở so sánh giữa những người khác đang thử nghiệm trên hệ thống của họ vì dữ liệu được tạo ngẫu nhiên sẽ có sự khác biệt về thời gian giữa các hệ thống hoặc thậm chí giữa các thử nghiệm trên cùng hệ thống nếu dữ liệu mẫu được tạo lại. Tôi đã bắt đầu với cùng một bảng 3 cột như Joe đã làm, nhưng đã sử dụng các giá trị mẫu từ câu hỏi làm mẫu để đưa ra một loạt các giá trị số được nối với mỗi tùy chọn ký tự có thể có (bao gồm cả không có ký tự dấu). Đây cũng là lý do tại sao tôi buộc Collation trên các cột: Tôi không muốn thực tế là tôi đang sử dụng Sơ đồ Collation nhị phân để phủ nhận không công bằng hiệu quả của việc sử dụngCOLLATE từ khóa để buộc một Collation khác nhau trong TVF).

Sự khác biệt duy nhất là trong thứ tự của các hàng trong bảng.

USE [tempdb];
SET NOCOUNT ON;

CREATE TABLE dbo.TestVals
(
  [TestValsID] INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
  [Col1] VARCHAR(50) COLLATE Latin1_General_100_CI_AS NOT NULL,
  [Col2] VARCHAR(50) COLLATE Latin1_General_100_CI_AS NOT NULL,
  [Col3] VARCHAR(50) COLLATE Latin1_General_100_CI_AS NOT NULL
);

;WITH cte AS
(
  SELECT (val.[number] + tmp.[blah]) AS [num]
  FROM [master].[dbo].[spt_values] val
  CROSS JOIN (VALUES (1), (7845), (0), (237), (61063), (999)) tmp(blah)
  WHERE val.[number] BETWEEN 0 AND 1000000
)
INSERT INTO dbo.TestVals ([Col1], [Col2], [Col3])
  SELECT FORMATMESSAGE('%08d%s', cte.[num], tab.[col]) AS [Col1],
       FORMATMESSAGE('%08d%s', ((cte.[num] + 2) * 2), tab.[col]) AS [Col2],
       FORMATMESSAGE('%08d%s', ((cte.[num] + 1) * 3), tab.[col]) AS [Col3]
  FROM    cte
  CROSS JOIN (VALUES (''), ('{'), ('A'), ('B'), ('C'), ('D'), ('E'), ('F'),
              ('G'), ('H'), ('I'), ('}'), ('J'), ('K'), ('L'), ('M'), ('N'),
              ('O'), ('P'), ('Q'), ('R'), ('p'), ('q'), ('r'), ('s'), ('t'),
              ('u'), ('v'), ('w'), ('x'), ('y')) tab(col)
  ORDER BY NEWID();
-- 463698 rows

CÀI ĐẶT: TVF

GO
CREATE OR ALTER FUNCTION dbo.ConvertAmountVerified_Solomon
(
    @amt VARCHAR(50)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN

    WITH ctePosition AS
    (
        SELECT CHARINDEX(RIGHT(RTRIM(@amt), 1) COLLATE Latin1_General_100_BIN2,
                             '{ABCDEFGHI}JKLMNOPQRpqrstuvwxy') AS [Value]
    ),
    cteAppend AS
    (
        SELECT pos.[Value] AS [Position],
               IIF(pos.[Value] > 0,
                      CHAR(48 + ((pos.[Value] - 1) % 10)),
                      '') AS [Value]
        FROM   ctePosition pos
    )
    SELECT (CONVERT(DECIMAL(18, 3),
                    IIF(app.[Position] > 0,
                           SUBSTRING(RTRIM(@amt), 1, LEN(@amt) - 1) + app.[Value],
                           @amt))
                        / 100. )
                    * IIF(app.[Position] > 10, -1., 1.) AS [AmountVerified]
    FROM   cteAppend app;
GO

Xin lưu ý:

  1. Tôi đã sử dụng _BIN2Collation nhị phân (tức là ) nhanh hơn Collation phân biệt chữ hoa chữ thường vì nó không cần tính đến bất kỳ quy tắc ngôn ngữ nào.
  2. Điều duy nhất thực sự quan trọng là vị trí (tức là "chỉ mục") của ký tự bên phải nhất trong danh sách các ký tự alpha cộng với hai dấu ngoặc nhọn. Tất cả mọi thứ được thực hiện hoạt động đều bắt nguồn từ vị trí đó nhiều hơn giá trị của chính nhân vật.
  3. Tôi đã sử dụng các tham số đầu vào và giá trị trả về kiểu dữ liệu như đã nêu trong UDF gốc đã được viết lại bởi OP Trừ khi có lý do chính đáng để đi từ VARCHAR(50)đến VARCHAR(60), và từ NUMERIC (18,3)đến NUMERIC (18,2)(lý do chính đáng sẽ là "họ đã sai lầm"), sau đó tôi sẽ dính với chữ ký / loại gốc.
  4. Tôi đã thêm một điểm thời gian / số thập phân đến hết 3 số literals / hằng số: 100., -1., và 1.. Đây không phải là phiên bản gốc của TVF này (trong lịch sử của câu trả lời này) nhưng tôi nhận thấy một số CONVERT_IMPLICITcuộc gọi trong kế hoạch thực hiện XML (vì đây 100là một INThoạt động cần phải NUMERIC/ DECIMAL) vì vậy tôi chỉ quan tâm đến nó trước thời hạn .
  5. Tôi tạo một ký tự chuỗi bằng cách sử dụng CHAR()hàm thay vì chuyển một phiên bản chuỗi của một số (ví dụ '2') vào một CONVERThàm (đó là những gì tôi đã làm ban đầu, một lần nữa trong lịch sử). Điều này dường như là nhanh hơn một chút. Chỉ một vài phần nghìn giây, nhưng vẫn còn.

KIỂM TRA

Xin lưu ý rằng tôi phải lọc ra các hàng kết thúc }vì điều đó khiến TVF của OP và Joe bị lỗi. Mặc dù mã của tôi xử lý }chính xác, tôi muốn thống nhất với các hàng đang được thử nghiệm trên 3 phiên bản. Đây là lý do tại sao số lượng hàng được tạo bởi truy vấn thiết lập cao hơn một chút so với số lượng tôi đã lưu ý ở trên kết quả kiểm tra cho số lượng hàng đã được kiểm tra.

SET STATISTICS TIME ON;

DECLARE @Dummy DECIMAL(18, 3);
SELECT --@Dummy =  -- commented out = results to client; uncomment to not return results
cnvrtS.[AmountVerified]
FROM  dbo.TestVals vals
CROSS APPLY dbo.ConvertAmountVerified_Solomon(vals.[Col1]) cnvrtS
WHERE RIGHT(vals.[Col1], 1) <> '}'; -- filter out rows that cause error in O.P.'s code

SET STATISTICS TIME OFF;
GO

Thời gian CPU chỉ thấp hơn một chút khi không chú ý đến --@Dummy =và thứ hạng trong số 3 TVF là như nhau. Nhưng thật thú vị, khi bỏ qua biến, bảng xếp hạng thay đổi một chút:

  • TVF của Joe: 3295 ms
  • TVF của OP: 2240 ms
  • TVF của Solomon: 1203 ms

Không chắc chắn tại sao mã của OP sẽ hoạt động tốt hơn nhiều trong kịch bản này (trong khi mã của tôi và Joe chỉ được cải thiện một chút), nhưng nó có vẻ nhất quán trong nhiều thử nghiệm. Và không, tôi đã không nhìn vào sự khác biệt của kế hoạch thực hiện vì tôi không có thời gian để điều tra điều đó.

NGAY LẬP TỨC

Tôi đã hoàn thành thử nghiệm phương pháp thay thế và nó cung cấp một cải tiến nhỏ nhưng chắc chắn cho những gì được hiển thị ở trên. Cách tiếp cận mới sử dụng SQLCLR và nó xuất hiện để mở rộng quy mô tốt hơn. Tôi thấy rằng khi thêm vào cột thứ hai vào truy vấn, cách tiếp cận T-SQL nhân đôi thời gian. Nhưng, khi thêm vào các cột bổ sung bằng cách sử dụng UDF vô hướng SQLCLR, thời gian đã tăng lên, nhưng không bằng với thời gian của cột đơn. Có thể có một số chi phí ban đầu khi gọi phương thức SQLCLR (không liên quan đến chi phí tải ban đầu của Miền ứng dụng và của Hội đồng vào Miền ứng dụng) vì thời gian đã trôi qua (không phải là thời gian của CPU):

  • 1 cột: 1018 ms
  • 2 cột: 1750 - 1800 ms
  • 3 cột: 2500 - 2600 ms

Vì vậy, có thể thời gian (đổ vào một biến, không trả về tập kết quả) có tổng phí 200 ms - 250 ms và sau đó là 750 ms - 800 ms mỗi lần. Thời gian của CPU lần lượt là: 950 ms, 1750 ms và 2400 ms cho 1, 2 và 3 trường hợp của UDF.

MÃ C #

using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public class Transformations
{
    private const string _CHARLIST_ = "{ABCDEFGHI}JKLMNOPQRpqrstuvwxy";

    [SqlFunction(IsDeterministic = true, IsPrecise = true,
        DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None)]
    public static SqlDouble ConvertAmountVerified_SQLCLR(
        [SqlFacet(MaxSize = 50)] SqlString Amt)
    {
        string _Amount = Amt.Value.TrimEnd();

        int _LastCharIndex = (_Amount.Length - 1);
        int _Position = _CHARLIST_.IndexOf(_Amount[_LastCharIndex]);

        if (_Position >= 0)
        {
            char[] _TempAmount = _Amount.ToCharArray();
            _TempAmount[_LastCharIndex] = char.ConvertFromUtf32(48 + (_Position % 10))[0];
            _Amount = new string(_TempAmount);
        }

        decimal _Return = decimal.Parse(_Amount) / 100M;

        if (_Position > 9)
        {
            _Return *= -1M;
        }

        return new SqlDouble((double)_Return);
    }
}

Ban đầu tôi sử dụng SqlDecimallàm kiểu trả về, nhưng có một hình phạt về hiệu suất khi sử dụng nó trái ngược với SqlDouble/ FLOAT. Đôi khi FLOAT có vấn đề (do đây là loại không chính xác), nhưng tôi đã xác minh đối với TVF T-SQL thông qua truy vấn sau và không phát hiện thấy sự khác biệt nào:

SELECT cnvrtS.[AmountVerified],
       dbo.ConvertAmountVerified_SQLCLR(vals.[Col1])
FROM   dbo.TestVals vals
CROSS APPLY dbo.ConvertAmountVerified_Solomon(vals.[Col1]) cnvrtS
WHERE  cnvrtS.[AmountVerified] <> dbo.ConvertAmountVerified_SQLCLR(vals.[Col1]);

KIỂM TRA

SET STATISTICS TIME ON;

DECLARE @Dummy DECIMAL(18, 3), @Dummy2 DECIMAL(18, 3), @Dummy3 DECIMAL(18, 3);
SELECT @Dummy = 
       dbo.ConvertAmountVerified_SQLCLR(vals.[Col1])
              , @Dummy2 =
       dbo.ConvertAmountVerified_SQLCLR(vals.[Col2])
              , @Dummy3 =
       dbo.ConvertAmountVerified_SQLCLR(vals.[Col3])
FROM  dbo.TestVals vals
WHERE RIGHT(vals.[Col1], 1) <> '}';

SET STATISTICS TIME OFF;

Cám ơn vì cái này. Tôi sẽ kiểm tra chức năng của bạn dựa trên dữ liệu của tôi. Nhìn về phía trước để thấy những thay đổi của bạn để làm cho nó nhanh hơn và kiểm tra dữ liệu.
Mazhar

1
@Mazhar Cảm ơn bạn đã chấp nhận :-). Tuy nhiên, tôi đã hoàn thành thử nghiệm của mình theo phương pháp thay thế và thấy rằng nó nhanh hơn một chút so với những gì tôi đã có ở đây. Nó sử dụng SQLCLR nhưng có quy mô tốt hơn. Nó cũng trở lại là một UDF vô hướng để làm việc dễ dàng hơn một chút (nghĩa là không cần CROSS APPLYs).
Solomon Rutzky

" Có thể có một số chi phí ban đầu khi gọi phương thức SQLCLR (không liên quan đến chi phí tải ban đầu của Miền ứng dụng và của hội vào Miền ứng dụng) " - Tôi sẽ đề xuất rằng chi phí có thể là quá trình biên dịch JIT, vì nó chỉ gặp ở lần chạy đầu tiên. Nhưng tôi đã lược tả mã của bạn trong một ứng dụng bảng điều khiển C # và nó chỉ phát sinh 10 giây trong quá trình biên dịch JIT. Phương thức tĩnh đặc biệt chỉ mất .3 ms là JIT'd. Nhưng tôi không biết gì về SQLCLR, vì vậy có lẽ có nhiều mã liên quan hơn tôi biết.
Josh Darnell

1
@ jadarnel27 Cảm ơn bạn đã giúp điều tra. Tôi nghĩ rằng nó có thể là một kiểm tra cho phép của một cái gì đó. Một cái gì đó liên quan đến việc tạo / xác nhận kế hoạch truy vấn.
Solomon Rutzky

4

Tôi sẽ bắt đầu bằng cách ném một số dữ liệu thử nghiệm vào một bảng. Tôi không biết dữ liệu thực của bạn trông như thế nào nên tôi chỉ sử dụng các số nguyên tuần tự:

CREATE TABLE APPLY_FUNCTION_TO_ME (
    COL1 VARCHAR(60),
    COL2 VARCHAR(60),
    COL3 VARCHAR(60)
);

INSERT INTO APPLY_FUNCTION_TO_ME WITH (TABLOCK)
SELECT RN, RN, RN
FROM (
    SELECT CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS VARCHAR(60)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t;

Việc chọn tất cả các hàng có bộ kết quả bị tắt sẽ cung cấp một dòng cơ sở:

-- CPU time = 1359 ms,  elapsed time = 1434 ms.
SELECT COL1 FROM dbo.APPLY_FUNCTION_TO_ME

Nếu một truy vấn tương tự với lệnh gọi hàm mất nhiều thời gian hơn thì chúng ta có ước tính sơ bộ về chi phí hoạt động của hàm. Đây là những gì tôi nhận được khi gọi TVF của bạn là:

-- CPU time = 41703 ms,  elapsed time = 41899 ms.
SELECT t1.AmountVerified
FROM dbo.APPLY_FUNCTION_TO_ME
CROSS APPLY dbo.ConvertAmountVerified_TVF (COL1) t1
OPTION (MAXDOP 1);

Vì vậy, chức năng cần khoảng 40 giây thời gian CPU cho 6,5 triệu hàng. Nhân số đó với 20 và thời gian CPU là 800 giây. Tôi nhận thấy hai điều trong mã chức năng của bạn:

  1. Sử dụng không cần thiết của OUTER APPLY. CROSS APPLYsẽ cho bạn kết quả tương tự và đối với truy vấn này, nó sẽ tránh được một loạt các phép nối không cần thiết. Điều đó có thể tiết kiệm một chút thời gian. Nó chủ yếu phụ thuộc vào nếu truy vấn đầy đủ đi song song. Tôi không biết gì về dữ liệu hoặc truy vấn của bạn vì vậy tôi chỉ đang thử nghiệm MAXDOP 1. Trong trường hợp đó tôi tốt hơn với CROSS APPLY.

  2. Có rất nhiều CHARINDEXcuộc gọi khi bạn chỉ tìm kiếm một ký tự đối với một danh sách nhỏ các giá trị khớp. Bạn có thể sử dụng ASCII()hàm và một phép toán nhỏ để tránh tất cả các phép so sánh chuỗi.

Đây là một cách khác để viết hàm:

CREATE OR ALTER FUNCTION dbo.ConvertAmountVerified_TVF3
(
    @amt VARCHAR(60)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
    WITH cteLastChar
    AS(
        SELECT LastCharASCIICode =  ASCII(RIGHT(RTRIM(@amt), 1) COLLATE Latin1_General_CS_AS)
    )
    SELECT
        AmountVerified  = CAST(RET.Y AS NUMERIC(18,2))
    FROM cteLastChar
    CROSS APPLY (
        SELECT N =
                CAST(
                    CASE 
                        --WHEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, '{ABCDEFGHI}', 0) >0
                        --    THEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, '{ABCDEFGHI}', 0)-1
                        WHEN LastCharASCIICode = 123 THEN 0
                        WHEN LastCharASCIICode BETWEEN 65 AND 73 THEN LastCharASCIICode - 64
                        WHEN LastCharASCIICode = 125 THEN 10

                        --WHEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, 'JKLMNOPQR', 0) >0
                        --    THEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, 'JKLMNOPQR', 0)-1
                        WHEN LastCharASCIICode BETWEEN 74 AND 82 THEN LastCharASCIICode - 74

                        --WHEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, 'pqrstuvwxy', 0) >0
                        --    THEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, 'pqrstuvwxy', 0)-1
                        WHEN LastCharASCIICode BETWEEN 112 AND 121 THEN LastCharASCIICode - 112
                        ELSE 
                            NULL
                    END
                AS VARCHAR(1))
        --FROM
        --    cteLastChar L
    ) NUM
    CROSS APPLY (
        SELECT N =
            CASE 
                --WHEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, '{ABCDEFGHI}', 0) >0
                WHEN LastCharASCIICode = 123 OR LastCharASCIICode = 125 OR LastCharASCIICode BETWEEN 65 AND 73
                    THEN 0

                --WHEN CHARINDEX(L.LastChar  COLLATE Latin1_General_CS_AS, 'JKLMNOPQRpqrstuvwxy', 0) >0
                WHEN LastCharASCIICode BETWEEN 74 AND 82 OR LastCharASCIICode BETWEEN 112 AND 121
                    THEN 1
                ELSE 0
            END
        --FROM cteLastChar L
    ) NEG
    CROSS APPLY(
        SELECT Amt= CASE
                        WHEN NUM.N IS NULL
                            THEN @amt 
                        ELSE
                            SUBSTRING(RTRIM(@amt),1, LEN(@amt) - 1) + Num.N
                    END
    ) TP
    CROSS APPLY(
        SELECT Y =  CASE
                        WHEN NEG.N = 0
                            THEN (CAST(TP.Amt AS NUMERIC) / 100)
                        WHEN NEG.N = 1
                            THEN (CAST (TP.Amt AS NUMERIC) /100) * -1
                    END
    ) RET
) ;

GO

Trên máy của tôi, chức năng mới nhanh hơn đáng kể:

-- CPU time = 7813 ms,  elapsed time = 7876 ms.
SELECT t1.AmountVerified
FROM dbo.APPLY_FUNCTION_TO_ME
CROSS APPLY dbo.ConvertAmountVerified_TVF3 (COL1) t1
OPTION (MAXDOP 1);

Có thể có một số tối ưu hóa bổ sung có sẵn là tốt, nhưng ruột của tôi nói rằng chúng sẽ không nhiều. Dựa trên những gì mã của bạn đang làm, tôi không thể thấy bạn sẽ thấy sự cải thiện hơn nữa bằng cách nào đó gọi hàm của bạn theo một cách khác. Nó chỉ là một loạt các hoạt động chuỗi. Gọi hàm 20 lần mỗi hàng sẽ chậm hơn chỉ một lần, nhưng định nghĩa đã được nội tuyến.


Cám ơn vì cái này. Bạn có nói rằng "định nghĩa đã được nội tuyến" rằng việc thực thi TVF trên nhiều cột sẽ hoạt động giống như một Hàm nội tuyến?
Mazhar

Tôi sẽ kiểm tra chức năng của bạn dựa trên dữ liệu của tôi.
Mazhar

2

Hãy thử sử dụng như sau

-- Get Last Character
SELECT @LastChar = RIGHT(RTRIM(@amt), 1) collate latin1_general_cs_as;

DECLARE @CharPos int=NULLIF(CHARINDEX(@LastChar,'{ABCDEFGHI}JKLMNOPQRpqrstuvwxy'),0)-1
SET @Num = ISNULL(@CharPos%10,''); 
SET @Negative = IIF(@CharPos>9,1,0);

thay thế

SELECT @Num =
    CASE @LastChar  collate latin1_general_cs_as
        WHEN '{'  THEN '0'
...

SELECT @Negative =
    CASE @LastChar collate latin1_general_cs_as
        WHEN '{' THEN 0
...

Một biến thể với việc sử dụng bảng phụ

-- auxiliary table
CREATE TABLE LastCharLink(
  LastChar varchar(1) collate latin1_general_cs_as NOT NULL,
  Num varchar(1) NOT NULL,
  Prefix varchar(1) NOT NULL,
CONSTRAINT PK_LastCharLink PRIMARY KEY(LastChar)
)

INSERT LastCharLink(LastChar,Num,Prefix)VALUES
('{','0',''),
('A','1',''),
('B','2',''),
('C','3',''),
('D','4',''),
('E','5',''),
('F','6',''), 
('G','7',''), 
('H','8',''), 
('I','9',''), 
('}','0','-'), 
('J','1','-'),
('K','2','-'),
('L','3','-'),
('M','4','-'),
('N','5','-'),
('O','6','-'),
('P','7','-'),
('Q','8','-'),
('R','9','-'),                
('p','0','-'),
('q','1','-'),
('r','2','-'),
('s','3','-'),
('t','4','-'),
('u','5','-'),
('v','6','-'),
('w','7','-'),
('x','8','-'),
('y','9','-')

Một truy vấn kiểm tra

CREATE TABLE #TestAmounts(Amt varchar(10))
INSERT #TestAmounts(Amt)VALUES('00064170'),('00057600A'),('00066294R'),('00059224}'),('00012345p')

SELECT
  *,
  CAST( -- step 5 - final cast
      CAST( -- step 3 - convert to number
          CONCAT( -- step 2 - add a sign and an additional number
              l.Prefix,
              LEFT(RTRIM(a.Amt),LEN(RTRIM(a.Amt))-IIF(l.LastChar IS NULL,0,1)), -- step 1 - remove last char
              l.Num
            )
          AS numeric(18,3)
        )/100 -- step 4 - divide
      AS numeric(18,3)
    ) ResultAmt
FROM #TestAmounts a
LEFT JOIN LastCharLink l ON RIGHT(RTRIM(a.Amt),1) collate latin1_general_cs_as=l.LastChar

DROP TABLE #TestAmounts

Là biến thể, bạn cũng có thể thử sử dụng bảng phụ tạm thời #LastCharLinkhoặc bảng biến @LastCharLink(nhưng có thể chậm hơn bảng thực hoặc tạm thời)

DECLARE @LastCharLink TABLE(
  LastChar varchar(1) collate latin1_general_cs_as NOT NULL,
  Num varchar(1) NOT NULL,
  Prefix varchar(1) NOT NULL,
PRIMARY KEY(LastChar)
)

INSERT LastCharLink(LastChar,Num,Prefix)VALUES
('{','0',''),
('A','1',''),
('B','2',''),
('C','3',''),
('D','4',''),
('E','5',''),
...

Và sử dụng nó như là

FROM #TestAmounts a
LEFT JOIN #LastCharLink l ON ...

hoặc là

FROM #TestAmounts a
LEFT JOIN @LastCharLink l ON ...

Sau đó, bạn cũng có thể tạo một hàm nội tuyến đơn giản và đưa vào đó tất cả các chuyển đổi

CREATE FUNCTION NewConvertAmountVerified(
  @Amt varchar(50),
  @LastChar varchar(1),
  @Num varchar(1),
  @Prefix varchar(1)
)
RETURNS numeric(18,3)
AS
BEGIN
  RETURN CAST( -- step 3 - convert to number
              CONCAT( -- step 2 - add a sign and an additional number
                  @Prefix,
                  LEFT(@Amt,LEN(@Amt)-IIF(@LastChar IS NULL,0,1)), -- step 1 - remove last char
                  @Num
                )
              AS numeric(18,3)
            )/100 -- step 4 - divide
END
GO

Và sau đó sử dụng chức năng này như

CREATE TABLE #TestAmounts(Amt varchar(10))
INSERT #TestAmounts(Amt)VALUES('00064170'),('00057600A'),('00066294R'),('00059224}'),('00012345p')

SELECT
  *,
  -- you need to use `RTRIM` here
  dbo.NewConvertAmountVerified(RTRIM(a.Amt),l.LastChar,l.Num,l.Prefix) ResultAmt
FROM #TestAmounts a
LEFT JOIN LastCharLink l ON RIGHT(RTRIM(a.Amt),1) collate latin1_general_cs_as=l.LastChar

DROP TABLE #TestAmounts

Tôi đã cập nhật câu trả lời của mình. Cố gắng sử dụng một bảng phụ trợ để làm những gì bạn muốn. Tôi nghĩ biến thể này sẽ nhanh hơn.

Tôi đã cập nhật câu trả lời của mình một lần nữa. Bây giờ nó sử dụng Prefixthay vì Divider.

2

Ngoài ra, bạn có thể tạo một bảng cố định. Đây là một lần tạo.

CREATE TABLE CharVal (
    charactor CHAR(1) collate latin1_general_cs_as NOT NULL
    ,positiveval INT NOT NULL
    ,negativeval INT NOT NULL
    ,PRIMARY KEY (charactor)
    )

insert into CharVal (charactor,positiveval,negativeval) VALUES

 ( '{' ,'0', 0 ),( 'A' ,'1', 0 ) ,( 'B' ,'2', 0 ) ,( 'C' ,'3', 0 ) ,( 'D' ,'4', 0 )       
                         ,( 'E' ,'5', 0 )  ,( 'F' ,'6', 0 ) ,( 'G' ,'7', 0 ) ,( 'H' ,'8', 0 )       
,( 'I' ,'9', 0 ),( '}' ,'0', 1 ),( 'J' ,'1', 1  ),( 'K' ,'2', 1 ) ,( 'L' ,'3', 1 ) ,( 'M' ,'4', 1 )       
,( 'N' ,'5', 1 )  ,( 'O' ,'6', 1 )  ,( 'P' ,'7', 1 )  ,( 'Q' ,'8', 1 )  ,( 'R' ,'9', 1  )
---ASCII
,( 'p' , '0', '1'),( 'q' , '1', '1'),( 'r' , '2', '1'),( 's' , '3', '1')
,( 't' , '4', '1'),( 'u' , '5', '1'),( 'v' , '6', '1'),( 'w' , '7', '1')
,( 'x' , '8', '1'),( 'y' , '9', '1')

--neg
('{' ,2, 0) ,('A' ,2, 0) ,('B' ,2, 0)  ,('C' ,2, 0) ,('D' ,2, 0)                    
,('E' ,2, 0),('F' ,2, 0)  ,('G' ,2, 0) ,('H' ,2, 0) ,('I' ,2, 0) ,('}' ,2, 1)
,('J' ,2, 1) ,('K' ,2, 1) ,('L' ,2, 1) ,('M' ,2, 1) ,('N' ,2, 1)                    
,('O' ,2, 1)  ,('P' ,2, 1)  ,('Q' ,2, 1) ,('R' ,2, 1)
  ---ASCII
,( 'p' ,2, '1'),( 'q' ,2, '1')
,( 'r' ,2, '1'),( 's' ,2, '1')
,( 't' ,2, '1'),( 'u' ,2, '1')
,( 'v' ,2, '1'),( 'w' ,2, '1')
,( 'x' ,2, '1'),( 'y' ,2, '1')

Rồi TVF

ALTER FUNCTION dbo.ConvertAmountVerified_TVFHarsh (@amt VARCHAR(60))
RETURNS TABLE
    WITH SCHEMABINDING
AS
RETURN (
        WITH MainCTE AS (
                SELECT TOP 1 
                Amt = CASE 
                        WHEN positiveval IS NULL
                            THEN @amt
                        ELSE SUBSTRING(RTRIM(@amt), 1, LEN(@amt) - 1) + positiveval
                        END
                    ,negativeval
                FROM (
                    SELECT positiveval
                        ,negativeval negativeval
                        ,1 sortorder
                    FROM dbo.CharVal WITH (NOLOCK)
                    WHERE (charactor = RIGHT(RTRIM(@amt), 1))

                    UNION ALL

                    SELECT NULL
                        ,0
                        ,0
                    ) t4
                ORDER BY sortorder DESC
                )

        SELECT AmountVerified = CASE 
                WHEN negativeval = 0
                    THEN (CAST(TP.Amt AS NUMERIC) / 100)
                WHEN negativeval = 1
                    THEN (CAST(TP.Amt AS NUMERIC) / 100) * - 1
                END
        FROM MainCTE TP
        );
GO

Từ ví dụ @Joe,

- Mất 30 giây

SELECT t1.AmountVerified
FROM dbo.APPLY_FUNCTION_TO_ME
CROSS APPLY dbo.ConvertAmountVerified_TVFHarsh (COL1) t1
OPTION (MAXDOP 1);

Nếu có thể, Số tiền cũng có thể được định dạng ở cấp UI. Đây là lựa chọn tốt nhất. Nếu không, bạn có thể chia sẻ truy vấn ban đầu của bạn cũng. HOẶC nếu có thể giữ giá trị định dạng trong bảng cũng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.