SQL Server вступает в порядок - PullRequest
7 голосов
/ 16 марта 2012

У меня есть 2 строки на входе, например, '1,5,6' и '2,89,9' с одинаковым номером элемента (3 или больше). Те 2 строки, которые я хочу, сделали "объединение ординат" как

1   2
5   89
6   9

Я думаю назначить число и сделать соединение между двумя наборами результатов как

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 ]

13 голосов
/ 16 марта 2012

Когда dbo.Split() возвращает набор данных, вы ничего не можете назначить по желанию row_number (исходя из их порядка в строке) с абсолютной уверенностью.SQL никогда не гарантирует порядок без ORDER BY, который действительно относится к данным.

С вами трюк использования (SELECT 0) для заказа вы можете часто получить правильные значения.Вероятно очень часто.Но это никогда гарантировано .Время от времени вы будете получать неправильный порядок.

Лучшим вариантом будет перекодировать dbo.Split(), чтобы назначить row_number при разборе строки.Только тогда вы сможете со 100% уверенностью знать, что номер строки действительно соответствует позиции элемента в списке.

Затем вы присоединяетесь к ним, как вы предлагаете, и получаете нужные результаты.

Кроме этого, идея мне кажется прекрасной.Хотя вы можете рассмотреть FULL OUTER JOIN, если один список может быть длиннее другого.

7 голосов
/ 16 марта 2012

Вы можете сделать это так же

Рассмотрим вашу функцию разделения следующим образом:

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
0 голосов
/ 23 сентября 2013

Благодаря предложению Ариона. Это очень полезно для меня. Я немного изменил функцию для поддержки типа входной строки типа 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

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...