update totally forgot the SUBSTRING_INDEX -1 thing (pointed out by @fthiella and @Kickstart), so updated the examples below
Solved by creating a stored function which is able to be used in-line. The stored function is able to accept an input string, any single-character delimiter, and an index of the desired item to extract.
The stored function definition is as follows
CREATE DEFINER = `user`@`%`
FUNCTION `getListElement`(
inString VARCHAR(255) ,
inDelimiter CHAR(1) ,
inIndexToReturn TINYINT UNSIGNED
) RETURNS varchar(255) READS SQL DATA DETERMINISTIC SQL SECURITY INVOKER
BEGIN
-- Takes in as argument a string, and then breaks out the desired string
DECLARE resultString VARCHAR(255) DEFAULT inString;
DECLARE numberOfListElements TINYINT UNSIGNED DEFAULT 0;
DECLARE errorMessage VARCHAR(255) DEFAULT 'Requested index was < 1 which was invalid';
-- First of all, additional processing only needed for element 2 upwards
IF inIndexToReturn = 1 THEN
RETURN SUBSTRING_INDEX( resultString , inDelimiter , inIndexToReturn);
ELSEIF inIndexToReturn > 1 THEN
-- Count the number of elements
-- This will count the missing delimiters based off the replace. A list of 4 will be missing 3 characters.
SET numberOfListElements = ( CHAR_LENGTH( resultString ) + 1 ) - CHAR_LENGTH( REPLACE( resultString , inDelimiter , '' ) );
IF numberOfListElements >= inIndexToReturn THEN
-- Make sure to only return the last of the elements returend by the first SUBSTRING_INDEX
RETURN SUBSTRING_INDEX( SUBSTRING_INDEX( inString , inDelimiter , inIndexToReturn ) , inDelimiter , -1 );
END IF;
SET errorMessage = CONCAT('List index ',inIndexToReturn,' was requested from a list with ',numberOfListElements,' elements');
END IF;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = errorMessage;
END
In the examples above, the following could be used to extract specific list elements
SELECT getListElement( '1,2,3,4,5' , ',' , 2 )
returns 2
SELECT getListElement( REPLACE( 'word1 word2 word3' ,' ', ',' ) , ',' , 3 )
returns word3
(see note below for reason on the REPLACE
)
SELECT getListElement( 'pipe|delimited|list' , '|' , 1 )
returns pipe
It would also be possible to use this within an iterator to loop over elements in a list. First of all you need to count the items in the list (see How to count items in comma separated list MySQL) however once you have that, it's just a matter of iterating over them as in this fragment from a stored procedure
-- Reinitialise variables
SET @list = '1,2,3,4,5';
SET @delimiter = ',';
SET @listLength = (CHAR_LENGTH( @list ) + 1 ) - CHAR_LENGTH( REPLACE( @list , @delimiter , '' ) );
SET @currentElement = 1;
listLoop: REPEAT
-- Select the list element and do something with it
SELECT getListElement( @list , @delimiter , @currentElement );
-- Increment the current element
SET @currentElement = @currentElement + 1;
UNTIL @currentElement > @listLength
END REPEAT listLoop;
important space-delimited lists seem to cause issues for this procedure, and so prior to parsing a string into the function I would recommend doing a simple REPLACE
to replace the spaces with another suitable single-character separator (i.e. ,
or |
depending on the content in the string)