2012-03-16 7 views
7

Tengo 2 cadenas en la entrada por ejemplo '1,5,6' y '2,89,9' con el mismo número de elemento (3 o más). Los 2 cuerdas que quiero hizo una "ordenada unirse" comoSQL Server Unir en el pedido

1 2 
5 89 
6 9 

he pensar en asignar un rownumber e hizo una unión entre 2 conjunto de resultados como

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 

es que un mejores prácticas ¿nunca?

Respuesta

13

Cuando dbo.Split() devuelve el conjunto de datos, nada de lo que haga puede asignar el número de fila que desee (según su orden en la cadena) con absoluta certeza. SQL nunca garantiza un pedido sin un ORDER BY que realmente se relaciona con los datos.

Con que truco de utilizar (SELECT 0) al orden por el que puede menudo obtener los valores correctos. Probablemente muy a menudo. Pero esto es nuncagarantizado. De vez en cuando usted obtendrá el orden incorrecto.

Su mejor opción es codificar dbo.Split() para asignar un row_number a medida que se analiza la cadena. Solo entonces puedes saber con 100% de certeza que row_number realmente se corresponde con la posición del elemento en la lista.

Luego, únete a ellos como sugieres y obtén los resultados que deseas.


Aparte de eso, la idea parece muy bien a mí. Aunque es posible que desee considerar un FULL OUTER JOIN si una lista puede ser más larga que la otra.

+0

Sí, es correcto que diga. E hizo un FULL OUTER JOIN es una buena práctica;). Pero he pensado que había una "ORDER JOIN" en particular :-) –

+0

Sí, un caso similar en el que 'SELECT 0' altera el orden deseado cuando se selecciona de una tabla derivada ordenada: http://stackoverflow.com/ q/18961789/521799 –

7

Usted puede hacerlo de esta manera, así

Tenga en cuenta su función de división de esta manera:

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 

El que será una tarea sencilla para JOIN juntos. De esta manera:

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

La ventaja de esto es que no es necesario ningún ROW_NUMBER() OVER(ORDER BY SELECT 0)

Editar

Al igual que en el comentario de que el rendimiento es mejor con una función de división recursiva. Así que tal vez algo como esto:

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 

Y entonces la selección es la siguiente:

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

¡bueno! ;) gracias –

+1

No hay problema ... Me alegra ayudar – Arion

+1

@Arion - ¿Sabe con certeza que este enfoque *** garantiza *** el orden en que se insertan los registros? Mi entendimiento era que las mismas consideraciones dadas al procesamiento paralelo, etc., se aplicarían aquí: no ha especificado un ORDER BY, y entonces los datos * pueden * (pero no * serán *) insertados en un orden diferente al cual aparece en la cuerda. Si hay alguna referencia que tenga que otorgue tales garantías, me encantaría verlas, ya que preferiría este enfoque para iterar a través de la cadena y crear explícitamente el valor de identidad. Solo quiero que *** garantía *** :) – MatBailie

0

Gracias a la sugerencia de Arion.Es muy útil para mi. Modifiqué la función un poco para admitir varchar (max) tipo de cadena de entrada, y una longitud máxima de 1000 para la cadena de delimitador. Además, agregó un parámetro para indicar si necesita la cadena vacía en la devolución final.

Para la pregunta de MatBailie, porque esta es una función en línea, puede incluir la columna pn en su consulta externa que llama a esta función.

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 
) 

Pero me encontré con un problema con esta función cuando la lista que se pretendía devolver tenía más de 100 registros. Por lo tanto, he creado otra función puramente utilizando funciones de análisis de cadenas:

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, @[email protected]) 
     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 

Espero que esto ayude.