SQL Server按顺序加入

时间:2012-03-16 12:20:22

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

我在输入中有2个字符串,例如'1,5,6'和'2,89,9',具有相同数量的元素(3或加号)。 我想要的那两个字符串作为

进行了“纵坐标连接”
1   2
5   89
6   9

我想要分配一个rownumber并在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 

最佳做法吗?

3 个答案:

答案 0 :(得分:13)

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

使用技巧使用(SELECT 0)订购, 经常 获取正确的值。可能经常非常。但这从不 保证 。偶尔你 得到错误的订单。

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

然后按照您的建议加入他们,并获得您想要的结果。


除此之外,这个想法对我来说似乎没什么问题。虽然您可能希望考虑FULL OUTER JOIN,如果一个列表可能比另一个更长。

答案 1 :(得分:7)

你也可以这样做

考虑你的拆分功能:

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
    *
FROM
    dbo.Split('1,5,6',',') AS a
    JOIN dbo.Split('2,89,9',',') AS b
        ON a.pn=b.pn

答案 2 :(得分:0)

感谢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

希望这可以提供帮助。