同じテーブル値関数(TVF)が20列で呼び出されるクエリを調整しようとしています。
最初に行ったのは、スカラー関数をインラインテーブル値関数に変換することでした。
使っている CROSS APPLY
クエリの複数の列で同じ関数を実行するための最良の方法は?
単純な例:
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
より良い代替案はありますか?
X個の列に対して複数のクエリで同じ関数を呼び出すことができます。
これが関数です:
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
誰か興味があれば、私が継承したスカラー関数のバージョンを以下に示します。
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
サンプルテストデータ:
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
最初に、望ましい結果を得るための絶対最速の方法は次のことを行うことであることに言及する必要があります:
{name}_new
として、DECIMAL(18, 3)
データ型のテーブルに追加します。VARCHAR
列からDECIMAL
列へのデータの移行を1回実行します{name}_old
に変更します{name}
だけに変更しますDECIMAL(18, 3)
データ型を使用して、{table_name}_new
として新しいテーブルを作成しますDECIMAL
ベースのテーブルへのデータの移行を1回実行します。_old
に変更します_new
を削除それが言われていること:それは主に不必要な重複であるため、そのコードの多くを取り除くことができます。また、少なくとも2つのバグがあり、出力が正しくない場合や、エラーがスローされる場合があります。 O.P.のコードと同じ結果(エラーを含む)が生成されるため、これらのバグはJoeのコードにコピーされました。例えば:
これらの値は正しい結果を生成します:
00062929x
00021577E
00000509H
これらの値は誤った結果を生成します。
00002020Q
00016723L
00009431O
00017221R
この値はエラーを生成します:
00062145}
anything ending with "}"
SET STATISTICS TIME ON;
を使用して、3つのバージョンすべてを448,740行と比較すると、すべて5000ミリ秒を超える経過時間で実行されました。しかし、CPU時間については、結果は次のとおりです。
設定:データ
次の例では、テーブルを作成してデータを入力します。これにより、SQL Server 2017を実行しているすべてのシステムで同じデータセットが作成されます。これは、spt_values
に同じ行が含まれるためです。これは、ランダムに生成されたデータがシステム全体、またはサンプルデータが再生成される場合は同じシステムでのテスト間のタイミングの違いを考慮に入れるため、システムでテストする他の人々の比較の基礎を提供するのに役立ちます。私はJoeと同じ3列のテーブルから始めましたが、質問のサンプル値をテンプレートとして使用して、さまざまな数値に可能な末尾文字オプション(末尾文字なしを含む)を追加しました。これが列に照合順序を強制した理由でもあります。バイナリ照合順序インスタンスを使用して、COLLATE
キーワードを使用して別の照合順序を強制する効果を不当に無効にしたくないので、 TVF)。
唯一の違いは、テーブル内の行の順序です。
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
セットアップ: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
ご注意ください:
_BIN2
)照合を使用しました。VARCHAR(50)
からVARCHAR(60)
へ、およびNUMERIC (18,3)
からNUMERIC (18,2)
(適切な理由は「それらが間違っていた」ということです)の場合、元のシグネチャ/タイプを使用します。100.
、-1.
、および1.
。これはこのTVFの元のバージョン(この回答の履歴では)にはありませんでしたが、XML実行計画でCONVERT_IMPLICIT
の呼び出しがいくつかあることに気付きました(100
はINT
なので、操作はNUMERIC
/DECIMAL
)ですので、事前に対処しました。'2'
)をCONVERT
関数に渡すのではなく、CHAR()
関数を使用して文字列文字を作成します(これは、元々私がやっていたことでしたが、歴史の中で)。これはこれまでよりもわずかに速いようです。ほんの数ミリ秒ですが、まだです。[〜#〜]テスト[〜#〜]
O.P.およびJoeのTVFでエラーが発生したため、}
で終わる行を除外する必要があったことに注意してください。私のコードは}
を正しく処理していますが、3つのバージョン間でテストされている行と一貫性を保つ必要がありました。これが、セットアップクエリによって生成された行の数が、テストされた行数のテスト結果の上に示した数よりもわずかに多い理由です。
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
--@Dummy =
のコメントを外すと、CPU時間はわずかに短くなり、3つのTVF間のランキングは同じになります。しかし興味深いことに、変数のコメントを外すと、ランキングが少し変化します。
O.P.のコードがこのシナリオでそれほど優れたパフォーマンスを発揮する理由はわかりませんが(私のコードとJoeのコードはわずかに改善されただけです)、多くのテストで一貫しているように見えます。いいえ、実行プランの違いを調査する時間がないため、実行プランの違いは確認しませんでした。
EVEN FASTERER
私は代替アプローチのテストを完了しました、そしてそれは上に示されているものにわずかですが明確な改善を提供します。新しいアプローチはSQLCLRを使用しており、より適切に拡張できるようです。 2番目の列をクエリに追加すると、T-SQLのアプローチが時間的に2倍になることがわかりました。ただし、SQLCLRスカラーUDFを使用して列を追加すると、時間が増加しましたが、単一列のタイミングと同じではありませんでした。タイミングが(CPU時間ではなく経過時間)だったため、SQLCLRメソッドの呼び出しに初期オーバーヘッド(アプリドメインおよびアプリドメインへのアセンブリの初期読み込みのオーバーヘッドに関連付けられていない)がある可能性があります。
したがって、(結果セットを返すのではなく、変数にダンプする)タイミングに200ミリ秒-250ミリ秒のオーバーヘッドがあり、インスタンス時間あたり750ミリ秒-800ミリ秒になる可能性があります。 CPUタイミングは、UDFの1、2、および3つのインスタンスで、それぞれ950ミリ秒、1750ミリ秒、2400ミリ秒でした。
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);
}
}
元々は戻り値の型としてSqlDecimal
を使用しましたが、SqlDouble
/FLOAT
とは対照的に、それを使用するとパフォーマンスが低下します。時々FLOATには問題があります(不正確なタイプのため)が、次のクエリを介してT-SQL TVFに対して検証しましたが、違いは検出されませんでした。
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]);
[〜#〜]テスト[〜#〜]
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;
最初に、いくつかのテストデータをテーブルにスローします。私はあなたの実際のデータがどのように見えるのか分かりませんので、私は連続した整数を使用しました:
_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;
_
結果セットをオフにしてすべての行を選択すると、ベースラインが提供されます。
_-- CPU time = 1359 ms, elapsed time = 1434 ms.
SELECT COL1 FROM dbo.APPLY_FUNCTION_TO_ME
_
関数呼び出しを使用した同様のクエリにさらに時間がかかる場合は、関数のオーバーヘッドに関する大まかな見積もりがあります。 TVFをそのまま呼び出すと次のようになります。
_-- 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);
_
したがって、この関数は、650万行に対して約40秒のCPU時間を必要とします。これに20を掛けると、CPU時間は800秒になります。私はあなたの関数コードに2つのことに気づきました:
_OUTER APPLY
_の不要な使用。 _CROSS APPLY
_でも同じ結果が得られます。このクエリでは、不必要な結合が大量に発生することが回避されます。それは少しの時間を節約できます。これは主に、完全なクエリが並列処理されるかどうかに依存します。データやクエリについて何も知りませんので、_MAXDOP 1
_でテストしています。その場合は、_CROSS APPLY
_を使用したほうがよいでしょう。
一致する値の小さなリストに対して1文字を検索するだけの場合、多くのCHARINDEX
呼び出しがあります。 ASCII()
関数と簡単な数学を使用して、すべての文字列比較を回避できます。
関数を作成する別の方法を次に示します。
_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
_
私のマシンでは、新しい機能が大幅に高速化されています。
_-- 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);
_
おそらくいくつかの追加の最適化も同様に利用可能ですが、私の直感ではそれらはそれほど多くはならないでしょう。あなたのコードが何をしているかに基づいて、どういうわけか別の方法で関数を呼び出すことによって、あなたがさらなる改善をどのように見るかはわかりません。これは単なる文字列操作の集まりです。行ごとに関数を20回呼び出すと、1回よりも遅くなりますが、定義は既にインライン化されています。
または、永続テーブルを1つ作成することもできます。これは1回限りの作成です。
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')
その後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
@Joeの例から、
-30秒かかります
SELECT t1.AmountVerified
FROM dbo.APPLY_FUNCTION_TO_ME
CROSS APPLY dbo.ConvertAmountVerified_TVFHarsh (COL1) t1
OPTION (MAXDOP 1);
可能であれば、金額はUIレベルでもフォーマットできます。これが最良のオプションです。それ以外の場合は、元のクエリを共有することもできます。 OR可能であれば、フォーマットされた値もテーブルに保存します。
以下を使用してみてください
-- 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);
代わりに
SELECT @Num =
CASE @LastChar collate latin1_general_cs_as
WHEN '{' THEN '0'
...
SELECT @Negative =
CASE @LastChar collate latin1_general_cs_as
WHEN '{' THEN 0
...
補助テーブルを使用する1つのバリアント
-- 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','-')
テストクエリ
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
バリアントとして、一時補助テーブル#LastCharLink
または変数テーブル@LastCharLink
を使用することもできます(ただし、実際のテーブルまたは一時テーブルよりも遅くなる場合があります)
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',''),
...
そしてそれを
FROM #TestAmounts a
LEFT JOIN #LastCharLink l ON ...
または
FROM #TestAmounts a
LEFT JOIN @LastCharLink l ON ...
次に、単純なインライン関数を作成して、すべての変換を追加することもできます
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
そして、この関数を次のように使用します
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