【问题标题】:How to extract alpha or numbers from a string within a SELECT statement WITHOUT a function call如何在没有函数调用的情况下从 SELECT 语句中的字符串中提取字母或数字
【发布时间】:2022-01-22 09:00:16
【问题描述】:

我正在使用以下 SQL 创建一个函数来去除字符或数字。

CREATE FUNCTION [dbo].[fn_StripCharacters]
(
    @String NVARCHAR(MAX), 
    @MatchExpression VARCHAR(255)
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
    SET @MatchExpression =  '%['+@MatchExpression+']%'

    WHILE PatIndex(@MatchExpression, @String) > 0
        SET @String = Stuff(@String, PatIndex(@MatchExpression, @String), 1, '')

    RETURN @String

END

我这样称呼它来提取字母字符,然后将数字字符提取到两个排序字段中:

SELECT
    ...
    (SELECT dbo.fn_StripCharacters(PD.District, '^a-z')) AS Sort1,
    CAST((SELECT dbo.fn_StripCharacters(PD.District, '^0-9')) AS INT) AS Sort2,
    ...
FROM

我正在搜索大量邮政编码区记录,并且对该函数的不断调用导致了相对较大的延迟。 有没有一种方法可以在没有函数调用的情况下重现此功能? 能否以某种方式将其合并到 SELECT 语句中?

SQL Server 版本是 2017

【问题讨论】:

  • 提问时,您需要提供minimal reproducible example: (1) DDL 和样本数据填充,即 CREATE 表和 INSERT T-SQL 语句。 (2) 你需要做什么,即逻辑和你的代码尝试在 T-SQL 中实现它。 (3) 期望的输出,基于上述#1 中的样本数据。 (4) 您的 SQL Server 版本 (SELECT @@version;)。
  • 从根本上说,SQL Server 在这方面做得很差......所以你可能会得到一个更快的解决方案,而不是在 SQL Server 中,或者在 CLR 函数中。
  • 另外 - “18.9.2”不是有效的 SQL Server 版本 - 最新的是 SQL Server 2019 - v15.xyz -您很可能在谈论 SQL Server Mgmt Studio 的版本——它只是与 SQL Server 对话的 GUI 工具,但它的版本与实际的 SQL Server 版本完全不同。在 SSMS 中运行 SELECT @@VERSION 并报告您真正拥有的 SQL Server 版本....
  • 最好在内联表值函数中执行此操作。请提供正确的 SQL Server 版本。示例数据和预期结果也适用于 SQL 问题。
  • ..将数字与任何其他字符分开(可能值得一试)..dbfiddle.uk/…

标签: sql sql-server tsql query-optimization stored-functions


【解决方案1】:

你可以试试这样的:

 Create Function [dbo].[fnCleanString] (
        @inputString varchar(8000)
      , @stringPattern varchar(50) = '[0-9a-zA-Z]'
        )
Returns table
   With schemabinding
     As
 Return

   With t(n)
     As (
 Select t.n 
   From (
 Values (0), (0), (0), (0), (0), (0), (0), (0), (0), (0)
      , (0), (0), (0), (0), (0), (0), (0), (0), (0), (0)) As t(n)
        )
      , iTally (n)
     As (
 Select Top (len(@inputString)) 
        checksum(row_number() over(Order By @@spid)) 
   From t t1, t t2, t t3                                -- 8000 rows
        )
 Select v.inputString
      , outputString = (Select substring(v.inputString, it.n, 1)
                          From iTally                     it
                         Where substring(v.inputString, it.n, 1) Like @stringPattern
                           For xml Path(''), Type).value('.', 'varchar(8000)')
   From (Values (@inputString)) As v(inputString);
GO

然后这样称呼它:

Declare @testData table (AlphaNumeric varchar(100));
 Insert Into @testData (AlphaNumeric)
 Values ('a1b2c3d4'), ('5e6f7g8i');

 Select *
   From @testData                       td
  Cross Apply dbo.fnCleanString(td.AlphaNumeric, '[^A-Z]') cs
  Cross Apply dbo.fnCleanString(td.AlphaNumeric, '[^0-9]') cs2;

这适用于 VARCHAR - 但如果这是您真正需要的,您可以轻松地将其更改为返回 NVARCHAR。请注意,它是为使用固定长度的字符串而编写的,并且 NVARCHAR 的最大大小为 4000。

您还需要了解排序规则以及这将如何影响结果。

【讨论】:

    【解决方案2】:

    如何在不调用函数的情况下从 SELECT 语句中的字符串中提取字母或数字?

    你不能,因为像REPLACE() 这样的简单字符串操作也是函数调用。而且,用户定义的函数由 SQL Server 编译。他们的表现相当不错。

    但您的问题不在于函数本身的性能,而在于您必须使用它的频率。你知道的。

    这里有一个可能的方法来加快您的邮政编码研磨任务:将持久计算列放在您的表上。您甚至可以为它们编制索引。

    这是如何做到这一点的。

    1. 告诉 SQL Server 将架构绑定与您的存储函数一起使用。它需要知道架构中的表定义取决于函数。为此,请将 WITH SCHEMABINDING 添加到您的函数定义中。

      ...
      RETURNS NVARCHAR(MAX)
      WITH SCHEMABINDING
      AS
      BEGIN
      ...
      
    2. 向您的表中添加两个计算的、持久的列。

      ...
      ALTER TABLE postcode
             ADD letters   
             AS (dbo.fn_StripCharacters(postcode, '^A-Z'))
             PERSISTED;
       ALTER TABLE dbo.postcode
             ADD numbers
             AS (CAST(dbo.fn_StripCharacters(postcode, '^0-9') AS INT)) 
             PERSISTED;
      
    3. 如果需要,您可以在计算列上放置索引。

      CREATE INDEX numbers ON postcode  (numbers DESC)
      
    4. 现在您可以根据需要插入、更新或删除非计算列。 SQL Server 在插入或更新存储函数时对每一行只计算一次。您的函数仍会被评估,但当您从表中选择时不会。

    5. 您可以根据需要使用计算列

      SELECT * FROM postcode ORDER BY numbers DESC
      

    这里有一个db<>fiddle 演示了这一点。

    【讨论】:

    • 我正在考虑向数据库添加列而不是在查询上拆分区域,但这是一个非常有趣和有用的答案,并且对我的教育远远超出了我最初遇到的性能问题试图解决。我从来没有想过以这种方式使用计算值。
    猜你喜欢
    • 1970-01-01
    • 2011-11-24
    • 1970-01-01
    • 2015-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-18
    • 1970-01-01
    相关资源
    最近更新 更多