mysql GROUP BY clause validation - mysql

Suppose I have a table with the following content:
mysql> select * from test;
+----+------+
| id | val |
+----+------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
| 6 | 2 |
| 7 | 2 |
| 8 | 2 |
+----+------+
8 rows in set (0.00 sec)
mysql>
Now I run erroneous SQL query with group by clause and without any aggregation on id column and get wrong results:
mysql> select id, val from test group by val;
+----+------+
| id | val |
+----+------+
| 1 | 1 |
| 5 | 2 |
+----+------+
2 rows in set (0.00 sec)
mysql>
Can mysql client or probably some other tool validate this query and issue error or warning on using group by without aggregation?

Yes, you can do this:
To disable the MySQL GROUP BY extension, enable the ONLY_FULL_GROUP_BY
SQL mode.
mysql> SET sql_mode = 'ONLY_FULL_GROUP_BY';
See the documentation here. Also, this section on server modules may help.

By default, the SQL Mode ONLY_FULL_GROUP_BY is set to disable. And that is the reason why your query did run without throwing any exceptions. If you don't want that behavior enable the ONLY_FULL_GROUP_BY mode,
ONLY_FULL_GROUP_BY
Setting the SQL Mode
You can set the default SQL mode by starting mysqld with the --sql-mode="modes" option, or by using sql-mode="modes" in my.cnf (Unix operating systems) or my.ini (Windows). modes is a list of different modes separated by comma (“,”) characters.
You can change the SQL mode at runtime by using a SET [GLOBAL|SESSION] sql_mode='modes' statement to set the sql_mode system value. ex: SET sql_mode = 'ONLY_FULL_GROUP_BY'

Related

MYSQL after QA Migration to Maria DB, application query returns different cases for a column result set meta data

Were attempting to migrate some mysql servers to MariaDB on a new AWS (amazon-linux-2) instance.
After creating a new instance and installing mariadb we've dumped the old database via
mysqldump ... --add-drop-database --triggers --routines --events
and imported into the new system using
mysql -u root < dump.sql
The database has a table defined with mixture of uppercase and lower case columns e.g COLUMN1,COLUMN2,column3,etc
The original database, via mysqld -V, is mysqld Ver 14.14 Distrib 5.5.62, for Linux (x86_64)
The new database is, via mysqld -V, is mysqld Ver 15.1 Distrib 5.5.64-MariaDB, for Linux (x86_64)
installed with yum install mariadb-server
I've verified the schema's columns match for the table in question.
The accessing application queries the table with a select statement requesting the columns as lowercase.
(I've connected a local application and tested executing the same query against both database)
e.g. SELECT column1,column2,column3,etc from TABLE1 where ..
The results from the query from the original database respect the casing in the "select" statement from the application, however in the new mariadb database, the query results match the column casing as defined in the table.
Is there a setting that can be applied that will cause the result column names to respect the casing in the "SELECT" statement, and therefore cause the result column names returned by the same select statement in both databases to match?
A testcase under MariaDB and Mysql
select cname from (select s.CName from (select 'A' as CNAME) s) t;
Under MariaDB the result set column casing matches the column from the intermediary temp table.
With the same query using the source mysql database,
The result set column name's casing matches the casing in the select statement
It's always a bad practice not to use coding styles for SQL and mixing lowercase, uppercase or mixed case identifiers ("It was hard to write, therefore it should be hard to read"). There are several coding style guidelines, e.g. SQL Style Guide.
Beside that, you should also consider to upgrade to a more recent version of MariaDB/MySQL. MariaDB 5.5 will be eoled in 3 months.
But back to your problem:
Column identifiers are case insensitive, therefore the optimizer recognizes that your SQL statement can be simplified, in more recent versions of MariaDB the EXPLAIN EXTENDED and SHOW WARNINGS commands will give you more information:
MariaDB [(none)]> select version();
+--------------------------+
| version() |
+--------------------------+
| 10.5.1-MariaDB-debug-log |
+--------------------------+
MariaDB [(none)]> explain extended select cname from (select s.CName from (select 'A' as CNAME) s) t;
+------+-------------+------------+--------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+-------------+------------+--------+---------------+------+---------+------+------+----------+----------------+
| 1 | PRIMARY | <derived3> | system | NULL | NULL | NULL | NULL | 1 | 100.00 | |
| 3 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+------+-------------+------------+--------+---------------+------+---------+------+------+----------+----------------+
2 rows in set, 1 warning (0.00 sec)
MariaDB [(none)]> show warnings;
+-------+------+------------------------------------------------+
| Level | Code | Message |
+-------+------+------------------------------------------------+
| Note | 1003 | /* select#1 */ select 'A' AS `CName` from dual |
+-------+------+------------------------------------------------+
As you can see, the statement was optimized and transformed to select 'A' AS `CName` from dual.
Obviously in MariaDB 5.5 the optimizer was already more modern than the MySQL optimizer, but in recent version of MySQL you will get the same result:
mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.13 |
+-----------+
1 row in set (0,00 sec)
mysql> explain select cname from (select s.CName from (select 'A' as CNAME) s) t;
+----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+----------------+
| 1 | PRIMARY | <derived3> | NULL | system | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 3 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+----------------+
2 rows in set, 1 warning (0,00 sec)
mysql> show warnings;
+-------+------+------------------------------------------------+
| Level | Code | Message |
+-------+------+------------------------------------------------+
| Note | 1003 | /* select#1 */ select 'A' AS `CName` from dual |
+-------+------+------------------------------------------------+

Mysql - InnoDB - Possible Key NULL when using function in an Update Query

BACKGROUND
I am working with a high traffic application which seems is extremely slow when executing the following.
Below is a description of my problem:
I have the following function defined:
CREATE FUNCTION getTableXMax() RETURNS INT
BEGIN
DECLARE NUM INT DEFAULT 0;
SELECT COALESCE((SELECT MAX(ID) FROM TABLE_X),0) INTO NUM;
RETURN NUM;
END //
TABLE_X has more than 30 million entries.
PROBLEMATIC QUERY
mysql> UPDATE TABLE_X SET COST = 0 WHERE ID=49996728;
-> //
Query OK, 1 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
mysql> UPDATE TABLE_X SET COLUMN_X=0 WHERE ID=getTableXMax();
-> //
Query OK, 1 rows affected (1 min 23.13 sec)
Rows matched: 1 Changed: 0 Warnings: 0
------- QUESTION -----------
As you can see above, the problem is that the query below takes more than a minute to execute when using the mysql function. I want to understand why this happens (although overall implementation might be bad).
------- DEBUG --------------
I run some EXPLAIN queries to check the possible_keys that mysql uses in order to perform the search. As you can see below the query that uses the function has a NULL value for possible_keys - thus I assume the why the problem exists is probably answered. The questions remaining is how to fix it, and what is the reason.
mysql> EXPLAIN UPDATE TRANSCRIPTIONS SET COST = 0 WHERE ID=12434;//
+----+-------------+----------------+-------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------------+-------+---------------+---------+---------+-------+------+-------------+
| 1 | SIMPLE | TRANSCRIPTIONS | range | PRIMARY | PRIMARY | 4 | const | 1 | Using where |
+----+-------------+----------------+-------+---------------+---------+---------+-------+------+-------------+
1 row in set (0.00 sec)
mysql> EXPLAIN UPDATE TRANSCRIPTIONS SET COST = 0 WHERE ID=getTableXMax();//
+----+-------------+----------------+-------+---------------+---------+---------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------------+-------+---------------+---------+---------+------+----------+-------------+
| 1 | SIMPLE | TRANSCRIPTIONS | index | NULL | PRIMARY | 4 | NULL | 38608423 | Using where |
+----+-------------+----------------+-------+---------------+---------+---------+------+----------+-------------+
MYSQL VERSION
+-------------------------+------------------------------+
| Variable_name | Value |
+-------------------------+------------------------------+
| innodb_version | 5.6.34 |
| protocol_version | 10 |
| slave_type_conversions | |
| version | 5.6.34 |
| version_comment | MySQL Community Server (GPL) |
| version_compile_machine | x86_64 |
| version_compile_os | Linux |
+-------------------------+------------------------------+
I hope my question was thorough enough.
I think that
UPDATE TABLE_X
SET COLUMN_X=0
ORDER BY ID DESC
LIMIT 1
is enough. And the function is not needed at all.
If you want to save the function and the logic then use
UPDATE TABLE_X,
( SELECT getTableXMax() criteria ) fn
SET COLUMN_X=0
WHERE ID=criteria;
But as the first step - try to define the function as DETERMINISTIC.
I think the problem is that the MySQL engine doesn't realize that getTableXMax() always returns the same value. So rather than calling the function once, and then finding that row in the index to update it, it scans the entire table, calling getTableXMax() for each row, and compares the result with ID to determine if it should update that row.
Declaring the function DETERMINISTIC should probably help this. This tells the optimizer that the function always returns the same value, so it only needs to call it once rather than for every row in the table.
The rewrites in Akina's answers will also work, and you could also use a variable:
SET #maxID = getTableXMAx();
UPDATE TABLE_X
SET COLUMN_X = 0
WHERE ID = #maxID;

MySQL command output too wide in command-line client [duplicate]

This question already has answers here:
How to best display in Terminal a MySQL SELECT returning too many fields?
(12 answers)
Closed 3 years ago.
I am using mysql command line client in terminal emulator lxterminal in Ubuntu. When I run the following command:
mysql> select * from routines where routine_name = "simpleproc";
The output is a mess:
But if I copy and paste it here, the output shows me a nice table:
mysql> select * from routines where routine_name = "simpleproc";
+---------------+-----------------+----------------+--------------+--------------+-----------+--------------------------+------------------------+-------------------+---------------+--------------------+--------------------+----------------+----------------+--------------+--------------------------------------------------------+---------------+-------------------+-----------------+------------------+-----------------+----------+---------------+---------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------+-----------------+----------------+----------------------+----------------------+--------------------+
| SPECIFIC_NAME | ROUTINE_CATALOG | ROUTINE_SCHEMA | ROUTINE_NAME | ROUTINE_TYPE | DATA_TYPE | CHARACTER_MAXIMUM_LENGTH | CHARACTER_OCTET_LENGTH | NUMERIC_PRECISION | NUMERIC_SCALE | DATETIME_PRECISION | CHARACTER_SET_NAME | COLLATION_NAME | DTD_IDENTIFIER | ROUTINE_BODY | ROUTINE_DEFINITION | EXTERNAL_NAME | EXTERNAL_LANGUAGE | PARAMETER_STYLE | IS_DETERMINISTIC | SQL_DATA_ACCESS | SQL_PATH | SECURITY_TYPE | CREATED | LAST_ALTERED | SQL_MODE | ROUTINE_COMMENT | DEFINER | CHARACTER_SET_CLIENT | COLLATION_CONNECTION | DATABASE_COLLATION |
+---------------+-----------------+----------------+--------------+--------------+-----------+--------------------------+------------------------+-------------------+---------------+--------------------+--------------------+----------------+----------------+--------------+--------------------------------------------------------+---------------+-------------------+-----------------+------------------+-----------------+----------+---------------+---------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------+-----------------+----------------+----------------------+----------------------+--------------------+
| simpleproc | def | test | simpleproc | PROCEDURE | | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | SQL | BEGIN
SELECT COUNT(*) INTO param1 FROM CUSTOMERS1;
END | NULL | NULL | SQL | NO | CONTAINS SQL | NULL | DEFINER | 2018-01-12 15:18:20 | 2018-01-12 15:18:20 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | | root#localhost | utf8 | utf8_general_ci | latin1_swedish_ci |
+---------------+-----------------+----------------+--------------+--------------+-----------+--------------------------+------------------------+-------------------+---------------+--------------------+--------------------+----------------+----------------+--------------+--------------------------------------------------------+---------------+-------------------+-----------------+------------------+-----------------+----------+---------------+---------------------+---------------------+-------------------------------------------------------------------------------------------------------------------------------------------+-----------------+----------------+----------------------+----------------------+--------------------+
1 row in set (0.01 sec)
I wonder if it is possible to view the output in a terminal emulator as a nice table like this one?
Using mysql's ego command
From mysql's help command:
ego          (\G) Send command to mysql server, display result vertically.
So by appending a \G to your select, you can get a very clean vertical output:
mysql> select * from routines where routine_name = "simpleproc" \G
Using a pager
You can tell MySQL to use the less pager with its -S option that chops wide lines and gives you an output that you can scroll with the arrow keys:
mysql> pager less -S
Thus, next time you run a command with a wide output, MySQL will let you browse the output with the less pager:
mysql> select * from routines where routine_name = "simpleproc";
If you're done with the pager and want to go back to the regular output on stdout, use this:
mysql> nopager
You can try also adjusting the font size of the terminal but displaying the output vertically should be clear if all doesn't.
use the /G option to run the query i.e
mysql> select * from routines where routine_name = "simpleproc" \G
If you are running on Ubuntu you can use the bash shell, it looks nice and not messed up like this.

What of these responses to this sql query, is more correct as per the sql specification?

What of these responses to this sql query, is more correct as per the sql specification?
Here is the response from the later mysql version
C:\wamp\bin\mysql\mysql5.7.11\bin>mysql.exe --version
mysql.exe Ver 14.14 Distrib 5.7.11, for Win32 (AMD64)
mysql> select * from blah6;
+------+------+
| col1 | col2 |
+------+------+
| 6 | 8 |
| 7 | 8 |
| 9 | 3 |
| 6 | 3 |
+------+------+
4 rows in set (0.01 sec)
mysql> select sum(col1),col2 from blah6;
+-----------+------+
| sum(col1) | col2 |
+-----------+------+
| 28 | 8 |
+-----------+------+
1 row in set (0.00 sec)
mysql>
vs
Here, the response from an earlier mysql version
C:\wamp\mysql\bin>mysql.exe --version
mysql.exe Ver 14.12 Distrib 5.0.27, for Win32 (ia32)
mysql> select * from asdf2;
+------+------+
| col1 | col2 |
+------+------+
| 5 | 6 |
| 9 | 3 |
| 29 | 13 |
| 44 | 13 |
| 44 | 53 |
| 5 | 33 |
+------+------+
6 rows in set (0.00 sec)
mysql> select sum(col1),col2 from asdf2;
ERROR 1140 (42000): Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal if there is no GROUP BY clause
The comments cover the salient points: The first is a (mis)feature in MySQL that causes confusion and generally encourages bad coding practices. The error is the correct behavior.
I want to point out that this has nothing to do with the MySQL version. Both operate the same way. The question is whether the model ONLY_FULL_GROUP_BY is set. When this mode is set, then MySQL better follows the ANSI standard for aggregation queries (read about it here).
Note: this mode might be set using the more comprehensive ANSI mode.

SELECT ... INTO fails for one variable, but not for two

Running a fairly old version of MySQL:
mysql> SELECT ##version;
+-----------+
| ##version |
+-----------+
| 5.0.77 |
+-----------+
Afraid I'm not at liberty to update it, so I acknowledge the easy answer may be "Upgrade MySQL to a newer version."
When I do SELECT...INTO query which should return a single row with only one column I get an error:
mysql> SELECT id
INTO #active_id
FROM a_table
WHERE active = 1
LIMIT 1
;
> Undeclared variable: id
But if I add an additional column, it works fine:
mysql> SELECT id, 42
INTO #active_id, #ignored
FROM a_table
WHERE active = 1
LIMIT 1
;
> Query OK, 1 row affected (0.00 sec)
mysql> SELECT #active_id, #ignored;
+------------+----------+
| #active_id | #ignored |
+------------+----------+
| 1 | 42 |
+------------+----------+
Is there a better workaround for this? Is my syntax wrong somehow?