2012-03-16 12 views
7

Ho 2 string in input per esempio '1,5,6' e '2,89,9' con lo stesso numero di elementi (3 o più). Coloro 2 stringa Voglio fatto una "ordinata join" comeSQL Server Join In Order

1 2 
5 89 
6 9 

ho pensare di assegnare un RowNumber e ha fatto un join tra 2 set di risultati come

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 

è che una best practicemai?

risposta

13

Quando dbo.Split() restituisce il set di dati, non è possibile assegnare il numero di riga desiderato (in base al relativo ordine nella stringa) con assoluta certezza. SQL mai garantisce un ordine senza uno ORDER BY che si riferisce effettivamente ai dati.

Con te trucco di utilizzare (SELECT 0) per ordinare da voi può spesso ottenere i giusti valori. Probabilmente molto spesso. Ma questo è maigarantito. Una volta ogni tanto il tuo sarà ottenere l'ordine sbagliato.

L'opzione migliore è ricodificare dbo.Split() per assegnare un numero_riga mentre la stringa viene analizzata. Solo allora puoi sapere con certezza al 100% che il numero_riga corrisponde realmente alla posizione dell'elemento nell'elenco.

Quindi ti unisci a loro come suggerisci e ottieni i risultati desiderati.


Oltre a questo, l'idea sembra bene a me. Sebbene tu possa considerare un FULL OUTER JOIN se una lista può essere più lunga dell'altra.

+0

Sì, è corretto che tu dica. E fatto un FULL OUTER JOIN è una best practice;). Ma ho pensato che c'era un particolare "ORDER JOIN" :-) –

+0

Sì, un caso simile in cui 'SELECT 0' manomette l'ordine desiderato quando si seleziona da una tabella derivata ordinata: http://stackoverflow.com/ q/18961789/521799 –

7

si può fare in questo modo così

Considerate la vostra funzione split in questo modo:

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 

Il che sarà un compito semplice per JOIN insieme. Come questo:

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

Il vantaggio di questo è che non è necessario alcun ROW_NUMBER() OVER(ORDER BY SELECT 0)

Modifica

Come nel commento la performance è migliore con una funzione split ricorsiva. Quindi forse qualcosa di simile:

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 

E poi la selezione è così:

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

ottimo! ;) grazie –

+1

Nessun problema .. Felice di aiutare – Arion

+1

@Arion - Sai per certo che questo approccio *** garantisce *** l'ordine in cui sono inseriti i record? La mia comprensione era che le stesse considerazioni date all'elaborazione parallela, ecc, si applicherebbero qui: non hai specificato un ordine BY, e quindi i dati * possono * (ma non * sarà *) essere inseriti in un ordine diverso a cui appare nella stringa. Se ci sono dei riferimenti che ti danno tali garanzie, mi piacerebbe vederli, poiché preferirei questo approccio per iterare attraverso la stringa e creare esplicitamente il valore dell'identità. Voglio solo quella *** *** *** :) – MatBailie

0

Grazie al suggerimento di Arion.È molto utile per me. Ho modificato leggermente la funzione per supportare il tipo di stringa di input varchar (max) e la lunghezza massima di 1000 per la stringa delimitatore. Inoltre, aggiunto un parametro per indicare se è necessaria la stringa vuota nel ritorno finale.

Per la domanda di MatBailie, poiché questa è una funzione inline, è possibile includere la colonna pn nella query esterna che chiama questa funzione.

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 
) 

Ma mi sono imbattuto in un po 'di problemi con questa funzione quando la lista intendeva restituire più di 100 record. Così, ho creato un'altra funzione puramente utilizzando le funzioni di analisi delle stringhe:

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 

Spero che questo possa aiutare.