Add row number after splitting a string field - mysql

I have a table that contains 2 fields:
ID: text
Suggestions: string (comma separated values)
I would like to make a select query that would return a new numbered rows representing each suggestion with its own number as shown in the original string
Example:
Note: this ranking must be guaranteed to be the same everytime I run the query..
Thanks

If Version of your DB is 8.0+, then with recursive cte as clause might be used as in the following select statement ( after needed DML's provided such as create table and insert statements ):
mysql> create table tab( ID int, suggestions varchar(25));
mysql> insert into tab values(1,'A,B,C');
mysql> insert into tab values(2,'D,E,F,G,H');
mysql> select q2.*,
row_number()
over
(partition by q2.id order by q2.suggestion) as number
from
(
select distinct
id,
substring_index(
substring_index(suggestions, ',', q1.nr),
',',
-1
) as suggestion
from tab
cross join
(with recursive cte as
(
select 1 as nr
union all
select 1+nr from cte where nr<10
)
select * from cte) q1
) q2;
+------+------------+--------+
| id | suggestion | number |
+------+------------+--------+
| 1 | A | 1 |
| 1 | B | 2 |
| 1 | C | 3 |
| 2 | D | 1 |
| 2 | E | 2 |
| 2 | F | 3 |
| 2 | G | 4 |
| 2 | H | 5 |
+------+------------+--------+

Find here same problem is solved.
https://gist.github.com/avoidwork/3749973

I would suggest a series of subqueries:
select id, substring_index(suggestions, ',', 1) as suggestion, 1
from example
where suggestions is not null
union all
select id, substring_index(substring_index(suggestions, ',', 2), ',', -1) as suggestion, 2
from example
where suggestions like '%,%'
union all
select id, substring_index(substring_index(suggestions, ',', 3), ',', -1) as suggestion, 3
from example
where suggestions like '%,%,%'
union all
select id, substring_index(substring_index(suggestions, ',', 4), ',', -1) as suggestion, 4
from example
where suggestions like '%,%,%,%'
union all
select id, substring_index(substring_index(suggestions, ',', 5), ',', -1) as suggestion, 5
from example
where suggestions like '%,%,%,%,%';
This can easily be extended if you have more than 5 options per id.

Related

How to get maximum appearance count of number from comma separated number string from multiple rows in MySQL?

My MySQL table having column with comma separated numbers. See below example -
| style_ids |
| ---------- |
| 5,3,10,2,7 |
| 1,5,12,9 |
| 6,3,5,9,4 |
| 8,3,5,7,12 |
| 7,4,9,3,5 |
So my expected result should have top 5 numbers with maximum appearance count in descending order as 5 rows as below -
| number | appearance_count_in_all_rows |
| -------|----------------------------- |
| 5 | 5 |
| 3 | 4 |
| 9 | 3 |
| 7 | 2 |
| 4 | 2 |
Is it possible to get above result by MySQL query ?
As already alluded to in the comments, this is a really bad idea. But here is one way of doing it -
WITH RECURSIVE seq (n) AS (
SELECT 1 UNION ALL SELECT n+1 FROM seq WHERE n < 20
), tbl (style_ids) AS (
SELECT '5,3,10,2,7' UNION ALL
SELECT '1,5,12,9' UNION ALL
SELECT '6,3,5,9,4' UNION ALL
SELECT '8,3,5,7,12' UNION ALL
SELECT '7,4,9,3,5'
)
SELECT seq.n, COUNT(*) appearance_count_in_all_rows
FROM seq
JOIN tbl ON FIND_IN_SET(seq.n, tbl.style_ids)
GROUP BY seq.n
ORDER BY appearance_count_in_all_rows DESC
LIMIT 5;
Just replace the tbl cte with your table.
As already pointed out you should fix the data if possible.
For further details read Is storing a delimited list in a database column really that bad?.
You could use below answer which is well explained here and a working fiddle can be found here.
Try,
select distinct_nr,count(distinct_nr) as appearance_count_in_all_rows
from ( select substring_index(substring_index(style_ids, ',', n), ',', -1) as distinct_nr
from test
join numbers on char_length(style_ids) - char_length(replace(style_ids, ',', '')) >= n - 1
) x
group by distinct_nr
order by appearance_count_in_all_rows desc ;

How to generate a survey curve with SQL query

I have a table with 2 columns WorkItem and LiveDays. For example
| WorkItem | LiveDays |
| A | 8 |
| B | 2 |
| C | 5 |
....
I would like to generate a survey data of the work item. Each item is normalized as starting from day 1 and ending to LiveDays, and value of nth day is how many workitems is still live (in process). For example
| Days | Counter | Comments |
| 1 | 3 | (A, B, C)|
| 2 | 3 | (A, B, C)|
| 3 | 2 | (A, C) |
| 4 | 2 | (A, C) |
| 5 | 2 | (A, C) |
| 6 | 1 | (A) |
| 7 | 1 | (A) |
| 8 | 1 | (A) |
Is it possible to use SQL query instead of inserting data into a new table with transaction?
Thanks
To show it can be done (it does after all answer your original question) here's your example reproduced using Db2 Developer-C 11.1 on dbfiddle.uk: (You did say there were several databases you could use, and this will no doubt serve to illustrate that different databases do things in different ways!). Note: additional MySQL solution further down.
CREATE TABLE surveydata AS (
WITH t1(workitem, livedays)
AS (VALUES ('A', 8), ('B', 2), ('C', 5)),
numbers(seq)
AS (VALUES (1)
UNION ALL
SELECT seq + 1
FROM numbers
WHERE seq < (SELECT MAX(livedays)
FROM t1)),
xdata(workitem, ndays)
AS (SELECT workitem,
seq
FROM t1,
numbers
WHERE seq <= livedays)
SELECT ndays AS "Days",
COUNT(*) AS "Counter",
'(' || LISTAGG(workitem, ', ') WITHIN GROUP (ORDER BY workitem) || ')' AS "Comments"
FROM xdata
GROUP BY ndays
) WITH DATA;
with the result of SELECT * FROM surveydata as below ==>
UPDATE: With a bit more fiddling, I've managed to get a solution using MySQL 8.0 as well:
WITH recursive t1(workitem, livedays)
AS (SELECT 'A', 8
UNION ALL SELECT 'B', 2
UNION ALL SELECT 'C', 5 ),
numbers(seq)
AS (SELECT 1 AS seq
UNION ALL
SELECT seq + 1
FROM numbers
WHERE seq < (SELECT MAX(livedays)
FROM t1)),
xdata(workitem, ndays)
AS (SELECT workitem,
seq
FROM t1,
numbers
WHERE seq <= livedays)
SELECT ndays AS "Days",
COUNT(*) AS "Counter" ,
CONCAT('(', GROUP_CONCAT(workitem ORDER BY workitem SEPARATOR ', '), ')') AS "Comments"
FROM xdata
GROUP BY ndays;

mysql: finding the shortest and the longest value within an mysql-column

I had a question about finding the shortest und the longest value within an concated string in MySQL-Column "values" .
The Problem, the values within the column are concated with "|" and may be diffrent long.
Table:
ID | values
----------------------------------------
A | 12.1|11.23|134.44
B | 11.134|1.3|34.5|152.12|1.31313|134.331|12.31
C | 34.11|1.34|343412.2|13......
The question is: Is there some simple possiblity to find both (shortest and the longest) value only by native mysql query, without using any language as Java or PHP.
Thank you
You can't get the result you need in a single query, at least not in the current version of MySQL. The reason is that you can't form a query to fetch individual elements from a delimited string of unknown length before you know the maximum length.
First find out how many elements are in the longest list:
select max(length(`values`)-length(replace(`values`, '|', ''))) as max from t;
+------+
| max |
+------+
| 6 |
+------+
Now you know you'll need to test up to 7 "fields" within your delimited string. There's no way to form SQL with a variable number of unioned queries. The syntax must be fixed at prepare time, so you need to know how many.
select id, substring_index(substring_index(`values`, '|', 1), '|', -1) as element from t
union
select id, substring_index(substring_index(`values`, '|', 2), '|', -1) from t
union
select id, substring_index(substring_index(`values`, '|', 3), '|', -1) from t
union
select id, substring_index(substring_index(`values`, '|', 4), '|', -1) from t
union
select id, substring_index(substring_index(`values`, '|', 5), '|', -1) from t
union
select id, substring_index(substring_index(`values`, '|', 6), '|', -1) from t
union
select id, substring_index(substring_index(`values`, '|', 7), '|', -1) from t;
+------+----------+
| id | element |
+------+----------+
| A | 12.1 |
| A | 11.23 |
| A | 134.44 |
| B | 11.134 |
| B | 1.3 |
| B | 34.5 |
| B | 152.12 |
| B | 1.31313 |
| B | 134.331 |
| B | 12.31 |
| C | 34.11 |
| C | 1.34 |
| C | 343412.2 |
| C | 13 |
+------+----------+
Now use the query above as a subquery, you can find the longest or shortest:
(select id, element from (...subquery...) as t1 order by length(element) asc limit 1)
union
(select id, element from (...subquery...) as t2 order by length(element) desc limit 1)
+------+----------+
| id | element |
+------+----------+
| C | 343412.2 |
| C | 13 |
+------+----------+
I agree with others who have commented that this is really the wrong way to use an RDBMS. I understand you said that you're committed to this structure, but you'll find in the long run, it makes more work for you than the work it would take to fix the schema.
See also my answer to Is storing a delimited list in a database column really that bad?
Max Length
select * from table order by length(column_name) DESC LIMIT 0,1
Min Length
select * from table order by length(column_name) ASC LIMIT 0,1
If this is not what you looking add your SQL query into the question.
SQL is not friendly to arrays of values in cells. Restructure the schema; then the solution will be easy.

Delete duplicated tags MySQL

I have duplicated tags on my MySQL DB such as below:
| id | tags |
+- ---+-------------------------------------+
| 3 | x,yz,z,x,x |
| 5 | a,b,c d,a,b,c d, d |
+-----+-------------------------------------+
How can I execute a query that can remove the duplicated tags?
The result should be:
| id | tags |
+- ---+-------------------------------------+
| 3 | x,yz,z |
| 5 | a,b,c d, d |
+-----+-------------------------------------+
setup
create table overly_complex_tags
(
id integer primary key not null,
tags varchar(100) not null
);
insert into overly_complex_tags
( id, tags )
values
( 3 , 'x,yz,z,x,x' ),
( 5 , 'a,b,c d,a,b,c d,d' )
;
create view digits_v
as
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
;
query delete duplicate tags
update overly_complex_tags t
inner join
(
select id, group_concat(tag) as new_tags
from
(
select distinct t.id, substring_index(substring_index(t.tags, ',', n.n), ',', -1) tag
from overly_complex_tags t
cross join
(
select a.N + b.N * 10 + 1 n
from digits_v a
cross join digits_v b
order by n
) n
where n.n <= 1 + (length(t.tags) - length(replace(t.tags, ',', '')))
) cleaned_tags
group by id
) updated_tags
on t.id = updated_tags.id
set t.tags = updated_tags.new_tags
;
output
+----+-----------+
| id | tags |
+----+-----------+
| 3 | yz,z,x |
| 5 | c d,a,d,b |
+----+-----------+
sqlfiddle
note
the complexity of above solution comes from not having a properly
normalised structure.. note that the solution uses an intermediate
normalised structure
In Oracle we accomplish this as
Update table set col=(select distinct regexp_substr(col, '[^,]+', 1, level) col
from table)
But for MySQL it not possible and only way is through PHP array as stated here. However Maria db have its solution as well. So use intermediate way.

How to make sql query using group by data

I have a table which look like below:
id name
---|------|------------|
| 1 | IP2.0-v1.0 |
| 2 | IP2.0-v2.0 |
| 3 | IP1.0-v1.0 |
| 4 | IP1.1-v1.0 |
I would like to have group by result such as below:
name count
---|----------|------------|
| IP2.0 | 2 |
| IP1.0 | 1 |
| IP1.1 | 1 |
How can I manage to get SQL query using group by?
Select name,count(*) from table_a GROUP BY ...........
SELECT SUBSTRING (NAME,1,5), COUNT(*) FROM table_a GROUP BY SUBSTRING (NAME,1,5)
You want a subquery to do the substring before the group by
select nameSub, count(*) from
(select id, substring(name,1,5) as nameSub from table)
group by nameSub
Try with SUBSTRING( str FROM pos FOR len )
SELECT
SUBSTRING(`name` from 1 for 5) as name_res,
COUNT(*)
FROM #TABLE
GROUP BY name_res;
Try this:
SELECT SUBSTRING(Name,1,CHARINDEX('-',Name)-1) AS Name, COUNT(*) AS Count
FROM table_a
GROUP BY SUBSTRING(Name,1,CHARINDEX('-',Name)-1)
SUBSTRING is useful if the length of Name vary
MySQL Fiddle and SQL Server Fiddle:
SELECT LEFT(name, 5), count(*)
FROM MyTable
GROUP BY LEFT(name, 5)