【问题标题】:SQL Server Join In OrderSQL Server 加入顺序
【发布时间】:2012-04-01 23:42:56
【问题描述】:

我在输入中有 2 个字符串,例如 '1,5,6' 和 '2,89,9' 具有相同数量的元素(3 或更多)。 我想要的那些 2 个字符串作为“纵坐标连接”

1   2
5   89
6   9

我想分配一个行号并在 2 个结果集之间进行连接

SELECT a.item, b.item  FROM 
  (
  SELECT  
  ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS rownumber,
  *  FROM dbo.Split('1,5,6',',')
  ) AS a
  INNER JOIN   
  (
  SELECT  
  ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS rownumber,
  *  FROM dbo.Split('2,89,9',',')
 ) AS b ON a.rownumber = b.rownumber 

这是最佳实践吗?

【问题讨论】:

    标签: sql sql-server sql-server-2008 join


    【解决方案1】:

    dbo.Split() 返回数据集时,您所做的任何事情都无法绝对确定地分配您想要的row_number(基于它们在字符串中的顺序)。 SQL 从不保证没有与数据实际相关的ORDER BY 的排序。

    使用(SELECT 0) 进行订购的技巧可以经常获得正确的值。可能非常经常。但这是从不 保证。偶尔您得到错误的顺序。

    您最好的选择是重新编码 dbo.Split() 以在解析字符串时分配 row_number。只有这样,您才能 100% 确定 row_number 确实对应于该项目在列表中的位置。

    然后你按照你的建议加入他们,得到你想要的结果。


    除此之外,这个想法在我看来确实不错。如果一个列表可能比另一个更长,您可能希望考虑使用FULL OUTER JOIN

    【讨论】:

    • 是的,你说的没错。并进行了 FULL OUTER JOIN 这是最佳实践;)。但我认为有一个特定的“ORDER JOIN”:-)
    • 是的,SELECT 0 篡改所需顺序的类似情况是从有序派生表中选择时:stackoverflow.com/q/18961789/521799
    【解决方案2】:

    你也可以这样做

    考虑这样的拆分函数:

    CREATE FUNCTION Split
    (
      @delimited nvarchar(max),
      @delimiter nvarchar(100)
    ) RETURNS @t TABLE
    (
      id int identity(1,1),
      val nvarchar(max)
    )
    AS
    BEGIN
      declare @xml xml
      set @xml = N'<root><r>' + replace(@delimited,@delimiter,'</r><r>') + '</r></root>'
    
      insert into @t(val)
      select 
        r.value('.','varchar(5)') as item
      from @xml.nodes('//root/r') as records(r)
    
      RETURN
    END
    GO
    

    JOIN他们在一起将是一项简单的任务。像这样:

    SELECT
        *
    FROM
        dbo.Split('1,5,6',',') AS a
        JOIN dbo.Split('2,89,9',',') AS b
            ON a.id=b.id
    

    这样做的好处是您不需要任何ROW_NUMBER() OVER(ORDER BY SELECT 0)

    编辑

    如评论中所述,递归拆分函数的性能更好。所以可能是这样的:

    CREATE FUNCTION dbo.Split (@s varchar(512),@sep char(1))
    RETURNS table
    AS
    RETURN (
        WITH Pieces(pn, start, stop) AS (
          SELECT 1, 1, CHARINDEX(@sep, @s)
          UNION ALL
          SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
          FROM Pieces
          WHERE stop > 0
        )
        SELECT pn,
          SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
        FROM Pieces
      )
    GO
    

    然后select是这样的:

    SELECT
        *
    FROM
        dbo.Split('1,5,6',',') AS a
        JOIN dbo.Split('2,89,9',',') AS b
            ON a.pn=b.pn
    

    【讨论】:

    • @Arion - 您确定这种方法保证记录的插入顺序吗?我的理解是对并行处理等的相同考虑将适用于此处:您尚未指定 ORDER BY,因此数据 can(但不是 will)以与它在字符串中出现的不同顺序插入。如果您有任何引用可以提供这样的保证,我很乐意看到它们,因为我更喜欢这种方法来遍历字符串并自己显式创建标识值。我只想要那个保证 :)
    • 不,我不能保证记录的顺序。我的理解是它将插入字符串中提供的顺序。但我不能保证插入顺序。我只是给了 OP 一种不同的方法来处理他的查询。
    • 使用正确编写的递归拆分函数可以获得更好的性能
    • 很高兴知道.. 更新了答案
    • 我会采用quite old KB article暗示,除非您添加order by.
    【解决方案3】:

    感谢 Arion 的建议。这对我来说非常有用。我稍微修改了函数以支持输入字符串的 varchar(max) 类型,并且分隔符字符串的最大长度为 1000。另外,添加了一个参数来指示您是否需要在最终返回中使用空字符串。

    对于 MatBailie 的问题,因为这是一个内联函数,您可以在调用此函数的外部查询中包含 pn 列。

    CREATE FUNCTION dbo.Split (@s nvarchar(max),@sep nvarchar(1000),  @IncludeEmpty bit)
    RETURNS table
    AS
    RETURN (
        WITH Pieces(pn, start, stop) AS (
          SELECT convert(bigint, 1) , convert(bigint, 1), convert(bigint,CHARINDEX(@sep, @s))
          UNION ALL
          SELECT pn + 1, stop + LEN(@sep), CHARINDEX(@sep, @s, stop + LEN(@sep))
          FROM Pieces
          WHERE stop > 0
        )
        SELECT pn,
          SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE LEN(@s) END) AS s
        FROM Pieces
        where start< CASE WHEN stop > 0 THEN stop ELSE LEN(@s) END + @IncludeEmpty
      )
    

    但是当打算返回的列表有超过 100 条记录时,我遇到了这个函数的一点问题。所以,我创建了另一个纯粹使用字符串解析函数的函数:

    Create function [dbo].[udf_split] (
        @ListString nvarchar(max),
        @Delimiter  nvarchar(1000),
        @IncludeEmpty bit) 
    Returns @ListTable TABLE (ID int, ListValue varchar(max))
    AS
    BEGIN
        Declare @CurrentPosition int, @NextPosition int, @Item nvarchar(max), @ID int
        Select  @ID = 1,
                @ListString = @Delimiter+ @ListString + @Delimiter,
                @CurrentPosition = 1+LEN(@Delimiter)
    
        Select  @NextPosition = Charindex(@Delimiter, @ListString, @CurrentPosition)
        While   @NextPosition > 0 Begin
    
            Select  @Item = Substring(@ListString, @CurrentPosition, @NextPosition-@CurrentPosition)
            If      @IncludeEmpty=1 or Len(LTrim(RTrim(@Item)))>0 Begin 
                    Insert Into @ListTable (ID, ListValue) Values (@ID, LTrim(RTrim(@Item)))
                    Set @ID = @ID+1
            End
            Select  @CurrentPosition = @NextPosition+LEN(@Delimiter), 
                    @NextPosition = Charindex(@Delimiter, @ListString, @CurrentPosition)
        End
        RETURN
    END
    

    希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 2017-04-13
      • 2021-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-13
      • 2012-04-23
      • 2014-11-12
      相关资源
      最近更新 更多