我正在尝试从多语言表中对结果进行排序。我想让排序算法来自一个函数,但它至少会增加 8% 的性能。所以,我不太确定该怎么做。因此,对于排序,我使用了一篇关于如何对多语言表格进行排序的文章中描述的方法,如下所示:
select UnicodeData,Collation
from (
select
ML.UnicodeData,
ML.Collation,
RN =
CASE
when Collation = 'he-IL' then ROW_NUMBER() OVER (order by unicodedata Collate Hebrew_CI_AS )
when Collation = 'en-US' then ROW_NUMBER() OVER (order by unicodedata Collate SQL_Latin1_General_CP1_CI_AS )
when Collation = 'kn-IN' then ROW_NUMBER() OVER (order by unicodedata Collate Indic_General_100_CI_AS )
when Collation = 'hi-IN' then ROW_NUMBER() OVER (order by unicodedata Collate Indic_General_100_CI_AS )
when Collation = 'ar-EG' then ROW_NUMBER() OVER (order by unicodedata Collate Arabic_CI_AS )
when Collation = 'cs' then ROW_NUMBER() OVER (order by unicodedata Collate Czech_CI_AS )
END
from MultipleLanguages ML
) T
order by RN
除了我将collation
代码抽象到它自己的函数中,就像这样:
CREATE FUNCTION [utils].[OrderByLanguage]
( @LanguageID tinyint
, @IDName utils.ID_Name READONLY
) RETURNS TABLE AS RETURN
SELECT
t.ID
, CASE @LanguageID
WHEN 1 THEN ROW_NUMBER() OVER (ORDER BY t.[Name]) -- en
WHEN 3 THEN ROW_NUMBER() OVER (ORDER BY t.[Name]) -- en-ca
WHEN 6 THEN ROW_NUMBER() OVER (ORDER BY t.[Name]) -- 'en-nz'
WHEN 5 THEN ROW_NUMBER() OVER (ORDER BY t.[Name]) -- 'en-za'
WHEN 2 THEN ROW_NUMBER() OVER (ORDER BY t.[Name] COLLATE Modern_Spanish_CI_AI) -- es
WHEN 4 THEN ROW_NUMBER() OVER (ORDER BY t.[Name] COLLATE French_CI_AI) -- 'fr-ca'
END RowNumber
FROM @IDName t
但是当我调用这个函数时,我必须对表值函数进行这种笨拙的双重调用。
CREATE FUNCTION api.GetTable
( @LanguageCode VARCHAR(10)
) RETURNS NVARCHAR(MAX)
AS BEGIN
DECLARE
@Result NVARCHAR(MAX)
, @LangID tinyint
DECLARE @Sort utils.ID_Name
SET @LangID = api_utils.GetLanguageID(@LanguageCode)
INSERT INTO @Sort (ID, [Name])
SELECT
t.ID
, t.title
FROM api_utils.GetTable(@LangID) t
SET @Result = (
SELECT
CONVERT(VARCHAR(10), t.ID) id,
t.category,
t.[system],
t.title,
JSON_QUERY(utils.ToRawJsonArray((
SELECT x.[Description]
FROM api_utils.GetKeywords(t.ID, @LangID) x
ORDER BY x.[Description]
FOR JSON AUTO), 'Description')
) keywords
FROM api_utils.GetTable(@LangID) t
ORDER BY (SELECT s.RowNumber
FROM utils.OrderByLanguage(@LangID, @Sort) s
WHERE s.ID = t.ID)
FOR JSON AUTO, ROOT('titles')
)
RETURN @Result
END
所以,你可以看到我必须调用函数api_utils.GetTable
两次。据我所知,提取归类排序的唯一其他方法是放入实际的排序算法,然后有一个脚本来搜索所有代码库,并在我需要添加另一种语言时添加另一种归类语言。还有其他方法吗?其他人做了什么?最佳做法是什么?这方面的性能并不是绝对关键,但保持精简是很好的,所以不会花太长时间,因为它已经是一个密集的呼叫。
提前致谢!
更新
在评论中回答@srutzky 的问题:
1) api_utils.GetTable返回了多少数据?
表中返回了大约 150 条记录。
2) 为什么第一次将结果转储到@Sort 时调用api_utils.GetTable 两次?
该@Sort
表是内存优化的用户定义表 ( UDT
)。因为我将一个表传递给utils.OrderByLanguage
函数,所以它需要是一个UDT
. 这意味着我需要api_utils.GetTable
两次从内联函数中获取数据。我不确定它是否导致性能问题调用api_utils.GetTable
两次。也许SQL Server
足够聪明来缓存结果?再次测试INSERT
查询成本为 38%。因此,查询成本的很大一部分。
将类别和系统列添加到 @Sort 并在第一次调用中将它们拉回,然后在 FROM 子句中使用 @Sort 不是更快吗?
由于UDT
是通用的,可用于调用该函数的所有不同过程,utils.OrderByLanguage
因此很难对不同过程将使用的未知数量的列进行概括。
3)这必须是一个函数还是可以是一个存储过程?
你在说什么api_utils.GetTable
?我宁愿api_utils.GetTable
保留一个功能,因为它更容易使用和测试。我称之为api_utils.GetTable
.Stored Procedure
如果你在谈论utils.OrderByLanguage
我不介意它是否是一个stored procedure
. 我不确定这会有什么帮助。所以,如果可以,请告诉我!
更新为已接受的答案
我发现添加一个索引并没有在性能方面产生影响。我还认为我不妨将该sort
列放在原始#sort
表中,因为无论如何它都必须相同。这减少了我的 SSDT 项目中的警告数量。然后我就像这样alter
在专栏上做一个:
ALTER TABLE #AlterSort ALTER COLUMN [sort] nvarchar(max) COLLATE SQL_Latin1_General_CP1_CI_AS
看看你到目前为止所拥有的,
[utils].[OrderByLanguage]
内联表值函数 (ITVF) 很好,但它仍然看起来是一个相关的子查询,其中对于其中的每一行,api_utils.GetTable(@LangID)
它都会传入所有行以api_utils.GetTable(@LangID)
对其进行排序(即ORDER BY
子句。在运行时应用排序规则可能会非常昂贵,因为它必须在那一刻为这些值生成排序键。为了获得最佳性能,创建索引将提前生成排序键,甚至将它们按正确的顺序排列。但是处理多个语言环境确实很棘手。根据字符串的大小、需要多少个区域设置/排序规则以及将超过多少行,为每个区域设置创建源列的副本可能需要大量额外的磁盘空间(和 I/O)未来 3 - 5 年。幸运的是,您可以创建非持久计算列作为那些副本(不占用任何空间)并索引那些(确实占用空间)。虽然如果有 10 个语言环境,这可能不可行,但至少有一个基本列
NVARCHAR(200)
和 100 万(或更多)行,对于您的情况,它应该可以正常工作。使用这种方法,动态部分将是选择要从中选择的列(可通过动态 SQL 或IF
语句实现,具体取决于情况)。但正如您在以下示例中看到的(打开“包括实际执行计划”),两个过滤查询(最后 2 个查询)都在预期索引上获取索引搜索并返回预期结果:您可以在 dbfiddle.uk 上看到现场演示。
但是,鉴于此代码
api.GetTable
可能不是最佳方法。如果您想保留当前结构(尽可能多),那么您可以执行以下操作:api.GetTable
为存储过程OUTPUT
参数,这样你就不需要处理结果集@Sort
应该是临时表,不是表变量#Sort
的所有列创建临时表:、、和。[Name]
title
ID
category
[system]
在一系列
IF
语句中,添加[title]
列,但使用正确的排序规则:FROM api_utils.GetTable(@LangID) t
主@Result=
查询更改为:FROM #Sort t
ORDER BY (SELECT s.RowNumber...
主@Result=
查询更改为:ORDER BY t.[title]
这将需要在每次执行时重新应用排序规则,但是:
api.GetTable
(没有双重调用)[Name]
列上的统计信息[Name]
它为您提供了在列被填充后创建索引的选项。代码重用
通过切换到存储过程和临时表,我们实际上可以实现代码重用的目标。将排序列添加到本地临时表的代码可以抽象为另一个存储过程。虽然在子过程调用中创建临时表没有帮助,因为一旦该子过程调用结束该临时表将消失,对子过程调用之前存在的临时表所做的更改将在该调用完成后继续存在. 例如:
设置
测试
通过仅将特定排序规则放在这个存储过程中,在添加要支持的新语言时,您应该只有这个地方可以更新。
把它们放在一起
考虑到以上所有内容,我们最终得到以下存储过程,其中包含对 的单个调用
api_utils.GetTable()
,没有相关的子查询,并且api_utils.AddSortColumn
(您需要创建)可以在其他存储过程中使用并针对其他基表:另一种方法是只拥有一个代码生成器。这是如果你需要最大性能,因为它只比标记为我给定参数的答案的解决方案快 14%(当你在多个地方和同一代码的多个部分使用它时,给定的 14% 加起来相当快) .