T-SQL: opuesto a la concatenación de cadenas: cómo dividir una cadena en varios registros [duplicado]
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.
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
)
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,'& ','& '),'<','<'), @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)
)