MySQL Query to Ungroup Data - mysql

I have the same exact question as this person, but for MySQL rather than SQL Server. Can ungrouping be done with MySQL? MySQL doesn't have an "Unpivot" function unfortunately. Here is an example of what I need:
Raw Data:
----------------------------------
owner id | name | occurances
----------------------------------
1 | red | 4
1 | yellow | 2
1 | green | 3
----------------------------------
Query to output:
---------------
id | name
---------------
1 | red
1 | red
1 | red
1 | red
1 | yellow
1 | yellow
1 | green
1 | green
1 | green
---------------

You need a set of numbers for this. Here is one way:
select id, name
from t join
(select d1.d + 10 * d2.d + 100*d3.d as num
from (select 1 as d union all select 2 union all select 3 union all
select 4 union all select 5 union all select 6
select 7 union all select 8 unin all select 9 union all select 0
) d1 cross join
(select 1 as d union all select 2 union all select 3 union all
select 4 union all select 5 union all select 6
select 7 union all select 8 unin all select 9 union all select 0
) d2 cross join
(select 1 as d union all select 2 union all select 3 union all
select 4 union all select 5 union all select 6
select 7 union all select 8 unin all select 9 union all select 0
) d3
) n
where n.num between 1 and occurrences
This works for numbers up to 999.

Related

How to emulate JSON_OVERLAPS function on MySQL 5.7?

I have two columns from different tables that hold JSON-formatted data. The data stored in both columns are arrays. Example:
users
+----+------------------+
| id | options |
+----+------------------+
| 1 | ["AB","CD","XY"] |
| 2 | ["CD","GH"] |
+----+------------------+
items
+----+-------------+
| id | options |
+----+-------------+
| 10 | ["CD","EF"] |
| 11 | ["GH","XY"] |
| 12 | ["GH"] |
+----+-------------+
I wanted to write a query that returns all the rows from users which matches a given row from items, using options columns to perform the match. The rule is if any value in the array is present in both rows, they are a match. Example: user 1 would match items 10 (because of CD option) and 11 (because of XY option); user 2 would match items 10, 11 and 12 because all of them have CD or GH.
Looking at MySQL docs I found that JSON_OVERLAPS does exactly that. However, I'm running MySQL 5.7 and the function is only available starting at 8.0.17. There is also no much talking around this function on the web.
How could I emulate JSON_OVERLAPS behavior on MySQL 5.7 in a query?
Edit: Unfortunately, upgrading to MySQL 8 is not an option since we run MariaDB on production, which also doesn't have that function.
How to emulate JSON_OVERLAPS function on MySQL 5.7?
Edit: Unfortunately, upgrading to MySQL 8 is not an option since we run MariaDB on production, which also doesn't have that function.
Be warned like Strawberry, suggested already upgrading is more easy
Now that is out off the way. You still asked for it, lets have some fun.
I posted some answers in the past to simulate MySQL's 8 JSON_TABLE(), why did i mention this? Because i use this method to emulate MySQL's 8 JSON_OVERLAPS to simply JOIN both resultsets which emulate JSON_TABLE() to a final resultset
Which makes the query below (forgive the formatting)
Query
SELECT
*
FROM (
SELECT
items.id
, JSON_UNQUOTE(
JSON_EXTRACT(items.options, CONCAT('$[', number_generator.number , ']'))
) AS json_options
FROM (
SELECT
#items_row := #items_row + 1 AS number
FROM (
SELECT 0 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
) row1
CROSS JOIN (
SELECT 0 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
) row2
CROSS JOIN (
SELECT #items_row := -1
) init_user_params
) AS number_generator
CROSS JOIN (
SELECT
items.id
, items.options
, JSON_LENGTH(items.options) AS json_array_length
FROM
items
) AS items
WHERE
number BETWEEN 0 AND json_array_length - 1
) AS items
INNER JOIN (
SELECT
users.id
, JSON_UNQUOTE(
JSON_EXTRACT(users.options, CONCAT('$[', number_generator.number , ']'))
) AS json_options
FROM (
SELECT
#users_row := #users_row + 1 AS number
FROM (
SELECT 0 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
) row1
CROSS JOIN (
SELECT 0 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
) row2
CROSS JOIN (
SELECT #users_row := -1
) init_user_params
) AS number_generator
CROSS JOIN (
SELECT
users.id
, users.options
, JSON_LENGTH(users.options) AS json_array_length
FROM
users
) AS users
WHERE
number BETWEEN 0 AND json_array_length - 1
) AS users
USING(json_options)
Result
| json_options | id | id |
| ------------ | --- | --- |
| CD | 10 | 2 |
| CD | 10 | 1 |
| GH | 11 | 2 |
| GH | 12 | 2 |
| XY | 11 | 1 |
see demo

Repeat rows in the result based on an integer value in column

I have following table.
Sales:
id quantity price_charged
------------------------------
101 2 100
102 3 300
103 1 120
I want to select the records such that it repeat Rows N time according to quantity column value.
So I need following results
id quantity price_charged
--------------------------------
101 1 50
101 1 50
102 1 100
102 1 100
102 1 100
103 1 120
I think, it is better to resolve not with query(SQL).
There is some generation feature, but its performance is poor.
You have to change your model(store always 1 quantity), or process it in backend(java/c/stb.)
Select id,
1 as quantity,
price_charged
from table_name t
JOIN
(SELECT e*10000+d*1000+c*100+b*10+a n 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) t1,
(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) t2,
(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) t3,
(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) t4,
(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) t5) counter
ON (counter.n<=t.quantity)
The joined subquery reapeted numbers from 0 to 99999 it is the burn it maximum for quantity. The join repeat by the counter 0... quantity-1 values.
I was able to come up with a solution for my problem after referring an answer for how to generate series in mysql. Here is the link.
SELECT
sal.id,
1 as quantity, sal.quantity as originalQty,
sal.price_charged/sal.quantity
FROM
(SELECT
#num := #num + 1 AS count
FROM
sales, -- this can be any table but it should have row count
-- more than what we expect the max value of Sales.quantity column
(SELECT #num := 0) num
LIMIT
100) ctr
JOIN sales sal
ON sal.quantity >= ctr.count
order by id;
If you are lucky enough to be running MySQL 8.0, you can use a recursive CTE to solve this problem. This is an elegant solution that does not require creating a list of numbers of using variables.
Consider this query:
WITH RECURSIVE cte AS (
SELECT 1 n, id, quantity, price_charged FROM sales
UNION ALL
SELECT n + 1, id, quantity, price_charged FROM cte WHERE n < quantity
)
SELECT id, quantity, price_charged/quantity quantity
FROM cte
ORDER BY id;
In this DB Fiddle with your sample data, the query returns:
| id | quantity | quantity |
| --- | -------- | -------- |
| 101 | 2 | 50 |
| 101 | 2 | 50 |
| 102 | 3 | 100 |
| 102 | 3 | 100 |
| 102 | 3 | 100 |
| 103 | 1 | 120 |

Splitting string with '+' seperator into seperate rows and apply aggregation

The data is not static and group of characters separted by + can vary. I want all the characters separated by + to be in row wise and then apply aggregation on the top of it. I am using mysql 5.7.14 in windows.
suppose data is:
group val
a+b 10
a 5
b 6
b+d+c 12
d 13
c+d 12
the output should be like:
grp_item val
a 15
b 28
c 24
d 24
Like i said the MySQL query is complex..
The general idea is a MySQL number generator which generates 1 to 10000 so it supports 10000 separated values with the + sign in the group column.
And it does not matter what data is between the + signs.
Query
SELECT
Table1_unique_groups.`group`
, SUM(Table1.val)
FROM (
SELECT
DISTINCT
SUBSTRING_INDEX(SUBSTRING_INDEX(Table1.`group`, '+', number_generator.number), '+', -1) AS `group`
FROM (
SELECT
#row := #row + 1 AS number
FROM (
SELECT 0 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
) record_1
CROSS JOIN (
SELECT 0 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
) record_2
CROSS JOIN (
SELECT 0 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
) record_4
CROSS JOIN (
SELECT 0 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
) record_5
CROSS JOIN (
SELECT #row := 0
) AS init_user_params
) AS number_generator
CROSS JOIN
Table1
) AS Table1_unique_groups
INNER JOIN
Table1
ON
FIND_IN_SET(Table1_unique_groups.`group`, REPLACE(Table1.group, '+', ','))
GROUP BY
Table1_unique_groups.`group`
Result
| group | SUM(Table1.val) |
| ----- | --------------- |
| a | 15 |
| b | 28 |
| c | 24 |
| d | 37 |
DB Fiddle demo

Auto insert rows with repeated data, following two patterns

I have a table that looks like this:
| id | letter | number |
|-----|--------|--------|
| 1 | a | 1 |
| 2 | b | 1 |
| 3 | c | 1 |
| 4 | d | 1 |
| 5 | a | 2 |
| 6 | b | 2 |
| 7 | c | 2 |
| 8 | d | 2 |
| 9 | a | 3 |
| 10 | b | 3 |
| 11 | c | 3 |
| 12 | d | 3 |
|etc..| | |
I'm trying to make an SQL statement that auto-fills the table following this pattern up to id 456.
So the letters are ABCD ABCD until the sequence ends, and each 'group' of 4 has a number, that should reach 114.
I'm not sure what the best way to tackle this is, any suggestions would be appreciated.
You can use the following sql script to insert the values required into your table:
INSERT INTO target (id, letter, `number`)
SELECT rn, col, (rn - 1) % 4 + 1 AS seq
FROM (
SELECT col, #rn := #rn + 1 AS rn
FROM (
SELECT 'a' AS col UNION ALL SELECT 'b' UNION ALL
SELECT 'c' UNION ALL SELECT 'd') AS t
CROSS JOIN (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ) AS t1
CROSS JOIN (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ) AS t2
CROSS JOIN (SELECT #rn := 0) AS var ) AS s
WHERE rn <= 456
The above query creates a numbers table of 121 rows using a 11 x 11 cartesian product. These rows are cross joined with in-line table ('a'), ('b'), ('c'), ('d') to produce a total of 484 rows. The outer query selects just the rows needed, i.e. 456 rows in total.
Note: If you want to insert values:
id, letter, number
1 'a' 1
2 'b' 1
3 'c' 1
4 'd' 1
5 'a' 2
6 'b' 2
7 'c' 2
8 'd' 2
... etc
instead of values:
id, letter, number
1 'a' 1
2 'b' 2
3 'c' 3
4 'd' 4
5 'a' 1
6 'b' 2
7 'c' 3
8 'd' 4
... etc
then simply replace (rn - 1) % 4 + 1 AS seq with (rn - 1) DIV 4 + 1 AS seq.
Demo here
It would help if you had a numbers table of some sort. Here is one method using cross join and some arithmetic:
select (#rn := #rn + 1) as id, l.letter, (n1 + n2*5 + n3*25) as number
from (select 0 as n union all select 1 as n union all select 2 as n union all select 3 union all select 4
) n1 cross join
(select 0 as n union all select 1 as n union all select 2 as n union all select 3 union all select 4
) n2 cross join
(select 0 as n union all select 1 as n union all select 2 as n union all select 3 union all select 4
) n3 cross join
(select 'a' as letter union all select 'b' union all select 'c' union all select 'd'
) l cross join
(select #rn := 0) params
where n1 + n2*5 + n3*25 < 114;

Select a row for each 'quantity'

Table:
Article | Quantity | pricePerUnit | order_id | article_id
--------|----------|--------------------------------------
14 | 2 | 10.0 | 1 | 1
X1 | 1 | 5.0 | 1 | 2
Expected output:
Article | Quantity | pricePerUnit | order_id
--------|----------|------------------------
14 | 1 | 10.0 | 1
14 | 1 | 10.0 | 1
X1 | 1 | 5.0 | 1
What is a fast SELECT to populate the resultset with 1 row for each quantity per article?
Sorry I didn't try anything, I'm not sure wether this is possible at all. Self join.. would not be a help, grouping functions,..
Maybe GROUP BY order_id, article_id, quantity somehow..
UPDATE: For the max quantity of three just do
SELECT Article, 1 Quantity, pricePerUnit, order_id
FROM articles a JOIN
(
SELECT 1 AS n UNION ALL
SELECT 2 UNION ALL
SELECT 3
) n
ON n.n <= a.Quantity
ORDER BY order_id, Article
Here is SQLFiddle demo
Original answer: You can try
SELECT Article, 1 Quantity, pricePerUnit, order_id
FROM articles a 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
ON n.n <= a.Quantity
ORDER BY order_id, Article
An inner select returns 100 rows meaning you can unpivot quantities up to the value of 100. If you need more update it accordingly.
Here is SQLFiddle demo
Given that it is for a report and you have necessary right to create a new table it's best to substitute an inner select with a tally (numbers) table which you can create in the same manner:
CREATE TABLE tally (n int not null auto_increment primary key);
INSERT INTO tally
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;
And then your query will look like
SELECT Article, 1 Quantity, pricePerUnit, order_id
FROM articles a JOIN tally n
ON n.n <= a.Quantity
ORDER BY order_id, Article
Here is SQLFiddle demo