T SQL 表值函数以逗号分隔列

Ove*_*etS 10 sql-server-2008 sql-server t-sql functions

我在 Microsoft SQL Server 2008 中编写了一个表值函数,以在数据库中使用逗号分隔的列来为每个值吐出单独的行。

例如:“一、二、三、四”将返回一个只有一列包含以下值的新表:

one
two
three
four
Run Code Online (Sandbox Code Playgroud)

这段代码看起来容易出错吗?当我测试它时

SELECT * FROM utvf_Split('one,two,three,four',',') 
Run Code Online (Sandbox Code Playgroud)

它永远运行并且永远不会返回任何东西。这真的很令人沮丧,尤其是因为 MSSQL 服务器上没有内置的拆分函数(为什么为什么为什么?!)而且我在网上找到的所有类似函数都是绝对垃圾或根本与我想要做的事情无关.

这是函数:

USE *myDBname*
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[utvf_SPlit] (@String VARCHAR(MAX), @delimiter CHAR)

RETURNS @SplitValues TABLE
(
    Asset_ID VARCHAR(MAX) NOT NULL
)

AS
BEGIN
            DECLARE @FoundIndex INT
            DECLARE @ReturnValue VARCHAR(MAX)

            SET @FoundIndex = CHARINDEX(@delimiter, @String)

            WHILE (@FoundIndex <> 0)
            BEGIN
                  DECLARE @NextFoundIndex INT
                  SET @NextFoundIndex = CHARINDEX(@delimiter, @String, @FoundIndex+1)
                  SET @ReturnValue = SUBSTRING(@String, @FoundIndex,@NextFoundIndex-@FoundIndex)
                  SET @FoundIndex = CHARINDEX(@delimiter, @String)
                  INSERT @SplitValues (Asset_ID) VALUES (@ReturnValue)
            END

            RETURN
END
Run Code Online (Sandbox Code Playgroud)

Aar*_*and 20

我不会用循环来做这件事;有更好的选择。迄今为止最好的,当您必须拆分时,是 CLR,而Adam Machanic 的方法是我测试过的最快的方法

下一个最好的方法恕我直言,如果你不能实现 CLR,是一个数字表:

SET NOCOUNT ON;

DECLARE @UpperLimit INT = 1000000;

WITH n AS
(
    SELECT
        x = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
    FROM       sys.all_objects AS s1
    CROSS JOIN sys.all_objects AS s2
    CROSS JOIN sys.all_objects AS s3
)
SELECT Number = x
  INTO dbo.Numbers
  FROM n
  WHERE x BETWEEN 1 AND @UpperLimit
  OPTION (MAXDOP 1); -- protecting from Paul White's observation

GO
CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers(Number) 
    --WITH (DATA_COMPRESSION = PAGE);
GO
Run Code Online (Sandbox Code Playgroud)

...允许此功能:

CREATE FUNCTION dbo.SplitStrings_Numbers
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN
   (
       SELECT Item = SUBSTRING(@List, Number, 
         CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)
       FROM dbo.Numbers
       WHERE Number <= CONVERT(INT, LEN(@List))
         AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter
   );
GO
Run Code Online (Sandbox Code Playgroud)

我相信所有这些都会比你拥有的函数表现得更好,你让它工作时,特别是因为它们是内联的而不是多语句的。我还没有调查为什么你的不工作,因为我认为让这个功能工作不值得。

但这一切都说...

既然您使用的是 SQL Server 2008,那么您首先需要拆分吗?我宁愿为此使用 TVP:

CREATE TYPE dbo.strings AS TABLE
(
  string NVARCHAR(4000)
);
Run Code Online (Sandbox Code Playgroud)

现在您可以接受它作为存储过程的参数,并像使用 TVF 一样使用内容:

CREATE PROCEDURE dbo.foo
  @strings dbo.strings READONLY
AS
BEGIN
  SET NOCOUNT ON;

  SELECT Asset_ID = string FROM @strings;
  -- SELECT Asset_ID FROM dbo.utvf_split(@other_param, ',');
END
Run Code Online (Sandbox Code Playgroud)

并且您可以直接从 C# 等传递 TVP 作为数据表。这几乎肯定会优于上述任何解决方案,特别是如果您专门在应用程序中构建逗号分隔的字符串,以便您的存储过程可以调用 TVP 将其再次拆分。有关TVP 的更多信息,请参阅Erland Sommarskog 的精彩文章

最近,我写了一个关于拆分字符串的系列文章:

如果您使用的是 SQL Server 2016 或更新版本(或 Azure SQL 数据库),则有一个新STRING_SPLIT函数,我在此博客中介绍了该函数


Mic*_*een 7

SQL Server 2016 引入了STRING_SPLIT()函数。它有两个参数 - 要切碎的字符串和分隔符。输出是每个返回值一行。

对于给定的例子

SELECT * FROM string_split('one,two,three,four', ',');
Run Code Online (Sandbox Code Playgroud)

将返回

value
------------------
one
two
three
four
Run Code Online (Sandbox Code Playgroud)


Der*_*omm 1

稍微重新做了一下...

DECLARE @FoundIndex INT
DECLARE @ReturnValue VARCHAR(MAX)

SET @FoundIndex = CHARINDEX(@delimiter, @String)

WHILE (@FoundIndex <> 0)
BEGIN
      SET @ReturnValue = SUBSTRING(@String, 0, @FoundIndex)
      INSERT @SplitValues (Asset_ID) VALUES (@ReturnValue)
      SET @String = SUBSTRING(@String, @FoundIndex + 1, len(@String) - @FoundIndex)
      SET @FoundIndex = CHARINDEX(@delimiter, @String)
END

INSERT @SplitValues (Asset_ID) VALUES (@String)

RETURN
Run Code Online (Sandbox Code Playgroud)