MYSQL - Count items separated by comma or not - mysql

I have a tinytext field which can contain 3 differents value formatted as followed:
NULL
"X" or "Y" (where X and Y can be any number)
"A,B,C,D" (where A, B, C and D can be any number)
I want to query the table and count the number of items separated or not with a comma.
For example with these lines:
42
NULL
42,56,99
24,10090
Then the expected count would be 6.
I can't find the correct query.

Okay here's the test data:
mysql> create table t (f tinytext);
mysql> insert into t values ('42'), (null), ('42,56,99'), ('24,10090');
mysql> select * from t;
+----------+
| f |
+----------+
| 42 |
| NULL |
| 42,56,99 |
| 24,10090 |
+----------+
You can calculate how many numbers in the string as the difference in the length of the string and the string with commas removed (add 1 for the first number in the list).
mysql> select f, length(f), length(replace(f,',','')), 1+ length(f)-length(replace(f,',','')) from t;
+----------+-----------+---------------------------+----------------------------------------+
| f | length(f) | length(replace(f,',','')) | 1+ length(f)-length(replace(f,',','')) |
+----------+-----------+---------------------------+----------------------------------------+
| 42 | 2 | 2 | 1 |
| NULL | NULL | NULL | NULL |
| 42,56,99 | 8 | 6 | 3 |
| 24,10090 | 8 | 7 | 2 |
+----------+-----------+---------------------------+----------------------------------------+
So then use SUM() to get the total. SUM() ignores NULLs.
mysql> select sum(1+length(f)-length(replace(f,',',''))) from t;
+--------------------------------------------+
| sum(1+length(f)-length(replace(f,',',''))) |
+--------------------------------------------+
| 6 |
+--------------------------------------------+
This would be easier if you don't store comma-separated lists in a string.

Related

MySQL: Add Default Value to Joined Table when Row not Found

System info:
$ uname -srvm
Linux 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
$ mysql --version
mysql Ver 8.0.31-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))
I am very inexperienced with MySQL & have been looking for an answer to this for about half a week. I am working with two tables named character_stats & halloffame that I want to join in a query. They look like this:
mysql> SELECT name, level FROM character_stats;
+-----------+-------+
| name | level |
+-----------+-------+
| foo | 0 |
| bar | 0 |
| baz | 3 |
| tester | 4 |
| testertoo | 2 |
+-----------+-------+
mysql> SELECT * from halloffame;
+----+-----------+----------+--------+
| id | charname | fametype | points |
+----+-----------+----------+--------+
| 1 | bar | T | 0 |
| 2 | foo | T | 0 |
| 3 | baz | T | 0 |
| 4 | tester | T | 0 |
| 5 | testertoo | T | 0 |
| 6 | tester | D | 40 |
| 7 | tester | M | 92 |
| 8 | bar | M | 63 |
+----+-----------+----------+--------+
In my query, I want to display all the rows from character_stats & I want to join the points column from halloffame for fametype='M'. If there is no row for fametype='M', I want to set points to 0 for that character name, instead of omitting the entire row as is done in the following:
mysql> SELECT name, level, points FROM character_stats JOIN
-> (SELECT charname, points FROM halloffame WHERE fametype='M')
-> AS hof ON (hof.charname=name);
+--------+-------+--------+
| name | level | points |
+--------+-------+--------+
| tester | 4 | 92 |
| bar | 0 | 63 |
+--------+-------+--------+
So I want it to output this:
+-----------+-------+--------+
| name | level | points |
+-----------+-------+--------+
| foo | 0 | 0 |
| bar | 0 | 63 |
| baz | 3 | 0 |
| tester | 4 | 92 |
| testertoo | 2 | 0 |
+-----------+-------+--------+
I have tried to learn how to use IFNULL, IF-THEN-ELSE, CASE, COALESCE, & COUNT statements from what I have found in documentation & answers on stackoverflow.com. But as I said, I am very inexperienced & don't know how to implement them.
The following works on its own:
SELECT IFNULL((SELECT points FROM halloffame WHERE fametype='M'
AND charname='foo' LIMIT 1), 0) as points;
But I don't know how to join it to the character_stats table. The following would work if I knew how to get the value of character_stats.name before COALESCE is called:
SELECT name, level, 'M' AS fametype, points FROM character_stats
JOIN (SELECT COALESCE((SELECT points FROM halloffame WHERE
fametype='M' AND charname=name LIMIT 1), 0) AS points) AS hof;
According to Adding Default Values on Joining Tables I should be able to use CROSS JOIN, but I am doing something wrong as it still results in Unknown column 'cc.name' in 'where clause':
SELECT name, level, points FROM character_stats
CROSS JOIN (SELECT DISTINCT name FROM character_stats) AS cc
JOIN (SELECT COALESCE((SELECT points FROM halloffame WHERE
fametype='M' AND charname=cc.name LIMIT 1), 0) AS points) AS hof;
Some references I have looked at:
Returning a value even if no result
Usage of MySQL's "IF EXISTS"
Return Default value if no row found
MySQL.. Return '1' if a COUNT returns anything greater than 0
How do write IF ELSE statement in a MySQL query
Simple check for SELECT query empty result
Is there a function equivalent to the Oracle's NVL in MySQL?
MySQL: COALESCE within JOIN
Unknown Column In Where Clause With Join
Adding Default Values on Joining Tables
https://www.tutorialspoint.com/returning-a-value-even-if-there-is-no-result-in-a-mysql-query
I found that I can do the following:
SELECT name, level, COALESCE((SELECT points FROM
halloffame WHERE fametype='M' AND charname=name
LIMIT 1), 0) AS points FROM character_stats;
Though I would still like to know how to do it within a JOIN statement.

How can I merge two strings of comma-separated numbers in MySQL?

For example, there are three rooms.
1|gold_room|1,2,3
2|silver_room|1,2,3
3|brown_room|2,4,6
4|brown_room|3
5|gold_room|4,5,6
Then, I'd like to get
gold_room|1,2,3,4,5,6
brown_room|2,3,4,6
silver_room|1,2,3
How can I achieve this?
I've tried: select * from room group by name; And it only prints the first row. And I know CONCAT() can combine two string values.
Please use below query,
select col2, GROUP_CONCAT(col3) from data group by col2;
Below is the Test case,
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ab35e8d66ffe3ac6436c17faf97ee9af
I'm not making an assumption that the lists don't have elements in common on separate rows.
First create a table of integers.
mysql> create table n (n int primary key);
mysql> insert into n values (1),(2),(3),(4),(5),(6);
You can join this to your rooms table using the FIND_IN_SET() function. Note that this cannot be optimized. It will execute N full table scans. But it does create an interim set of rows.
mysql> select * from n inner join rooms on find_in_set(n.n, rooms.csv) order by rooms.room, n.n;
+---+----+-------------+-------+
| n | id | room | csv |
+---+----+-------------+-------+
| 2 | 3 | brown_room | 2,4,6 |
| 3 | 4 | brown_room | 3 |
| 4 | 3 | brown_room | 2,4,6 |
| 6 | 3 | brown_room | 2,4,6 |
| 1 | 1 | gold_room | 1,2,3 |
| 2 | 1 | gold_room | 1,2,3 |
| 3 | 1 | gold_room | 1,2,3 |
| 4 | 5 | gold_room | 4,5,6 |
| 5 | 5 | gold_room | 4,5,6 |
| 6 | 5 | gold_room | 4,5,6 |
| 1 | 2 | silver_room | 1,2,3 |
| 2 | 2 | silver_room | 1,2,3 |
| 3 | 2 | silver_room | 1,2,3 |
+---+----+-------------+-------+
Use GROUP BY to reduce these rows to one row per room. Use GROUP_CONCAT() to put the integers together into a comma-separated list.
mysql> select room, group_concat(distinct n.n order by n.n) as csv
from n inner join rooms on find_in_set(n.n, rooms.csv) group by rooms.room
+-------------+-------------+
| room | csv |
+-------------+-------------+
| brown_room | 2,3,4,6 |
| gold_room | 1,2,3,4,5,6 |
| silver_room | 1,2,3 |
+-------------+-------------+
I think this is a lot of work, and impossible to optimize. I don't recommend it.
The problem is that you are storing comma-separated lists of numbers, and then you want to query it as if the elements in the list are discrete values. This is a problem for SQL.
It would be much better if you did not store your numbers in a comma-separated list. Store multiple rows per room, with one number per row. You can run a wider variety of queries if you do this, and it will be more flexible.
For example, the query you asked about, to produce a result with numbers in a comma-separated list is more simple, and you don't need the extra n table:
select room, group_concat(n order by n) as csv from rooms group by room
See also my answer to Is storing a delimited list in a database column really that bad?

SQL - Select column with certain value in it and other random values

Let me say i have a table called test with the following data
+---------+-----------------+
| id | t_number |
+---------+-----------------+
| 1 | 864291100247345 |
| 2 | 355488020906457 |
| 3 | 864296100098739 |
| 4 | 864296100098325 |
| 5 | 864296100119956 |
What i want to do is to be able to write a select statement that returns a 3 rows with two random values and one mandatory value from the t_number column
for example if the mandatory value is 864291100247345 the output should something like below
+---------+-----------------+
| id | t_number |
+---------+-----------------+
| 1 | 864291100247345 |
| 2 | 355488020906457 |
| 4 | 864296100098325 |
OR
+---------+-----------------+
| id | t_number |
+---------+-----------------+
| 1 | 864291100247345 |
| 3 | 864296100098739 |
| 4 | 864296100098325 |
I have tried the below query but it's not yielding the output i expect, in a sense that it does return a result but without the mandatory value
SELECT * FROM test WHERE t_number = 864291100247345 OR id LIMIT 3;
What is the best way to go about this?
Thank you.
You can use order by:
SELECT t.*
FROM test
ORDER BY (t_number = 864291100247345) DESC,
rand()
LIMIT 3;
This returns the mandatory number first and then random numbers after that.
MySQL treats boolean values (the result of the = expression) as numbers in a numeric context, with "1" for true and "0" for false. So the first expression in the order by sorts the result set with the "true" conditions first, followed by the others.

Get count of zeros in an integer using MySQL

Let's say I have an integer value in MySQL (10090). I need to count all occurrences of the zero digit in that number. So for the previous case it would return 3:
select count_zeros(number) from dual;
-- when number = 10090, it return 3
-- when number = 10000, it return 4
How can I do that the fastest way using a MySQL query?
You can compare the string length with and without the character you want to count.
Solution using LENGTH
-- 0 in 10090: 3
-- 0 in 10000: 4
SELECT
(LENGTH(number) - LENGTH(REPLACE(number, '0', ''))) AS char_count
FROM dual;
A better and safer solution is to use the CHAR_LENGTH function instead of the LENGTH function. With CHAR_LENGTH function you can also count multi-byte characters (like §).
Solution using CHAR_LENGTH
-- § in 100§0: 1
SELECT
(CHAR_LENGTH(number) - CHAR_LENGTH(REPLACE(number, '§', ''))) AS char_count
FROM dual;
You can also extend the above solution to count for a string value using multiple characters.
-- 12 in 10120012: 2
SELECT number,
FLOOR((CHAR_LENGTH(number) - CHAR_LENGTH(REPLACE(number, '12', ''))) / CHAR_LENGTH('12')) AS str_count
FROM dual;
demo on dbfiddle.uk
On MySQL you can create a function to use the above logic on a simpler way:
CREATE FUNCTION GetStringCount(strValue VARCHAR(255), strSearchValue VARCHAR(255))
RETURNS INT DETERMINISTIC NO SQL
RETURN FLOOR((CHAR_LENGTH(strValue) - CHAR_LENGTH(REPLACE(strValue, strSearchValue, ''))) / CHAR_LENGTH(strSearchValue));
You can use this new function GetStringCount like this:
-- example to count non-multi-byte character (here 0).
-- 0 in 10090: 3
-- 0 in 10000: 4
SELECT number, GetStringCount(number, '0') AS strCount
FROM dual;
-- example to count multi-byte character (here §).
-- § in 100§0: 1
SELECT number, GetStringCount(number, '§') AS strCount
FROM dual;
-- example to count a string with multiple characters.
-- 12 in 10120012: 2
SELECT number, GetStringCount(number, '12') AS strCount
FROM dual;
I think the first thing to be done is, casting those integer values to string.
https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_cast
Then find occurences of a certain char
https://lists.mysql.com/mysql/215049
mysql> create table numbers(x int);
Query OK, 0 rows affected (0,38 sec)
mysql> select * from numbers;
+-----------+
| x |
+-----------+
| 123000 |
| 1300 |
| 135600 |
| 135623400 |
| 13560 |
| 135160 |
| 13514560 |
| 1351120 |
| 13512310 |
+-----------+
9 rows in set (0,00 sec)
Find occurences of zero
mysql> select x, round((length(cast(x as char(11))) - length( replace( cast( x as char(11) ), "0", "" ) ))/length("0")) as str_x from numbers limit 5;
+-----------+-------+
| x | str_x |
+-----------+-------+
| 123000 | 3 |
| 1300 | 2 |
| 135600 | 2 |
| 135623400 | 2 |
| 13560 | 1 |
+-----------+-------+
5 rows in set (0,00 sec)
Find thirteens
mysql> select x, round((length(cast(x as char(11))) - length( replace( cast( x as char(11) ), "13", "" ) ))/length("13")) as str_x from numbers;
+-----------+-------+
| x | str_x |
+-----------+-------+
| 123000 | 0 |
| 1300 | 1 |
| 135600 | 1 |
| 135623400 | 1 |
| 13560 | 1 |
| 135160 | 1 |
| 13514560 | 1 |
| 1351120 | 1 |
| 13512310 | 1 |
| 132134534 | 2 |
+-----------+-------+
10 rows in set (0,00 sec)
mysql>

Approximate selection between decimal values in perl-DBI

I was developed database table by using this query
create table test(
id varchar(10),
alpha varchar(1000),
marks decimal(10,4)
);
In the marks should use decimal datatypes.
marks column
1100.2523
1100.2722
733.8375
1192.257
587.6248
392.0859
I should use to select query
select * from test where marks="1100.2625";
and
select * from test where marks="392.0252";
The output is Empty set (0.00 sec)
Expected Output is
1100.2523 1100.2722 and 392.0859 this rows.
If possible to select between decimal range.?
This is usually not possible using the query and for that you need to define a benchmark for the approximation.
Now if you have a bench mark set for the approximation and lets say its something as
If the marks and compared value is same rounding to first decimal point then its a match.
If the after rounding to first decimal point both marks and input and taking the difference the value is 1 or -1 then its a match.
Using the above benchmark it could done.
Consider the following table
mysql> select * from test ;
+------+-------+-----------+
| id | alpha | marks |
+------+-------+-----------+
| 1 | aa | 1100.2523 |
| 2 | bb | 1100.2722 |
| 3 | cc | 392.0859 |
+------+-------+-----------+
mysql> select * from test where round(marks,1) = round(1100.2625,1);
+------+-------+-----------+
| id | alpha | marks |
+------+-------+-----------+
| 1 | aa | 1100.2523 |
| 2 | bb | 1100.2722 |
+------+-------+-----------+
mysql> select * from test
where round(marks,1) = round(392.0252,1)
or round(marks,1) - round(392.0252,1) = 0.1
or round(marks,1) - round(392.0252,1) = -0.1;
+------+-------+----------+
| id | alpha | marks |
+------+-------+----------+
| 3 | cc | 392.0859 |
+------+-------+----------+