T-SQL: opuesto a la concatenación de cadenas: cómo dividir una cadena en varios registros [duplicado]

Resuelto kristof asked hace 15 años • 11 respuestas

Posible duplicado:
cadena dividida en SQL

He visto un par de preguntas relacionadas con la concatenación de cadenas en SQL. Me pregunto cómo abordaría el problema opuesto: dividir una cadena delimitada por comas en filas de datos:

Digamos que tengo tablas:

userTypedTags(userID,commaSeparatedTags) 'one entry per user
tags(tagID,name)

Y quiero insertar datos en la tabla.

userTag(userID,tagID) 'multiple entries per user

Inspirado en ¿Qué etiquetas no están en la base de datos? pregunta

EDITAR

Gracias por las respuestas, en realidad más de una merece ser aceptada pero solo puedo elegir una, y la solución presentada por Cade Roux con recursiones me parece bastante limpia. Funciona en SQL Server 2005 y superior.

Para versiones anteriores de SQL Server se puede utilizar la solución proporcionada por miies . Para trabajar con datos de texto, la respuesta wcm será útil. Gracias de nuevo.

kristof avatar Nov 25 '08 00:11 kristof
Aceptado

Hay una amplia variedad de soluciones a este problema documentadas aquí , incluida esta pequeña joya:

CREATE FUNCTION dbo.Split (@sep char(1), @s varchar(512))
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
  )
Cade Roux avatar Nov 24 '2008 18:11 Cade Roux

También puede lograr este efecto utilizando XML, como se ve aquí , lo que elimina la limitación de las respuestas proporcionadas, que parecen incluir recursividad de alguna manera. El uso particular que he hecho aquí permite un delimitador de hasta 32 caracteres, pero podría aumentarse por grande que sea.

create FUNCTION [dbo].[Split] (@sep VARCHAR(32), @s VARCHAR(MAX))
RETURNS TABLE
AS
    RETURN
    (
        SELECT r.value('.','VARCHAR(MAX)') as Item
        FROM (SELECT CONVERT(XML, N'<root><r>' + REPLACE(REPLACE(REPLACE(@s,'& ','&amp; '),'<','&lt;'), @sep, '</r><r>') + '</r></root>') as valxml) x
        CROSS APPLY x.valxml.nodes('//root/r') AS RECORDS(r)
    )

Luego puedes invocarlo usando:

SELECT * FROM dbo.Split(' ', 'I hate bunnies')

Que devuelve:

-----------
|I        |
|---------|
|hate     |
|---------|
|bunnies  |
-----------


Debo señalar que en realidad no odio a los conejitos... simplemente me vino a la cabeza por alguna razón.
Lo siguiente es lo más parecido que se me ocurrió usando el mismo método en una función con valores de tabla en línea. ¡NO LO USES, ES HORRIBLE INEFICIENTE! Está aquí solo como referencia.

CREATE FUNCTION [dbo].[Split] (@sep VARCHAR(32), @s VARCHAR(MAX))
RETURNS TABLE
AS
    RETURN
    (
        SELECT r.value('.','VARCHAR(MAX)') as Item
        FROM (SELECT CONVERT(XML, N'<root><r>' + REPLACE(@s, @sep, '</r><r>') + '</r></root>') as valxml) x
        CROSS APPLY x.valxml.nodes('//root/r') AS RECORDS(r)
    )
Nathan Wheeler avatar May 14 '2010 21:05 Nathan Wheeler