Laravel Query Builder raw query with two FROM statements - mysql

my apologies if this question sounds a little boring for someone I really turn here because I have already tried to solve my problem in different ways and I have not been able to obtain good results.
I am trying to convert a MySQL query to Laravel Query Builder, I show you the query:
SELECT DAY(AAA.fecha_hora_entrada) as DAY, IFNULL(BBB.SALES, 0) SALES, IFNULL(BBB.NET, 0) NET
FROM (
SELECT fecha_hora_entrada
FROM (
SELECT MAKEDATE(YEAR(NOW()), 1) +
INTERVAL (MONTH(NOW()) - 1) MONTH +
INTERVAL daynum DAY fecha_hora_entrada
FROM (
SELECT t * 10 + u daynum
FROM (SELECT 0 t UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) A,
(SELECT 0 u UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) B
ORDER BY daynum
) AS AA
) AS AA
WHERE MONTH(fecha_hora_entrada) = MONTH(NOW())
) AS AAA
LEFT JOIN (SELECT (DATE(fecha_hora_entrada)) AS fecha_hora_entrada, (SUM(neto)) AS NET, (COUNT(neto)) AS SALES
FROM t_derivados
GROUP BY DATE(fecha_hora_entrada)) BBB
ON AAA.fecha_hora_entrada = BBB.fecha_hora_entrada;
What I have tried so far is the following. So far I have managed to pass part of the MySQL query to Query Builder
public function salesYear()
{
return DB::table(DB::table(DB::table(DB::table(DB::table(null, null)
->selectRaw('0 t UNION SELECT 2 UNION SELECT 3'),'A')
->orderBy('daynum')
->selectRaw('t * 10 + u daynum'), 'AA')
->selectRaw('MAKEDATE(YEAR(NOW()), 1) + INTERVAL (MONTH(NOW()) - 1) MONTH + INTERVAL daynum DAY fecha_hora_entrada'), 'AA')
->addSelect('fecha_hora_entrada')
->whereRaw('MONTH(fecha_hora_entrada) = MONTH(NOW())'), 'AAA')
->selectSub('DAY(AAA.fecha_hora_entrada)', 'DAY')
->selectSub('IFNULL(BBB.SALES, 0)', 'SALES')
->selectSub('IFNULL(BBB.NET, 0)', 'NET')
->leftJoinSub(DB::table('t_derivados')
->selectSub('DATE(fecha_hora_entrada)', 'fecha_hora_entrada')
->selectSub('SUM(neto)', 'NET')
->selectSub('COUNT(neto)', 'SALES')
->groupBy(DB::raw('DATE(fecha_hora_entrada)')), 'BBB', 'AAA.fecha_hora_entrada', 'BBB.fecha_hora_entrada')
->toSql();
}
I know it's a bit boring to read everything .. but let's focus on the next sub-sentence:
SELECT t * 10 + u daynum
FROM (SELECT 0 t UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) A,
(SELECT 0 u UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) B
ORDER BY daynum
...
The truth is I have not found a way to add the second FROM to Query Builder
SELECT 0 u UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) B
...
Someone expert on the subject could help me please. Thank you very much in advance

Related

Insert unique random number with recursive MySQL

I want to populate a table with unique random numbers without using a procedure.
I've tried using this reply to do it but not success.
What I'm trying to do is something like this but validating that numbers are not repeated:
INSERT into table (row1,row2)
WITH RECURSIVE
cte AS ( SELECT 0 num, LPAD(FLOOR(RAND() * 99999999),8,0) random_num
UNION ALL
SELECT num+1, LPAD(FLOOR(RAND() * 99999999),8,0) random_num
FROM cte WHERE num < 100000-1)
SELECT random_num, null
FROM cte;
In the example above, I am able to generate random values and insert them but without validating that the numbers are not repeated.
I have tried to do this:
INSERT into table (row1,row2)
WITH RECURSIVE
cte AS ( SELECT 0 num, LPAD(FLOOR(RAND() * 99999999),8,0) random_num
UNION ALL
SELECT num+1, LPAD(FLOOR(RAND() * 99999999),8,0) random_num
FROM cte WHERE num < 100000-1 AND random_num NOT IN (SELECT random_num FROM cte WHERE random_num IS NOT NULL))
SELECT random_num, null
FROM cte;
but the condition AND random_num NOT IN (SELECT random_num FROM cte WHERE random_num IS NOT NULL) in the where case, causes an SQL Error [4008] [HY000]: Restrictions imposed on recursive definitions are violated for table 'cte'
Any suggestions of how to do it? thank you!.
This could be an option. Generate all possible values, sort randomly and take desired number of entries.
CREATE TABLE random_data (
row1 INT PRIMARY KEY AUTO_INCREMENT,
row2 VARCHAR(10) NOT NULL,
UNIQUE KEY _Idx1 ( row2 )
);
INSERT INTO random_data (row2)
SELECT LPAD(num, 8, 0)
FROM (
SELECT h * 10000000 + g * 1000000 + f * 100000 + e * 10000 + d * 1000 + c * 100 + b * 10 + a AS num
FROM (SELECT 0 a 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) ta
JOIN (SELECT 0 b 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) tb
JOIN (SELECT 0 c 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) tc
JOIN (SELECT 0 d 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) td
JOIN (SELECT 0 e 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) te
JOIN (SELECT 0 f 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) tf
JOIN (SELECT 0 g 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) tg
JOIN (SELECT 0 h 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) th
) n
ORDER BY RAND()
LIMIT 100000;
If you have a table - any table - with e.g. 100 rows then you can generate million distinct random numbers between 0 and 99999999 as follows:
select distinct floor(rand() * 100000000)
from t as t0, t as t1, t as t2
limit 1000000
Note that because of distinct you will need to generate a bigger number of rows so that you get desired number of rows after distinct.

How to fill missing values in aggregate-by-time function

I have function (from this question) which groups values by every 5 minutes and calculate min/avg/max:
SELECT (FLOOR(clock / 300) * 300) as period_start,
MIN(value), AVG(value), MAX(value)
FROM data
WHERE clock BETWEEN 1200000000 AND 1200001200
GROUP BY FLOOR(clock / 300);
However, due to missing values, some five-minute periods are skipped, making the timeline inconsistent. How to make it so that in the absence of data for a certain period, the value of max / avg / min becomes 0, instead of being skipped?
For example:
If I have timestamp - value
1200000001 - 100
1200000002 - 300
1200000301 - 100
1200000601 - 300
I want to get this: (select min/avg/max, time between 1200000000 and 1200001200)
1200000000 - 100/200/300
1200000300 - 100/100/100
1200000600 - 300/300/300
1200000900 - 0/0/0
Instead of this: (time between 1200000000 and 1200001200)
1200000000 - 100/200/300
1200000300 - 100/100/100
1200000600 - 300/300/300
1200000900 - THIS LINE WILL NOT BE, I will only get 3 lines above. No data between 1200000900 and 1200001200 for calculation.
My Answer:
Generate first table with required time range, and then left join this generated table on query with common group by operator. Such like this:
select * from
(select UNIX_TIMESTAMP(gen_date) as unix_date from
(select adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) gen_date from
(select 0 t0 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 t1 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 t2 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 t3 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 t4 union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v
where gen_date between '2017-01-01' and '2017-12-31') date_range_table
left join (
SELECT (FLOOR(clock / 300) * 300) as period_start,
MIN(value), AVG(value), MAX(value)
FROM table
WHERE clock BETWEEN 1483218000 AND 1514667600
GROUP BY FLOOR(clock / 300)) data_table
on date_range_table.unix_date = data_table.period_start;
Use recursive CTE (available in MariaDB starting from 10.2.2) and generate base calendar table:
WITH RECURSIVE
cte AS ( SELECT #timestart timestart, #timestart + 300 timeend
UNION ALL
SELECT timestart + 300, timeend + 300 FROM cte WHERE timeend < #timeend)
SELECT cte.timestart,
COALESCE(MIN(value), 0) min_value,
COALESCE(AVG(value), 0) avg_value,
COALESCE(MAX(value), 0) max_value
FROM cte
LEFT JOIN example ON example.clock >= cte.timestart
AND example.clock < cte.timeend
GROUP BY cte.timestart;
https://dbfiddle.uk/?rdbms=mariadb_10.3&fiddle=f5c41b7596d56f1d7babe075f19302ec
I am not very sure but here's a link which can solve your problem
https://www.sqlservercurry.com/2009/06/find-missing-identity-numbers-in-sql.html
You can try this one;
with seq as (
select
(step-1)* 300 + (select (FLOOR(min(clock) / 300) * 300) from data) as step
from
(select row_number() over() as step from data) tmp
where
tmp.step-1 < (select(max(clock)-min(clock))/ 300 from data))
SELECT seq.step as period_start, MIN(value), AVG(value), MAX(value)
FROM seq left join data on (seq.step=(FLOOR(clock / 300) * 300))
WHERE clock BETWEEN 1622667600 AND 1625259600
GROUP BY period_start
Alternative answer is generate first table with required time range, and then left join this generated table on query with common group by operator.

SQL - Is there a way to calculate a running total on a series of dates?

Given a list of events with date where 1 indicates something (e.g. student) joined a group and -1 indicates something left a group, is it possible to calculate group size by date in SQL? I have code that produces all dates in a range... that works when I run it on its own. Then I'd like to join in enrollment events by class and have a total of the number enrolled on each date. (+1 == class add, -1 == class drop).
I think I am missing something fundamental about how Joins and Grouping works in SQL.
http://sqlfiddle.com/#!9/e4835/5/0
Sample data:
CREATE TABLE classes(`id` int, `name` varchar(7));
INSERT INTO classes(`id`, `name`) VALUES
(1, 'math'),
(2, 'english'),
(3, 'sciene');
CREATE TABLE enrollment_changes(
`class_id` int,
`change_date` date,
`change` int);
INSERT INTO enrollment_changes
(`class_id`, `change_date`, `change`)
VALUES
(1, '2019-01-01', 1),
(1, '2019-01-01', 1),
(1, '2019-01-02', -1),
(3, '2019-01-02', 1),
(1, '2019-01-03', 1),
(2, '2019-01-03', -1)
;
-- This gets me part way there... it produces the product of dates x classes
SELECT
date_range.event_date, c.name
FROM
(SELECT adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) event_date
FROM
(SELECT 0 t0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0,
(SELECT 0 t1 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
(SELECT 0 t2 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
(SELECT 0 t3 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
(SELECT 0 t4 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4
) AS date_range
JOIN
classes c
WHERE
date_range.event_date BETWEEN '2019-01-01' AND '2019-01-03'
;
-- This does not work at all... it reduces the output to a single record.
SELECT
date_range.event_date, c.name, SUM(e.change) AS 'NetEnrollment'
FROM
(SELECT adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) event_date
FROM
(SELECT 0 t0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0,
(SELECT 0 t1 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
(SELECT 0 t2 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
(SELECT 0 t3 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
(SELECT 0 t4 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4
) AS date_range
JOIN
classes c
JOIN
enrollment_changes AS e
ON
e.change_date <= date_range.event_date
AND
e.class_id = c.id
WHERE
date_range.event_date BETWEEN '2019-01-01' AND '2019-01-03'
;
Current result:
event_date name NetEnrollment
------------------------------------
2019-01-01 math 6
Desired result:
event_date name NetEnrollment
------------------------------------
2019-01-01 math 1
2019-01-01 english 0
2019-01-01 science 0
2019-01-02 math 1
2019-01-02 english 1
2019-01-02 science 0
2019-01-03 math 2
2019-01-03 english 1
2019-01-03 science 1
You can use the following solution:
SELECT date_range.event_date, c.name, IFNULL(SUM(e.change), 0) AS 'NetEnrollment'
FROM (
SELECT adddate('1970-01-01',t4*10000 + t3*1000 + t2*100 + t1*10 + t0) event_date
FROM
(SELECT 0 t0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0,
(SELECT 0 t1 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
(SELECT 0 t2 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
(SELECT 0 t3 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
(SELECT 0 t4 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4
) AS date_range JOIN classes c
LEFT JOIN enrollment_changes AS e ON e.change_date <= date_range.event_date AND c.id = e.class_id
WHERE date_range.event_date BETWEEN '2019-01-01' AND '2019-01-03'
GROUP BY date_range.event_date, c.name
ORDER BY date_range.event_date, c.name
demo on dbfiddle.uk
Every day on the calendar table get joined with all classes (using JOIN). The encrollment changes get joined (using LEFT JOIN) to the day and specific class. Using IFNULL you can replace the NULL values with 0.
Using a GROUP BY just on the day results in one row per day. To get a row for every class per day you have to GROUP BY day and class name.

Insert where in join, code confused

This code returns me the most used words in a column in TEXT format called description and is on the table 'messages`.
However I can not stick this in the WHERE code:
messages.tag = 'HELLO'
I need it to do what it already does, but with this WHERE I tried this code and gave not sure:
SELECT message, count(message) as count
FROM (
SELECT
messages.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(messages.message, ' ', count.n), ' ', -1) as message
FROM
(select (h*100+t*10+u+1) n from
(select 0 h union select 1 union select 2 union select 3 union select 4 union
select 5 union select 6 union select 7 union select 8 union select 9) A,
(select 0 t union select 1 union select 2 union select 3 union select 4 union
select 5 union select 6 union select 7 union select 8 union select 9) B,
(select 0 u union select 1 union select 2 union select 3 union select 4 union
select 5 union select 6 union select 7 union select 8 union select 9) C
) as count
INNER JOIN messages
ON CHAR_LENGTH(messages.message)-CHAR_LENGTH(REPLACE(messages.message, ' ',''))>=count.n-1
ORDER BY id, n
) x
WHERE LENGTH(message) >= 5
AND messages.tag = 'HELLO'
GROUP BY message
ORDER BY count DESC
LIMIT 10
You have to move that where clause into the subquery. The messages alias is not known in the outer query:
FROM (SELECT messages.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(messages.message, ' ', count.n), ' ', -1) as message
FROM (select (h*100+t*10+u+1) n
from (select 0 h union select 1 union select 2 union select 3 union select 4 union
select 5 union select 6 union select 7 union select 8 union select 9) A,
(select 0 t union select 1 union select 2 union select 3 union select 4 union
select 5 union select 6 union select 7 union select 8 union select 9) B,
(select 0 u union select 1 union select 2 union select 3 union select 4 union
select 5 union select 6 union select 7 union select 8 union select 9) C
) count INNER JOIN
messages
ON CHAR_LENGTH(messages.message)-CHAR_LENGTH(REPLACE(messages.message, ' ','')) >= count.n-1
WHERE messages.tag = 'HELLO'
ORDER BY id, n
) x
The other condition stays in the outer query.
Try this,
I think messages.tag should be like, not equal to 'HELLO'
SELECT message, count(message) as count
FROM (
SELECT
messages.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(messages.message, ' ', count.n), ' ', -1) as message
FROM
(
select (h*100+t*10+u+1) n
from
(
select 0 h union select 1 union select 2 union select 3 union select 4
union
select 5 union select 6 union select 7 union select 8 union select 9) A,
(select 0 t union select 1 union select 2 union select 3 union select 4
union
select 5 union select 6 union select 7 union select 8 union select 9) B,
(select 0 u union select 1 union select 2 union select 3 union select 4
union
select 5 union select 6 union select 7 union select 8 union select 9) C
) count
INNER JOIN messages
ON CHAR_LENGTH(messages.message)-CHAR_LENGTH(REPLACE(messages.message, ' ',''))>=count.n-1
WHERE messages.tag like '%HELLO%'
ORDER BY id, n
) x
WHERE LENGTH(message) >= 5
GROUP BY message
ORDER BY count DESC
LIMIT 10

Rewrite to not include subquery in FROM?

A great fellow helped me with developing the following statement. However, in mySQL - I cannot save a view with a subquery in the FROM clause. Any suggestions o nhow to rewrite this so that it can be saved into a mySQL server?
SELECT t.idPatternMetadata, SUBSTRING_INDEX(SUBSTRING_INDEX(t.sKeywords, ',', n.n), ',', -1) color , count(*) as counts
FROM tblPatternMetadata 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
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(t.sKeywords) - LENGTH(REPLACE(t.sKeywords, ',', '')))
group by color
THANKS in advance!
One option is to create a table that contains the 100 integer values, and reference that table in the query.
CREATE TABLE n (n INT UNSIGNED PRIMARY KEY);
INSERT INTO n (n)
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
CROSS
JOIN ( 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
ORDER BY 1;
Then rewrite your query to reference the table in place of the inline view:
SELECT t.idPatternMetadata, SUBSTRING_INDEX(SUBSTRING_INDEX(t.sKeywords, ',', n.n), ',', -1) AS color
, count(*) AS counts
FROM tblPatternMetadata t
JOIN n
ON n.n <= 1 + (LENGTH(t.sKeywords) - LENGTH(REPLACE(t.sKeywords, ',', '')))
GROUP BY color