拆分值对并使用 UDF 创建表

发布于 2024-11-24 03:23:32 字数 541 浏览 0 评论 0原文

我一直在尝试编写一个表值函数,它将值对作为参数并返回一个包含两列的表。

下面是我想要做的函数签名。

FUNCTION [dbo].[ValuePairParser]( @DelimitedValuePairs VARCHAR(MAX),
                                  @Delimiter CHAR(1), 
                                  @ValuePairDelimiter CHAR(1) ) 
  RETURNS @ValuePairTable
  TABLE ( Id INT, Code INT ) 

我想调用如下方法,

@ValuePairs VARCHAR(MAX) = '1:1, 1:2, 1:4, 2:3, 1000:230, 130:120,'

ValuePairParser (@ValuePairs, ',', ':')

你能看到任何好的方法来分割上面的 ValuePairs 字符串并创建一个包含两列的表吗?

I've been trying to write a Table-Valued function that takes value pairs as a parameter and return a table with two columns.

Below is the function signature I am trying to do.

FUNCTION [dbo].[ValuePairParser]( @DelimitedValuePairs VARCHAR(MAX),
                                  @Delimiter CHAR(1), 
                                  @ValuePairDelimiter CHAR(1) ) 
  RETURNS @ValuePairTable
  TABLE ( Id INT, Code INT ) 

I want to call the method like below

@ValuePairs VARCHAR(MAX) = '1:1, 1:2, 1:4, 2:3, 1000:230, 130:120,'

ValuePairParser (@ValuePairs, ',', ':')

Can you see any nice way to split above ValuePairs sting and create a table with two columns?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

蓝眸 2024-12-01 03:23:32

十年后回顾,今天肯定有更好的方法来做到这一点。

SQL Server 2022、Azure SQL 数据库、托管实例

示例

CREATE OR ALTER FUNCTION dbo.SplitWithPairs
(
    @List           nvarchar(max),
    @MajorDelimiter varchar(3) = ',',
    @MinorDelimiter varchar(3) = ':'
)
RETURNS TABLE WITH SCHEMABINDING
AS
  RETURN 
  (
    SELECT LeftItem = [1], RightItem = [2], Position
    FROM
    (
      SELECT Position = o.ordinal, 
             value    = TRIM(i.value),
             i.ordinal
        FROM        STRING_SPLIT(@List,   @MajorDelimiter, 1) AS o
        CROSS APPLY STRING_SPLIT(o.value, @MinorDelimiter, 1) AS i
      WHERE o.value > ''
    ) AS s PIVOT (MAX(value) FOR ordinal IN ([1],[2])) AS p
  );

SQL Server 2016 - 2019

(示例

在 SQL Server 2016 中通过2019 年,进行了一些更改:

  • STRING_SPLIT 在 SQL Server 2022 之前不支持序数,因此我们可以使用 OPENJSON
  • 需要使用 LTRIM/RTRIM > 由于 SQL Server 2017 中添加了 TRIM
  • key 是从 0 开始的,因此我们增加 1 以获得等效的从 1 开始的位置

关于此函数的缺点是,在不更改函数的情况下,您不能使用 , 作为次分隔符,因为 ,OPENJSON 的分隔方式。您可能希望保留其他字符作为这两个分隔符。

CREATE OR ALTER FUNCTION dbo.SplitWithPairs
(
    @List           nvarchar(max),
    @MajorDelimiter varchar(3) = ',',
    @MinorDelimiter varchar(3) = ':' -- can't be ,
)
RETURNS TABLE WITH SCHEMABINDING
AS
  RETURN 
  (
    SELECT LeftItem = [1], RightItem = [2], Position
    FROM
    (  
      SELECT Position = o.[key] + 1, 
             value    = LTRIM(RTRIM(i.value)),
             ordinal  = i.[key] + 1
      FROM OPENJSON
      (
        CONCAT('["', REPLACE(STRING_ESCAPE(@List, 'JSON'), 
          @MajorDelimiter, '","'), '"]')
      ) AS o
      CROSS APPLY OPENJSON
      (
        REPLACE(CONCAT('[', QUOTENAME(o.value, '"'), ']'), 
          @MinorDelimiter, '","')
      ) AS i
      WHERE o.value > ''
    ) AS s PIVOT (MAX(value) FOR ordinal IN ([1],[2])) AS p
  );

不再支持的版本

最后,原始答案适用于 SQL Server 2016 之前的版本,我将保持原样,但有一个免责声明,它可以改进(不乏有关那里的阅读这里 以及一般来说,多语句表值函数(特别是带有循环的函数)是一个大危险信号):

CREATE FUNCTION [dbo].[SplitWithPairs]
(
    @List NVARCHAR(MAX),
    @MajorDelimiter VARCHAR(3) = ',',
    @MinorDelimiter VARCHAR(3) = ':'
)
RETURNS @Items TABLE
(
    Position  INT IDENTITY(1,1) NOT NULL,
    LeftItem  INT NOT NULL,
    RightItem INT NOT NULL
)
AS
BEGIN
    DECLARE
        @Item      NVARCHAR(MAX),
        @LeftItem  NVARCHAR(MAX),
        @RightItem NVARCHAR(MAX),
        @Pos       INT;

    SELECT
        @List = @List + ' ',
        @MajorDelimiter = LTRIM(RTRIM(@MajorDelimiter)),
        @MinorDelimiter = LTRIM(RTRIM(@MinorDelimiter));

    WHILE LEN(@List) > 0
    BEGIN
        SET @Pos = CHARINDEX(@MajorDelimiter, @List);

        IF @Pos = 0 
            SET @Pos = LEN(@List) + LEN(@MajorDelimiter);

        SELECT
            @Item = LTRIM(RTRIM(LEFT(@List, @Pos - 1))),
            @LeftItem = LTRIM(RTRIM(LEFT(@Item,
            CHARINDEX(@MinorDelimiter, @Item) - 1))),
            @RightItem = LTRIM(RTRIM(SUBSTRING(@Item,
            CHARINDEX(@MinorDelimiter, @Item)
            + LEN(@MinorDelimiter), LEN(@Item))));

        INSERT @Items(LeftItem, RightItem)
            SELECT @LeftItem, @RightItem;

        SET @List = SUBSTRING(@List,
            @Pos + LEN(@MajorDelimiter), DATALENGTH(@List));
    END
    RETURN;
END
GO

DECLARE @ValuePairs VARCHAR(MAX) = '1:1, 1:2, 1:4, 2:3,1000:230, 130:120,';

SELECT LeftItem, RightItem
  FROM dbo.SplitWithPairs(@ValuePairs, ',', ':')
  ORDER BY Position;
GO

Revisiting after a decade there are definitely better ways to do this today.

SQL Server 2022, Azure SQL Database, Managed Instance

(example)

CREATE OR ALTER FUNCTION dbo.SplitWithPairs
(
    @List           nvarchar(max),
    @MajorDelimiter varchar(3) = ',',
    @MinorDelimiter varchar(3) = ':'
)
RETURNS TABLE WITH SCHEMABINDING
AS
  RETURN 
  (
    SELECT LeftItem = [1], RightItem = [2], Position
    FROM
    (
      SELECT Position = o.ordinal, 
             value    = TRIM(i.value),
             i.ordinal
        FROM        STRING_SPLIT(@List,   @MajorDelimiter, 1) AS o
        CROSS APPLY STRING_SPLIT(o.value, @MinorDelimiter, 1) AS i
      WHERE o.value > ''
    ) AS s PIVOT (MAX(value) FOR ordinal IN ([1],[2])) AS p
  );

SQL Server 2016 - 2019

(example)

In SQL Server 2016 through 2019, a few changes:

  • STRING_SPLIT doesn't support ordinal until SQL Server 2022, so we can use OPENJSON instead
  • we need to use LTRIM/RTRIM since TRIM was added in SQL Server 2017
  • key is 0-based so we bump by 1 to get an equivalent 1-based position

One note about this function is that you can't use , as the minor delimiter without changing the function, since , is how OPENJSON delimits. There are probably other characters you may want to stay from for both delimiters.

CREATE OR ALTER FUNCTION dbo.SplitWithPairs
(
    @List           nvarchar(max),
    @MajorDelimiter varchar(3) = ',',
    @MinorDelimiter varchar(3) = ':' -- can't be ,
)
RETURNS TABLE WITH SCHEMABINDING
AS
  RETURN 
  (
    SELECT LeftItem = [1], RightItem = [2], Position
    FROM
    (  
      SELECT Position = o.[key] + 1, 
             value    = LTRIM(RTRIM(i.value)),
             ordinal  = i.[key] + 1
      FROM OPENJSON
      (
        CONCAT('["', REPLACE(STRING_ESCAPE(@List, 'JSON'), 
          @MajorDelimiter, '","'), '"]')
      ) AS o
      CROSS APPLY OPENJSON
      (
        REPLACE(CONCAT('[', QUOTENAME(o.value, '"'), ']'), 
          @MinorDelimiter, '","')
      ) AS i
      WHERE o.value > ''
    ) AS s PIVOT (MAX(value) FOR ordinal IN ([1],[2])) AS p
  );

Versions no longer supported

And finally, the original answer, which will work on versions older than SQL Server 2016, I'll leave as it was, but with a disclaimer that it could be improved (no shortage of reading about there here and, generally, multi-statement table-valued functions - particularly with loops - are a big red flag):

CREATE FUNCTION [dbo].[SplitWithPairs]
(
    @List NVARCHAR(MAX),
    @MajorDelimiter VARCHAR(3) = ',',
    @MinorDelimiter VARCHAR(3) = ':'
)
RETURNS @Items TABLE
(
    Position  INT IDENTITY(1,1) NOT NULL,
    LeftItem  INT NOT NULL,
    RightItem INT NOT NULL
)
AS
BEGIN
    DECLARE
        @Item      NVARCHAR(MAX),
        @LeftItem  NVARCHAR(MAX),
        @RightItem NVARCHAR(MAX),
        @Pos       INT;

    SELECT
        @List = @List + ' ',
        @MajorDelimiter = LTRIM(RTRIM(@MajorDelimiter)),
        @MinorDelimiter = LTRIM(RTRIM(@MinorDelimiter));

    WHILE LEN(@List) > 0
    BEGIN
        SET @Pos = CHARINDEX(@MajorDelimiter, @List);

        IF @Pos = 0 
            SET @Pos = LEN(@List) + LEN(@MajorDelimiter);

        SELECT
            @Item = LTRIM(RTRIM(LEFT(@List, @Pos - 1))),
            @LeftItem = LTRIM(RTRIM(LEFT(@Item,
            CHARINDEX(@MinorDelimiter, @Item) - 1))),
            @RightItem = LTRIM(RTRIM(SUBSTRING(@Item,
            CHARINDEX(@MinorDelimiter, @Item)
            + LEN(@MinorDelimiter), LEN(@Item))));

        INSERT @Items(LeftItem, RightItem)
            SELECT @LeftItem, @RightItem;

        SET @List = SUBSTRING(@List,
            @Pos + LEN(@MajorDelimiter), DATALENGTH(@List));
    END
    RETURN;
END
GO

DECLARE @ValuePairs VARCHAR(MAX) = '1:1, 1:2, 1:4, 2:3,1000:230, 130:120,';

SELECT LeftItem, RightItem
  FROM dbo.SplitWithPairs(@ValuePairs, ',', ':')
  ORDER BY Position;
GO
獨角戲 2024-12-01 03:23:32
create function ValuePairParser(@DelimitedValuePairs varchar(MAX),
                                @Delimiter char(1), 
                                @ValuePairDelimiter char(1)) 
returns @ValuePairTable table(Id int, Code int) as
begin 
  with Split(ValuePair, Rest) as
  (
    select left(@DelimitedValuePairs, charindex(@Delimiter, @DelimitedValuePairs)-1),
           stuff(@DelimitedValuePairs, 1, charindex(@Delimiter, @DelimitedValuePairs), '')
    where charindex(@Delimiter, @DelimitedValuePairs) > 0
    union all
    select left(Rest, charindex(@Delimiter, Rest)-1),
           stuff(Rest, 1, charindex(@Delimiter, Rest), '')
    from Split         
    where charindex(@Delimiter, Rest) > 0
  )               
  insert into @ValuePairTable
  select left(ValuePair, charindex(@ValuePairDelimiter, ValuePair)-1),
         stuff(ValuePair, 1, charindex(@ValuePairDelimiter, ValuePair), '')
  from Split                 
  option (maxrecursion 0)

  return
end
create function ValuePairParser(@DelimitedValuePairs varchar(MAX),
                                @Delimiter char(1), 
                                @ValuePairDelimiter char(1)) 
returns @ValuePairTable table(Id int, Code int) as
begin 
  with Split(ValuePair, Rest) as
  (
    select left(@DelimitedValuePairs, charindex(@Delimiter, @DelimitedValuePairs)-1),
           stuff(@DelimitedValuePairs, 1, charindex(@Delimiter, @DelimitedValuePairs), '')
    where charindex(@Delimiter, @DelimitedValuePairs) > 0
    union all
    select left(Rest, charindex(@Delimiter, Rest)-1),
           stuff(Rest, 1, charindex(@Delimiter, Rest), '')
    from Split         
    where charindex(@Delimiter, Rest) > 0
  )               
  insert into @ValuePairTable
  select left(ValuePair, charindex(@ValuePairDelimiter, ValuePair)-1),
         stuff(ValuePair, 1, charindex(@ValuePairDelimiter, ValuePair), '')
  from Split                 
  option (maxrecursion 0)

  return
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文