I have a problem to build a query. After searching on stackoverflow i think i need a crosstab. But i don't know what exactly is this. I think it would be quicker if i show you a simplified version of my problem. I would be glad if you could point me to right direction.
This is example of data:
ColumnIDToGroup Value
-----------------------
1 AAAA
1 BBBB
2 AAAA
2 BBBB
2 CCCC
I need to build a query to get the data in this format:
ColumnIDToGroup Value1 Value2 Value3 Value4 Value5
------------------------------------------------------------
1 AAAA BBBB 'Empty' 'Empty' 'Empty'
2 AAAA BBBB CCCC 'Empty' 'Empty'
As a workarround, I could accept this output, if it is simple to build (when a value is not null, it always have the same size)
ColumnIDToGroup ValueConcat
-------------------------------------
1 AAAABBBB************
2 AAAABBBBCCCC********
SAMPLE TABLE
CREATE TABLE #TEMP(ColumnIDToGroup INT,Value VARCHAR(30))
INSERT INTO #TEMP
SELECT 1, 'AAAA'
UNION ALL
SELECT 1, 'BBBB'
UNION ALL
SELECT 2, 'AAAA'
UNION ALL
SELECT 2, 'BBBB'
UNION ALL
SELECT 2, 'CCCC'
Now select the rows from the table, create a column for Value1, Value2 etc and select the extra columns for Value4,Value5 by UNION all and set text to Empty.
SELECT *,
'Value'+CAST(ROW_NUMBER() OVER(PARTITION BY ColumnIDToGroup ORDER BY VALUE)AS VARCHAR(3)) COL
INTO #NEWTABLE
FROM #TEMP
UNION ALL
SELECT 1,'Empty','Value3'
UNION ALL
SELECT 2,'Empty','Value3'
UNION ALL
SELECT 1,'Empty','Value4'
UNION ALL
SELECT 2,'Empty','Value4'
UNION ALL
SELECT 1,'Empty','Value5'
UNION ALL
SELECT 2,'Empty','Value5'
Now get the columns for pivot
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + COL + ']', '[' + COL + ']')
FROM (SELECT DISTINCT COL FROM #NEWTABLE) PV
ORDER BY COL
Now pivot the query
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT * FROM
(
SELECT *
FROM #NEWTABLE
) x
PIVOT
(
MIN(VALUE)
FOR COL IN (' + #cols + ')
) p
ORDER BY ColumnIDToGroup;'
EXEC SP_EXECUTESQL #query
Click here to view the result
If you want in the second specified format, you can use the below query
;WITH CTE AS
(
SELECT *
FROM #TEMP
UNION ALL
SELECT 1,'****'
UNION ALL
SELECT 2,'****'
UNION ALL
SELECT 1,'****'
UNION ALL
SELECT 2,'****'
UNION ALL
SELECT 1,'****'
UNION ALL
SELECT 2,'****'
UNION ALL
SELECT 1,'****'
UNION ALL
SELECT 1,'***'
)
SELECT DISTINCT C2.ColumnIDToGroup,
-- Convert to single row for each ColumnIDToGroup
SUBSTRING(
(SELECT ' ' + CTE.VALUE
FROM CTE
WHERE C2.ColumnIDToGroup=ColumnIDToGroup
ORDER BY
CASE WHEN CTE.VALUE = 'AAAA' THEN 1
WHEN CTE.VALUE = 'BBBB' THEN 2
WHEN CTE.VALUE = 'CCCC' THEN 3
ELSE 4
END
FOR XML PATH('')),2,200000) ValueConcat
FROM CTE C2
Click here to view result
Related
wondering if anyone can help me convert the following Excel formula to MySQL query:
Excel: =PRODUCT(1+A1:A7)-1
MySQL data:
id | data
---------------
1 | -1.64
2 | 1.38
3 | 0
4 | 0
5 | -1.52
6 | 0
7 | -1.78
Result should equal -0.207936
This is how you can do it. I sure hope someone can improve on it cause it is awful.
with data(id, val) as(
select 1,-1.64 from dual union all
select 2,1.38 from dual union all
select 3,0.00 from dual union all
select 4,0.00 from dual union all
select 5,-1.52 from dual union all
select 6,0.00 from dual union all
select 7,-1.78 from dual
),
products as(
select Replace(LISTAGG(val+1, '*') within group (order by val),',','.') chain
from data)
SELECT o.val-1 AS product_value
FROM products p
OUTER APPLY(
SELECT *
FROM XMLTABLE('/ROWSET/ROW/*'
passing dbms_xmlgen.getXMLType('SELECT ' || p.chain || ' FROM dual')
COLUMNS val NUMBER PATH '.')
WHERE p.chain IS NOT NULL
) o;
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=c12284b5165da61de8ed418cdf1d6cd7
a prettier solution
with data(id, val) as(
select 1,-1.64 from dual union all
select 2,1.38 from dual union all
select 3,0.00 from dual union all
select 4,0.00 from dual union all
select 5,-1.52 from dual union all
select 6,0.00 from dual union all
select 7,-1.78 from dual
),
neg(val , modifier) as(
select exp(sum(ln(abs(val+1)))), case when mod(count(*),2) = 0 then 1 Else -1 end
from data
where val+1 <0
)
,
pos(val) as (
select exp(sum(ln(val+1)))
from data
where val+1 >=0
)
select (select val*modifier from neg)*(select val from pos)-1 from dual
I am new in SQL basically i required Sum result in separate columns as per different elements.
Please find my table and required result in below image.
Please guide me with regards to SQL query which give me the required result.
You can use conditional aggregation:
select payment,
sum(case when product = 'A' then amount else 0 end) as a,
sum(case when product = 'B' then amount else 0 end) as b,
sum(case when product = 'C' then amount else 0 end) as c,
sum(case when product = 'D' then amount else 0 end) as d,
sum(amount) as total
from t
group by payment;
You can use PIVOT to get the desired result
SELECT * ,
[A] + [B] + [C] + [D] AS Total
FROM ( SELECT Payment, Product, Amount FROM Your_Table) tr
PIVOT( SUM(Amount) FOR Product IN ( [A], [B], [C], [D] ) ) p;
You can use dynamic SQL pivot query if your products are not limited with A,B,C and D
Here is a sample query for SQL Server
DECLARE #products nvarchar(max)
SELECT #products =
STUFF(
(
select distinct ',[' + product + ']'
from Payments
for xml path('')
),
1,1,'')
declare #sql nvarchar(max)
set #sql = '
SELECT
*
FROM (
SELECT
Payment AS '' '',
Product,
Amount
from Payments
) Data
PIVOT (
SUM(Amount)
FOR Product
IN (
' + #products + '
)
) PivotTable'
exec sp_executesql #sql
Below is sample data
IF OBJECT_ID('tempdb..#t')IS NOT NULL
DROp TABLE #t
;with cte (Payment,Product,Amount)
AS
(
SELECT 'Cash','A',1 UNION ALL
SELECT 'Credit','B',2 UNION ALL
SELECT 'Credit','C',3 UNION ALL
SELECT 'Cash','D',5 UNION ALL
SELECT 'Cash','A',6 UNION ALL
SELECT 'Credit','B',23 UNION ALL
SELECT 'Credit','C',7 UNION ALL
SELECT 'Cash','D',11 UNION ALL
SELECT 'Cash','A',12 UNION ALL
SELECT 'Credit','B',14 UNION ALL
SELECT 'Credit','C',16 UNION ALL
SELECT 'Cash','D',26
)
SELECT * INTO #t FROM cte
Using Dynamic Sql
DECLARE #DyColumn Nvarchar(max),
#Sql Nvarchar(max),
#ISNULLDyColumn Nvarchar(max),
#SumCol Nvarchar(max)
SELECT #DyColumn=STUFF((SELECT DISTINCT ', '+QUOTENAME(Product) FROM #t FOR XML PATH ('')),1,1,'')
SELECT #ISNULLDyColumn=STUFF((SELECT DISTINCT ', '+'ISNULL('+QUOTENAME(Product)+',''0'')' +' AS '+QUOTENAME(Product) FROM #t FOR XML PATH ('')),1,1,'')
SELECT #SumCol=STUFF((SELECT DISTINCT ' + '+QUOTENAME(Product) FROM #t FOR XML PATH ('')),1,2,'')
SET #Sql='
SELECT *,('+#SumCol+') AS Total FROM
(
SELECT Payment,'+#ISNULLDyColumn+'
FROM
(
SELECT * FROM #t
)AS
SRC
PIVOT
(
SUM(AMOUNT) FOR Product IN ('+#DyColumn+')
) AS Pvt
)dt
'
PRINT #Sql
EXECUTE (#Sql)
Result
Payment A B C D Total
---------------------------------
Cash 19 0 0 42 61
Credit 0 39 26 0 65
Database Table
ID Post Tags
1 Range rover range-rover,cars
2 Lamborghini lamborghini,cars
3 Kawasaki kawasaki,bikes
4 Yamaha R1 yamaha,r1,bikes
I Want to Remove Duplicate Values from Result sql
What i Get When i fetch tags (tags are in ,) from Database
SELECT Tags from posts;
Resut:
range-rover,cars lamborghini,cars kawasaki,bikes yamaha,r1,bikes
What I Need is not to show same result again.
range-rover,cars lamborghini kawasaki,bikes yamaha,r1
You can split your text using tally table and SUBSTRING_INDEX:
SELECT DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(t.tags, ',', n.n), ',', -1) AS val
FROM posts t
CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
) n
WHERE n.n <= 1 + (LENGTH(t.tags) - LENGTH(REPLACE(t.tags, ',', '')))
SqlFiddleDemo
If you need one row add GROUP_CONCAT:
SELECT GROUP_CONCAT(DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(t.tags, ',', n.n), ',', -1)) AS val
...
SqlFiddleDemo2
Problem:
I want to test a set of values is equal to another set but not necessary their order of position will be same.
For example:
'a,b,c,d' must be equal to 'b,a,c,d'
What I have tried:
I have tried IN Clause and I have checked with FIND_IN_SET.
SELECT 'a,b,c,d' IN 'b,c,a,d';
Both of them can not do this work.
Will be thankful if anyone can help.
Thanks
Sandeep
This demonstrates the use the splitting of values to multiple rows, mentioned by GolezTrol in combination with FIND_IN_SET, modified to function to be used in forms like:
SELECT are_sets_equal(col_with_set, 'a,b,d,c') FROM example;
or
SELECT * FROM example
WHERE are_sets_equal(col_with_set, 'a,b,d,c')
The idea is this:
Split the the first set to a temporary table
Check how many of those values are found in the second set.
If this count is equal to the count of elements in both sets, then the sets are equal
The function will return 1, if both sets are equal and 0, if the sets differ as by requirement.
The limit for both sets is 1000 values, but could be expanded easily:
DELIMITER //
CREATE FUNCTION are_sets_equal(set_a VARCHAR(2000), set_b VARCHAR(2000)) RETURNS BOOLEAN
BEGIN
DECLARE is_equal BOOLEAN;
DECLARE count_a INT;
DECLARE count_b INT;
-- calculate the count of elements in both sets
SET count_a = 1 + LENGTH(set_a) - LENGTH(REPLACE(set_a, ',', ''));
SET count_b = 1 + LENGTH(set_b) - LENGTH(REPLACE(set_b, ',', ''));
SELECT
-- if all elements of the first set are contained in the second
-- set and both sets have the same number of elements then both
-- sets are considered equal
COUNT(t.value) = count_a AND count_a = count_b INTO is_equal
FROM (
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX(e.col, ',', n.n), ',', -1) value
FROM ( SELECT set_a AS col ) e
CROSS JOIN(
-- build for up to 1000 separated values
SELECT
a.N + b.N * 10 + c.N * 100 + 1 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
ORDER BY n
) n
WHERE n.n <= count_a
) t
WHERE FIND_IN_SET(t.value, set_b);
return is_equal;
END //
DELIMITER ;
Explanation
Building a numbers table
SELECT
a.N + b.N * 10 + c.N * 100 + 1 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
ORDER BY n
builds a number table with the values from 1 to 1000 on the fly. How to expand this to a greater range should be obvious.
Note Such a numbers table could be contained in your database, so there would be no need to create one on the fly.
Split a set to a table
With the help of this number table we can split the value list to a table, using nested SUBSTRING_INDEX calls to cut just one value after the other from the list as mentioned in SQL split values to multiple rows:
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX(t.col, ',', n.n), ',', -1) value
FROM (SELECT #set_a as col ) t CROSS JOIN (
-- build for up to 100 separated values
SELECT
a.N + b.N * 10 + c.N * 100 + 1 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
ORDER BY n
) n
WHERE
n <= 1 + LENGTH(#set_a) - LENGTH(REPLACE(#set_a, ',', ''))
Count the elements of the sets
We get the count of elements in the list by the expression in the WHERE clause: we have one more values than occurences of the separator.
Then we restrict the result by searching those values in the second set with FIND_IN_SET.
As a last step we check count of values in the result against the count of values in both sets and return this value.
Demo
Experiment with this demo.
FIND_IN_SET should do the trick, but the first value is an individual value and doesn't work right if it contains a comma. You would have to look for each individual value:
SELECT
FIND_IN_SET('a', 'b,c,a,d') AND
FIND_IN_SET('b', 'b,c,a,d') AND
FIND_IN_SET('c', 'b,c,a,d') AND
FIND_IN_SET('d', 'b,c,a,d')
If you don't have these separate values available, maybe you can split the input value into multiple values. The answers to the question 'Split values to multiple rows' might give you some inspiration.
The better solution would be not to store comma separated values at all. It's considered bad practice.
You can use a UDF(user defined function) to compare your sets,As from comments no duplicate values will be in the sets,I have little customized a UDF function provided by #Simon at mso.net.I have calculated a count of values in a second comma separated list and in the end compared with the matched result of find_in_set stored in numReturn variable,If both equal the return 1 else return 0 for non matched.Please not this will not work for repeated/duplicate values in a set
DELIMITER $$
DROP FUNCTION IF EXISTS `countMatchingElements`$$
CREATE DEFINER = `root` #`localhost` FUNCTION `countMatchingElements` (
inFirstList VARCHAR (1000),
inSecondList VARCHAR (1000)
) RETURNS TINYINT (3) UNSIGNED NO SQL DETERMINISTIC SQL SECURITY INVOKER
BEGIN
DECLARE numReturn TINYINT UNSIGNED DEFAULT 0 ;
DECLARE idsInFirstList TINYINT UNSIGNED ;
DECLARE currentListItem VARCHAR (255) DEFAULT '' ;
DECLARE currentID TINYINT UNSIGNED ;
DECLARE total_values_in_second INT DEFAULT 0 ;
SET total_values_in_second = ROUND(
(
LENGTH(inSecondList) - LENGTH(REPLACE (inSecondList, ',', ''))
) / LENGTH(',')
) + 1 ;
SET idsInFirstList = (CHAR_LENGTH(inFirstList) + 1) - CHAR_LENGTH(REPLACE(inFirstList, ',', '')) ;
SET currentID = 1 ;
-- Loop over inFirstList, and for each element that is in inSecondList increment numReturn
firstListLoop :
REPEAT
SET currentListItem = SUBSTRING_INDEX(
SUBSTRING_INDEX(inFirstList, ',', currentID),
',',
- 1
) ;
IF FIND_IN_SET(currentListItem, inSecondList)
THEN SET numReturn = numReturn + 1 ;
END IF ;
SET currentID = currentID + 1 ;
UNTIL currentID > idsInFirstList
END REPEAT firstListLoop ;
IF total_values_in_second = numReturn
THEN RETURN 1 ;
ELSE RETURN 0 ;
END IF ;
END $$
DELIMITER ;
Fiddle Demo
MySQL stored procedures can be used to split the string, the following details for your usage of MySQL stored procedures, for your reference learning purposes.
Existing string, such as Apple, banana, orange, pears, grape, it should follow the comma (,) is divided into:
apple
banana
orange
pears
grape
Where in () method can then query.
1, the specific function:
Function: func_split_TotalLength the
DELIMITER $ $
DROP function IF EXISTS `func_split_TotalLength` $ $
CREATE DEFINER = `root` # `%` FUNCTION `func_split_TotalLength`
(F_string varchar (1000), f_delimiter varchar (5)) RETURNS int (11)
BEGIN
return 1 + (length (f_string) - length (replace (f_string, f_delimiter,'')));
END $ $
DELIMITER;
Function: func_split
DELIMITER $ $
DROP function IF EXISTS `func_split` $ $
CREATE DEFINER = `root` # `%` FUNCTION `func_split`
(F_string varchar (1000), f_delimiter varchar (5), f_order int) RETURNS varchar (255) CHARSET utf8
BEGIN
declare result varchar (255) default '';
set result = reverse (substring_index (reverse (substring_index (f_string, f_delimiter, f_order)), f_delimiter, 1));
return result;
END $ $
DELIMITER;
Stored procedure: SplitString
DELIMITER $ $
DROP PROCEDURE IF EXISTS `splitString` $ $
CREATE Procedure the `SplitString`
(IN f_string varchar (1000), IN f_delimiter varchar (5))
BEGIN
declare cnt int default 0;
declare i int default 0;
set cnt = func_split_TotalLength (f_string, f_delimiter);
DROP TABLE IF EXISTS `tmp_split`;
create temporary table `tmp_split` (`status` varchar (128) not null) DEFAULT CHARSET = utf8;
while i <cnt
do
set i = i + 1;
insert into tmp_split (`status`) values ??(func_split (f_string, f_delimiter, i));
end while;
END $ $
DELIMITER;
2, the test will be successful segmentation
call splitString ("apple, banana, orange, pears, grape", ",");
select * from tmp_split;
The results are splitting success:
mysql> call splitString ("apple, banana, orange, pears, grape", ",");
select * from tmp_split;
Query OK, 1 row affected but i need to insert by column wise data...,please help me
Well I would use an other approach to this problem, to get the result in table form:
Simpler approach without a stored procedure
Assuming you have a table example with two columns: id and col1 like so:
CREATE TABLE example (
id INT NOT NULL PRIMARY KEY,
col1 VARCHAR(1000)
);
and those values
id | col
-----------------------------
1 | abcd,efgh,ijkl,ghjy,sdfg
2 | some other text
and you want to get the comma separated values from col into a column with one value at the time for the id = 1. Then I would use following sql:
SELECT
id,
SUBSTRING_INDEX(SUBSTRING_INDEX(e.col, ',', t.n), ',', -1) value
FROM example e CROSS JOIN (
SELECT
1 + a.N + b.N * 10 + c.N * 100 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
ORDER BY n
) t
WHERE
e.id = 1
AND
t.n <= (1 + LENGTH(e.col) - LENGTH(REPLACE(e.col, ',', '')));
It's the same procedure as in the stored procedure. You can use this result easily in joins or other operations.
See this Demo
First part assuming, a stored procedure was needed.
stored procedure
no other function needed
no explicit temp table
works up to 1000 items, easily exptensible
Code:
DROP procedure splitString;
DELIMITER $ $
CREATE procedure splitString(IN f_string VARCHAR(1000), IN f_delimiter VARCHAR(5))
BEGIN
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX(h.haystack, f_delimiter, t.n
), f_delimiter, -1) as value
FROM (SELECT f_string as haystack) h
CROSS JOIN (
SELECT
1 + a.N + b.N * 10 + c.N * 100 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
ORDER BY n
) t
WHERE t.n <=
(
1
+ (
CHAR_LENGTH(f_string)
-
CHAR_LENGTH(REPLACE(f_string, f_delimiter, ''))
)
/ CHAR_LENGTH(f_delimiter)
);
END;
$ $
DELIMITER ;
Explanation
1. Generating a list of numbers from 1 to 1000
The most inner SELECT
SELECT
1 + a.N + b.N * 10 + c.N * 100 AS n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
ORDER BY n
does not more than creating a list with the numbers from 1 to 1000. If you need more then you should see, how it's done. Add a line with those union all and call this d, add + d.N * 1000 to the sum.
2. Performing the split and getting just one element per row
We use this list to do the split of your func_split in the form of (a bit more readable statements)
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX(h.haystack, f_delimiter, 1
), f_delimiter, -1) as value
FROM (SELECT f_string as haystack) h
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX(h.haystack, f_delimiter, 2
), f_delimiter, -1) as value
FROM (SELECT f_string as haystack) h
SELECT SUBSTRING_INDEX(
SUBSTRING_INDEX(h.haystack, f_delimiter, 3
), f_delimiter, -1) as value
FROM (SELECT f_string as haystack) h
that extract the first, second, third, ... substring, separated with the f_delimiter.
Getting the number of elements
Our WHERE clause is all but self-explanatory:
-- because we begin by 1 we've got to include the upper limit
WHERE t.n <=
(
1 -- we have one comma less than parts
+ (
-- count how many characters (not bytes) we lose by the replace operation
CHAR_LENGTH(f_string)
-
CHAR_LENGTH(REPLACE(f_string, f_delimiter, ''))
)
-- and divide this by the count of characters in the delimiter string
/ CHAR_LENGTH(f_delimiter)
);
Note
LENGTH() instead of CHAR_LENGTH() would do too, because the result of the division would be the same, but I like to do it right :-)
** Check of function**
You can check the result with
CALL splitString('apple, bananas, grape, ananas, pears', ', ');
it returns
value
-----
apple
bananas
grape
ananas
pears