66

In MySQL, I want to be able to search for '31 - 7', when another value = '7 - 31'. What is the syntax that I would use to break apart strings in MySQL? In PHP, I would probably use explode(' - ',$string) and put them together. Is there a way to do this in MySQL?

Background: I'm working with sports scores and want to try games where the scores are the same (and also on the same date) - the listed score for each team is backwards compare to their opponent's database record.

The ideal MySQL call would be:

Where opponent1.date  = opponent2.date
  AND opponent1.score = opponent2.score

(opponent2.score would need to be opponent1.score backwards).

Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
Bob Cavezza
  • 2,810
  • 7
  • 38
  • 56

12 Answers12

102

MYSQL has no explode() like function built in. But you can easily add similar function to your DB and then use it from php queries. That function will look like:

CREATE FUNCTION SPLIT_STRING(str VARCHAR(255), delim VARCHAR(12), pos INT)
RETURNS VARCHAR(255)
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(str, delim, pos),
       CHAR_LENGTH(SUBSTRING_INDEX(str, delim, pos-1)) + 1),
       delim, '');

Usage:

SELECT SPLIT_STRING('apple, pear, melon', ',', 1)

The example above will return apple. I think that it will be impossible to return array in MySQL so you must specify which occurrence to return explicitly in pos. Let me know if you succeed using it.

Arman P.
  • 4,314
  • 2
  • 29
  • 47
  • The delimiter parameter datatype size VARCHAR(12) should be larger than 12 so it does not produce unpredictable results with larger delimiters. – doc_id Mar 23 '14 at 03:22
  • 2
    I need to declare this function as DETERMINISTIC in one of my servers. Like here: RETURNS VARCHAR(255) DETERMINISTIC RETURN – nerkn Apr 25 '14 at 12:57
  • Use: SET GLOBAL log_bin_trust_function_creators = 1; before execute above query, if you got "This function has none of DETERMINISTIC" error. – prashant Dec 12 '19 at 04:55
  • 2
    Nice function, but you should [replace `LENGTH()` with `CHAR_LENGTH()`](https://stackoverflow.com/questions/28816726/mysql-function-to-split-strings-by-delimiter-doenst-work-with-polish-special-ch), if you work with multi-byte character sets. – Boolean_Type Oct 05 '20 at 11:31
  • @Boolean_Type Thanks, I haven't taken into account unicode characters/symbols that take variable length. – Arman P. Jan 05 '21 at 14:41
82

I try with SUBSTRING_INDEX(string,delimiter,count)

mysql> SELECT SUBSTRING_INDEX('www.mysql.com', '.', 2);
-> 'www.mysql'

mysql> SELECT SUBSTRING_INDEX('www.mysql.com', '.', -2);
-> 'mysql.com'

see more on mysql.com http://dev.mysql.com/doc/refman/5.1/en/string-functions.html#function_substring-index

deW1
  • 5,562
  • 10
  • 38
  • 54
madde74
  • 829
  • 6
  • 3
34

You can use stored procedure in this way..

DELIMITER |

CREATE PROCEDURE explode( pDelim VARCHAR(32), pStr TEXT)                                
BEGIN                                
  DROP TABLE IF EXISTS temp_explode;                                
  CREATE TEMPORARY TABLE temp_explode (id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, word VARCHAR(40));                                
  SET @sql := CONCAT('INSERT INTO temp_explode (word) VALUES (', REPLACE(QUOTE(pStr), pDelim, '\'), (\''), ')');                                
  PREPARE myStmt FROM @sql;                                
  EXECUTE myStmt;                                
END |   

DELIMITER ;
  • example call:

     SET @str  = "The quick brown fox jumped over the lazy dog"; 
     SET @delim = " "; 
    
    CALL explode(@delim,@str);
    SELECT id,word FROM temp_explode;
    
Disha Goyal
  • 624
  • 12
  • 22
18

First of all you should change database structure - the score in this case is some kind of composite value and should be stored in two columns, eg. score_host, score_guest.


MySQL doesn't provide explode() equivalent however in this case you could use SUBSTRING() and LOCATE() to cut off score of a host and a guest.

SELECT 
   CONVERT(SUBSTRING(score, 1, LOCATE('-',score) - 2) USING INTEGER) as score_host,
   CONVERT(SUBSTRING(score, LOCATE('-',score)+2) USING INTEGER) as score_guest
FROM ...;

CONVERT() is used to cast a string "23" into number 23.

toraman
  • 598
  • 4
  • 14
Crozin
  • 43,890
  • 13
  • 88
  • 135
10

Use this function. It works like a charm.

Replace | with the char to explode/split and the values 1, 2, 3, etc… are based on the number of entries in the data-set: Value_ONE|Value_TWO|Value_THREE.

SUBSTRING_INDEX(SUBSTRING_INDEX(`tblNAME`.`tblFIELD`, '|', 1), '|', -1) AS PSI,
SUBSTRING_INDEX(SUBSTRING_INDEX(`tblNAME`.`tblFIELD`, '|', 2), '|', -1) AS GPM,
SUBSTRING_INDEX(SUBSTRING_INDEX(`tblNAME`.`tblFIELD`, '|', 3), '|', -1) AS LIQUID

I hope this helps.

Giacomo1968
  • 25,759
  • 11
  • 71
  • 103
Sergio Rodriguez
  • 8,258
  • 3
  • 18
  • 25
  • How could you get the number of items in the set? – Lucien Jun 19 '16 at 10:36
  • Unfortunately not, you need to know the number of values before hand. In my case I know there are three values I need and I rename them PSI,GPM, and LIQUID. if one of the the values in the data set is empty, SQL returns an empty value without giving errors. This way I always print/echo $return-> PSI, $return->GPM, and $return->LIQUID – Sergio Rodriguez Jun 19 '16 at 11:06
  • 1
    @Lolums You can count occurrences of the separator: `ROUND ( (CHAR_LENGTH(`tblNAME`.`tblFIELD`) - CHAR_LENGTH(REPLACE ( `tblNAME`.`tblFIELD`, "|", "") ) ) / CHAR_LENGTH("|"))` Zero= length is one item (no separator), One= there are two items, etc – FrancescoMM Apr 28 '17 at 08:24
4

use substring_index, in the example below i have created a table with column score1 and score2, score1 has 3-7, score2 7-3 etc as shown in the image. The below query is able to split using "-" and reverse the order of score2 and compare to score1

SELECT CONCAT(
  SUBSTRING_INDEX(score1, '-', 1),
  SUBSTRING_INDEX(score1,'-',-1)
) AS my_score1,
CONCAT(
  SUBSTRING_INDEX(score2, '-', -1),
  SUBSTRING_INDEX(score2, '-', 1)
) AS my_score2
FROM test HAVING my_score1=my_score2

scores table

query results

Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
philip mudenyo
  • 714
  • 8
  • 8
3

This is actually a modified version of the selected answer in order to support Unicode characters but I don't have enough reputation to comment there.

CREATE FUNCTION SPLIT_STRING(str VARCHAR(255) CHARSET utf8, delim VARCHAR(12), pos INT) RETURNS varchar(255) CHARSET utf8
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(str, delim, pos),
       CHAR_LENGTH(SUBSTRING_INDEX(str, delim, pos-1)) + 1),
       delim, '')

The modifications are the following:

  • The first parameter is set as utf8
  • The function is set to return utf8
  • The code uses CHAR_LENGTH() instead of LENGTH() to calculate the character length and not the byte length.
vensires
  • 43
  • 4
2

As @arman-p pointed out MYSQL has no explode(). However, I think the solution presented in much more complicated than it needs to be. To do a quick check when you are given a comma delimited list string (e.g, list of the table keys to look for) you do:

SELECT 
    table_key, field_1, field_2, field_3
FROM
    my_table
WHERE
    field_3 = 'my_field_3_value'
    AND (comma_list = table_key
        OR comma_list LIKE CONCAT(table_key, ',%')
        OR comma_list LIKE CONCAT('%,', table_key, ',%')
        OR comma_list LIKE CONCAT('%,', table_key))

This assumes that you need to also check field_3 on the table too. If you do not need it, do not add that condition.

Al Zziwa
  • 1,127
  • 9
  • 5
1

if explode is used together with foreach to build a new string you can simulate explode by using a while loop like this:

CREATE FUNCTION explode_and_loop(sep VARCHAR(),inputstring VARCHAR()) RETURNS VARCHAR() 
BEGIN
    DECLARE part,returnstring VARCHAR();
    DECLARE cnt,partsCnt INT();
    SET returnstring = '';
    SET partsCnt = ((LENGTH(inputstring ) - LENGTH(REPLACE(inputstring,sep,''))) DIV LENGTH(sep);
    SET cnt = 0;
    WHILE cnt <= partsCnt DO
        SET cnt = cnt + 1;
        SET part = SUBSTRING_INDEX(SUBSTRING_INDEX(inputstring ,sep,cnt),sep,-1);
        -- DO SOMETHING with the part eg make html:
        SET returnstring = CONCAT(returnstring,'<li>',part,'</li>')
    END WHILE;
    RETURN returnstring;
END

this example will return a html list of the parts. (required variable legths have to be added)

ohjay
  • 11
  • 2
1

I believe your problem can be solved in other simpler ways. But to answer your question, you can leverage the JSON functions in order to "explode" a string.

In your particular case, the strings '7 - 31' and '31 - 7' can be reformatted into a valid JSON string combining functions CONCAT() and REPLACE():

SELECT CONCAT('["',REPLACE('31 - 7',' - ','","'),'"]') score

The output will look like this:

["31","7"]

This is a valid JSON string.

The next step is to use the JSON_VALUE() function to extract each opponents score.

SELECT JSON_VALUE(CONCAT('["',REPLACE('31 - 7',' - ','","'),'"]'),'$[0]') oponent1, JSON_VALUE(CONCAT('["',REPLACE('31 - 7',' - ','","'),'"]'),'$[1]') oponent2

The result will look like this:

+-----------+----------+
| oponent1  | oponent2 |
+-----------+----------+
|       31  |        7 |
+-----------+----------+

Finally, you can manipulate these values as you please. Perhaps you wish to reorder them, placing the least score first so that the score is always a standard format instead of being dependent on who the home team may be.

dailabala
  • 31
  • 2
0

I faced same issue today and resolved it like below, please note in my case, I know the number of items in the concatenated string, hence I can recover them this way:

set @var1=0;
set @var2=0;
SELECT SUBSTRING_INDEX('value1,value2', ',', 1) into @var1;
SELECT SUBSTRING_INDEX('value1,value2', ',', -1) into @var2;

variables @var1 and @var2 would have the values similar to explode().

DoubleA
  • 82
  • 5
0

I'm not sure if this is fully answering the question (it isn't), but it's the solution I came up with for my very similar problem. I know some of the other solutions look shorter but they seem to use SUBSTRING_INDEX() way more than necessary. Here I try to just use LOCATE() just once per delimiter.

-- *****************************************************************************
-- test_PVreplace

DROP FUNCTION IF EXISTS test_PVreplace;

delimiter //
CREATE FUNCTION test_PVreplace (
   str TEXT,   -- String to do search'n'replace on
   pv TEXT     -- Parameter/value pairs 'p1=v1|p2=v2|p3=v3'
   )
   RETURNS TEXT

-- Replace specific tags with specific values.

sproc:BEGIN
   DECLARE idx INT;
   DECLARE idx0 INT DEFAULT 1;   -- 1-origined, not 0-origined
   DECLARE len INT;
   DECLARE sPV TEXT;
   DECLARE iPV INT;
   DECLARE sP TEXT;
   DECLARE sV TEXT;

   -- P/V string *must* end with a delimiter.

   IF (RIGHT (pv, 1) <> '|') THEN
      SET pv = CONCAT (pv, '|');
      END IF;

   -- Find all the P/V pairs.

   SELECT LOCATE ('|', pv, idx0) INTO idx;
   WHILE (idx > 0) DO
      SET len = idx - idx0;
      SELECT SUBSTRING(pv, idx0, len) INTO sPV;

      -- Found a P/V pair.  Break it up.

      SELECT LOCATE ('=', sPV) INTO iPV;
      IF (iPV = 0) THEN
         SET sP = sPV;
         SET sV = '';
      ELSE
         SELECT SUBSTRING(sPV, 1, iPV-1) INTO sP;
         SELECT SUBSTRING(sPV, iPV+1) INTO sV;
         END IF;

      -- Do the substitution(s).

      SELECT REPLACE (str, sP, sV) INTO str;

      -- Do next P/V pair.

      SET idx0 = idx + 1;
      SELECT LOCATE ('|', pv, idx0) INTO idx;
      END WHILE;
   RETURN (str);
END//
delimiter ;

SELECT test_PVreplace ('%one% %two% %three%', '%one%=1|%two%=2|%three%=3');
SELECT test_PVreplace ('%one% %two% %three%', '%one%=I|%two%=II|%three%=III');
SELECT test_PVreplace ('%one% %two% %three% - %one% %two% %three%', '%one%=I|%two%=II|%three%=III');
SELECT test_PVreplace ('%one% %two% %three% - %one% %two% %three%', '');
SELECT test_PVreplace ('%one% %two% %three% - %one% %two% %three%', NULL);
SELECT test_PVreplace ('%one% %two% %three%', '%one%=%two%|%two%=%three%|%three%=III');
Alan Stewart
  • 93
  • 2
  • 7