26

I want to split a comma-separated string in Category column :

Category
Auto,A,1234444
Auto,B,2345444
Electronincs,Computer,33443434

I want to get only a single value from above string:

value1: Auto
value2: A
value3: 1234444

I found how using Replace() and Trim(). However, I want an easier approach. In SQL there is SubString() but not in SQLite. substr() can set a maximum length but my string doesn't have fixed length.

user4157124
  • 2,809
  • 13
  • 27
  • 42
dipali
  • 10,966
  • 5
  • 25
  • 51
  • @dipali is it possible to get the whole `String` first, then process it in Java? (using `String.split(",")`) – Andrew T. Jun 17 '14 at 08:28
  • @AndrewT. its not possible.my query is not enough for this.i have already create 10 line query and i just put this question solution there..please help me. – dipali Jun 17 '14 at 08:30
  • if you only have to split into two columns https://www.pauldesalvo.com/split-a-column-with-a-delimiter-in-sqlite/ shows a viable answer for three columns https://stackoverflow.com/questions/43443919/split-one-column-into-three-columns-based-on-a-delimiter – Wolfgang Fahl Mar 08 '22 at 06:13

5 Answers5

44

You can use a Common Table Expression to split comma separated values in SQLite.

WITH split(word, csv) AS (
  -- 'initial query' (see SQLite docs linked above)
  SELECT 
    -- in final WHERE, we filter raw csv (1st row) and terminal ',' (last row)
    '', 
    -- here you can SELECT FROM e.g. another table: col_name||',' FROM X
    'Auto,A,1234444'||',' -- terminate with ',' indicating csv ending
  -- 'recursive query'
  UNION ALL SELECT
    substr(csv, 0, instr(csv, ',')), -- each word contains text up to next ','
    substr(csv, instr(csv, ',') + 1) -- next recursion parses csv after this ','
  FROM split -- recurse
  WHERE csv != '' -- break recursion once no more csv words exist
) SELECT word FROM split 
WHERE word!=''; -- filter out 1st/last rows

Output is as expected:

Auto
A
1234444
ecoe
  • 4,994
  • 7
  • 54
  • 72
user1461607
  • 2,416
  • 1
  • 25
  • 23
  • how can this query be modified to work on a simple table like `CREATE TABLE tablename (id INT,name VARCHAR(20));` where name has comma separated values? – indago Jan 11 '16 at 08:47
  • @indago: Create a view that selects id and the split name? Is this what you mean? – MaciekS Jan 20 '16 at 13:41
  • It has problem when only one item in the string, for example `trim('Auto', ',')`, there is no output. – tony.0919 Apr 03 '16 at 07:38
  • 1
    We can modify the `substr` part into `substr(str, 0, case when instr(str, ',') then instr(str, ',') else length(str)+1 end)`, in order to print the string without comma delimiter. – tony.0919 Apr 03 '16 at 08:12
  • How to use it on a column after a select? – nowox Apr 30 '17 at 08:39
  • 1
    This might be clearer if you added the keyword `recursive` to show how it's splitting each row of `split` till it can't split any more. – Noumenon Mar 14 '19 at 23:01
  • Great example! The final SELECT slightly confused me by making me wonder how an empty string got inserted in the 'word' column. Finally, I concluded it didn't, and the final SELECT could have just been `SELECT word FROM split`. – Ron Burk Dec 23 '19 at 02:56
  • "split" keyword may not work with the pre-lollipop android OS sqlite version. As I checked with the samsung galaxy s3 device running on android OS 4.3, was throwing error and unable to execute query that contains "split" keyword. – Dhaval Shah Jun 22 '20 at 06:47
  • In addition to above comment, error is thrown in android os 4.3 `Caused by: android.database.sqlite.SQLiteException: near "split":` – Dhaval Shah Jun 22 '20 at 07:03
  • @DhavalShah use the custom [SQLite android bindings](https://sqlite.org/android/doc/trunk/www/index.wiki) which will have the latest SQLite features. – user1461607 Dec 14 '20 at 22:04
  • the answer shows how to handle a row not a table. – Wolfgang Fahl Mar 08 '22 at 06:11
  • @WolfgangFahl see my edit, showing an example handling a table in comments (`col_name||',' FROM X` instead of `'Auto,A,1234444'||','`). The latter (single hard-coded row) just makes it easy to paste into any SQLite client to get an instantly working example – ecoe Jun 10 '22 at 18:55
7

SQLite provide functions for this purpose, e.g. to get substring substr('your string', start_position, end_position), to get position of a specific character in a string instr('22:string', ':'), and to get length of a string length('string'). Now let see the following examples:

select substr('22:khan', x, y);
returns a string starting at x and ends with y;
select substr('22:khan', 0, instr('22:khan',':'));
returns: 22 
select substr('22:khan', instr('22:khan',':')+1, length('22:khan'));
returns: khan
select substr('22:khan',instr('22:khan',':'), length('22:khan'));
returns: :khan
select substr('Noor,Khan', 0, instr('Noor,Khan', ','));
returns: Noor
select substr('Noor,Khan', instr('Noor,Khan', ',')+1, length('Noor,Khan'));
returns: Khan

for more info visit: https://www.sqlite.org/lang_corefunc.html

Noor Khan
  • 151
  • 1
  • 9
  • unfortunately, `instr` only finds the first instance of the separateor character. suppose you had the strings "asdfa:bfab:zarva"... how would you find the third colon delimited word? – Michael Feb 09 '21 at 20:50
  • @Michael use the position of the first match + 1 as the starting position of the next substring, and repeat – elig Nov 17 '21 at 21:24
5

I like the answer from @user1461607 except: it seems to me the SQLite documentation warns against assuming any particular order from a SELECT, both in the general case, and in the specific case of a recursive SELECT. Here, I modified that answer to add an ordering column in a manner that I think SQLite guarantees to work.

I also cosmetically changed the example from a comma-separated list to a path, to suggest there are cases where you really need to process things in a particular order. This example also prints out all the columns from the temporary table so it's slightly easier to see what went on. AFAICT, a CTE in SQLite does not have the usual ROWID column, so it seems like adding some ordering column yourself really is required to sleep soundly at night.

WITH RECURSIVE split(seq, word, str) AS (
    SELECT 0, '/', 'home/ronburk/layers/branch'||'/'
    UNION ALL SELECT
        seq+1,
        substr(str, 0, instr(str, '/')),
        substr(str, instr(str, '/')+1)
    FROM split WHERE str != ''
) SELECT * FROM split ORDER BY split.seq ASC;
Ron Burk
  • 6,058
  • 1
  • 18
  • 20
  • "split" keyword may not work with the pre-lollipop android OS sqlite version. As I checked with the samsung galaxy s3 device running on android OS 4.3, was throwing error and unable to execute query that contains "split" keyword. Error: `Caused by: android.database.sqlite.SQLiteException: near "split"` – Dhaval Shah Jun 22 '20 at 07:06
1

This variation of the answer provided by @user1461607 ensures that any CSV values that happen to be empty strings are preserved:

WITH RECURSIVE split(value, str) AS (
      SELECT null, 'Auto,A,1234444' || ','  -- the string to be split 
      UNION ALL
      SELECT
      substr(str, 0, instr(str, ',')),
      substr(str, instr(str, ',')+1)
      FROM split WHERE str!=''
  ) SELECT value FROM split WHERE value is not NULL;

Converting a CSV line to a JSON array

Assuming the JSON1 extension has been loaded, you could use json_group_array(value) in the last line to convert the CSV to a JSON array of strings.

peak
  • 105,803
  • 17
  • 152
  • 177
  • "split" keyword may not work with the pre-lollipop android OS sqlite version. As I checked with the samsung galaxy s3 device running on android OS 4.3, was throwing error and unable to execute query that contains "split" keyword. Error: `Caused by: android.database.sqlite.SQLiteException: near "split"` – Dhaval Shah Jun 22 '20 at 07:06
  • I just had to some small changes to better suit my needs, but this is working perfectly! Thanks. – ftani Sep 14 '22 at 14:06
1

Late to the party, but I found an extension which replicates the behaviour of the SPLIT_PART function of PostgreSQL

SELECT split_part(category, ',', 3) FROM ...

https://github.com/nalgeon/sqlean/blob/main/docs/text.md#split_partsource-sep-part

psimms
  • 180
  • 2
  • 9