Very new to SQL but I require some help with something that I am sure is a simple fix.
I have a single column of data within a table called \'Produce\' where types of fr
This can be done with pure SQL, no user written functions needed.
SQL Server
WITH
fruittable
AS
( SELECT 'Apple' fruit, 1 id
UNION ALL
SELECT 'Banana,Apple', 2
UNION ALL
SELECT 'Tomato,Grapefruit,Apple', 3
UNION ALL
SELECT 'Watermelon,Persimmons', 4
),
split (fruit, id, leftover)
AS
(SELECT case when len(fruit) = 0 or fruit is null then null else left(fruit + ',', charindex(',',fruit + ',') -1 ) end AS fruit
, id
, case when len(fruit) = 0 or fruit is null then null else right(fruit + ',', len(fruit) - charindex(',',fruit + ',') + 1) end as leftover
FROM fruittable
UNION ALL
SELECT case when len(leftover) = 0 or leftover is null then null else left(leftover, charindex(',',leftover) - 1) end AS fruit
, id
, case when len(leftover) = 0 or leftover is null then null else substring(leftover, charindex(',',leftover) + 1, len(leftover)) end as leftover
FROM split
WHERE fruit IS NOT NULL)
SELECT fruit, id
FROM split where fruit is not null
order by fruit, id;
Oracle
WITH
fruittable
AS
(SELECT 'Apple' fruit, 1 id
FROM DUAL
UNION ALL
SELECT 'Banana,Apple', 2
FROM DUAL
UNION ALL
SELECT 'Tomato,Grapefruit,Apple', 3
FROM DUAL
UNION ALL
SELECT 'Watermelon,Persimmons', 4
FROM DUAL),
split (fruit, id, leftover)
AS
(SELECT SUBSTR (fruit || ',', 1, INSTR (fruit || ',', ',') - 1) AS fruit
, id
, SUBSTR (fruit || ',', INSTR (fruit || ',', ',') + 1) AS leftover
FROM fruittable
UNION ALL
SELECT SUBSTR (leftover, 1, INSTR (leftover, ',') - 1) AS fruit
, id
, SUBSTR (leftover, INSTR (leftover, ',') + 1) AS leftover
FROM split
WHERE fruit IS NOT NULL)
SELECT fruit, id
FROM split
WHERE fruit IS NOT NULL
ORDER BY fruit, id
The core problem to fix would be to stop storing your values as comma separated lists. Keep your data normalized. With that being said... everyone needs a good splitter...
declare @table table (Fruit varchar(64))
insert into @table
values
('Apple'),
('Plum'),
('Pear,Mango'),
('Pear')
select distinct
Item
from
@table
cross apply
dbo.DelimitedSplit8K(Fruit,',')
OR, If you are on SQL Server 2016...
select distinct
Item
from
@table
cross apply
string_split(Fruit,',')
THE FUNCTION
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[DelimitedSplit8K] (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
/* "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
enough to cover VARCHAR(8000)*/
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(@pString, l.N1, l.L1)
FROM cteLen l
;
GO
Jeff Moden Article for Function