SET a variable in SELECT statement - MySQL - mysql

I'm using this code which has an error:
SET #rejects = '';
SELECT *
FROM list
WHERE maker = 1
AND by_ids IN ('10','11')
AND country LIKE '%I%'
AND (
src IS NULL
|| src NOT IN (#rejects)
AND checkSrc(src) = 'yes'
AND SET #rejects = CONCAT(#rejects,',',src)
);
What's causing the issue?

The issue is that you cannot mix select and set in one statement, there'll surely be syntax error:
select*from t where 1 and set#a=1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'set#a=1' at line 1
If you want to do set within select, use the colon equals syntax. Change this:
select*from t where 1 and set#a=1;
into:
select*,#a:=1 from t where 1;
Here's how you update the variable upon each row:
create table t(id int); insert t values(1),(2),(3);
set#a=0;
select#a:=id from t;
+--------+
| #a:=id |
+--------+
| 1 |
| 2 |
| 3 |
+--------+
And you can even do concat:
set#a='0';
select #a:=concat(#a,',',id)from t;
+-----------------------+
| #a:=concat(#a,',',id) |
+-----------------------+
| 0,1 |
| 0,1,2 |
| 0,1,2,3 |
+-----------------------+
Or concat without the leading 0:
set#a='';
select #a:=concat(#a,if(#a='','',','),id)from t;
+------------------------------------+
| #a:=concat(#a,if(#a='','',','),id) |
+------------------------------------+
| 1 |
| 1,2 |
| 1,2,3 |
+------------------------------------+
However, the manual explicitly states that this is dangerous:
...you should never assign a value to a user variable and read the
value within the same statement...
...you might get the results you expect, but this is not
guaranteed.
...the order of evaluation for expressions involving user variables is
undefined.
This has also been mentioned on Xaprb.
Lastly, if you're doing quirky things like assigning differing value types to the variable and etc, checkout the manual to be sure you understand the intricate mechanisms.

Then you might write your query like this.
SET #rejects = '';
SELECT #rejects = CONCAT(#rejects,',',src) FROM list WHERE maker = 1 AND by_ids IN ('10','11') AND country LIKE '%I%' AND
(src IS NULL OR src NOT IN (#rejects) AND checkSrc(src) = 'yes');
SELECT #rejects;

Related

MySQL string splitting on delimiters

Based on https://stackoverflow.com/a/59666211/4250302 I created the stored function get_enum_item for future processing the lists of possible values in the ENUM() type fields.
It works fine enough, but... but I can't determine what to do if the delimiter itself is the part of a string being split. For example:
(square brackets are for readability)
mysql> set #q=",v1,',v2'" --empty string, "v1", "comma-v2";
mysql> select concat('[',get_enum_item(#q,',',0),']') as item;
+------+
| item |
+------+
| [] |
+------+
it is OK
mysql> select concat('[',get_enum_item(#q,',',1),']') as item;
+------+
| item |
+------+
| [v1] |
+------+
it is also OK
mysql> select concat('[',get_enum_item(#q,',',2),']') as item;
+------+
| item |
+------+
| ['] |
+------+
It is not OK
the #q contains 3 commas, the first two of these are real delimiters, while the last one is the part of the third possible value: "comma-v-two". And I have no idea how to avoid confusion of splitting function. MySQL WorkBench in the "form editor" mode solves this trouble somehow, but how can I solve this with MySQL's code?
Well, I can rely on the fact that the show_columns-like queries show the enums in "hardcoded" manner:
select column_name,column_type
from information_schema.columns
where data_type='enum' and table_name='assemblies';
+--------------+------------------------------------------------------------------+
| COLUMN_NAME | COLUMN_TYPE |
+--------------+------------------------------------------------------------------+
| AssetTagType | enum('','И/Н','Н/Н',',fgg') |
| PCTagType | enum('','И/Н','Н/Н') |
| MonTagType | enum('','И/Н','Н/Н') |
| UPSTagType | enum('','И/Н','Н/Н') |
| OtherTagType | enum('','И/Н','Н/Н') |
| state | enum('в работе','на списание','списано') |
+--------------+------------------------------------------------------------------+
Thus I can try to use ',' as a delimiter, but this will not save me from the case if the "comma-apostrophe" combination is the part of possible value... :-(
The only thing I can imagine is to count apostrophes and if the delimiting comma is after the even number of ''s, then it is the delimiter, while if it follows an odd number of ''s, it is the part of the value.
And I can't invent anything except for dumb scanning the input string inside the loop. But maybe there are some other suggestions to get the values split correctly?
Please, don't suggest use PHP, Python, AWK, and so on. The query will be executed from the Pascal (Lazarus, CodeTyphoon) application, and calling external processors is highly unsafe.
As a last resort, I can process the column_type with Pascal's code, but at first, I must make myself sure that the task is not solvable by MySQL's features.
edit:
select column_type from information_schema.columns
where column_name='assettagtype' and table_name='assemblies';
+------------------------------------------+
| COLUMN_TYPE |
+------------------------------------------+
| enum('','И/Н','Н/Н',''''',fgg','''') |
+------------------------------------------+
1 row in set (0.00 sec)
Fourth field: '',fgg, fifth field: '
set #q="'в работе','на списание','списано'";
WITH RECURSIVE cte as (
select 1 as a union all
select a+1 from cte where a<35
)
select distinct regexp_substr(#q,'''[^,]*''',a) as E from cte;
Too high values for 35 raise an error ERROR 3686 (HY000): Index out of bounds in regular expression search.. (I created a bug for this)
The null value should be filtered out... 😉
output:
E
'в работе'
'на списание'
'списано'
null
EDIT: With some effort, this also works for a more complex example (not for every "staged" example!)
set #q="'в работе','на списание','списано',''',fgg'";
select #q;
WITH RECURSIVE cte as (
select 1 as a union all
select a+1 from cte where a<35
)
select distinct regexp_substr(#q,'(''([^,]|[^''][^''])*'')',a) E from cte;
output:
E
'в работе'
'на списание'
'списано'
''',fgg'

How to convert select statement result to JSONARRAY and update another table

I want to convert select result to JSON and write it to another table:
update patrol_patrol a, position_user b
set a.route = json_array(select coordinate from b )
where a.id = 1;
and get error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select coordinate from b ) where a.id = 1' at line 2
select route from patrol_patrol;
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| route |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ["112.58006496213066,22.311484443420195"] |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set
select coordinate from position_user;
+---------------------------------------+
| coordinate |
+---------------------------------------+
| 112.701036,22.738611 |
| 112.701036,22.738632 |
| 112.701036,22.738632 |
| 112.701036,22.738652
position_user.coordinate should be ["112.701036,22.738611", "112.701036,22.738632", "112.701036,22.738652", ....] after update
Since you're only updating the patrol_patrol table, you should only include that in the first part of your update statement. To get what you're looking for, I recommend using the JSON_ARRAYAGG function, which will combine your results into one array, which can then be used to assign the result to a.route:
UPDATE patrol_patrol a
SET a.route = (SELECT JSON_ARRAYAGG(coordinate) FROM position_user)
WHERE a.id = 1;
A dbfiddle can be found here demonstrating this approach.

Using Self-Join to find differences between rows

I have tried finding a solution to this question, but everything I've found has either asked a slightly different question or hasn't had an adequate answer. I have a table with the following setup:
fullvna
+--------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+----------------+
| startdate | date | YES | | NULL | |
| starttime | time | YES | | NULL | |
| id | int(11) | NO | PRI | NULL | auto_increment |
+--------------+-------------+------+-----+---------+----------------+
I want to find the time difference between each pair of consecutive lines, so the starttime of id=1 minus the starttime of id=2 (the table is ordered in reverse chronological order). I based my query off of what I found here: http://www.mysqltutorial.org/mysql-tips/mysql-compare-calculate-difference-successive-rows/
create table difference as SELECT
one.id,
     one.starttime,
     two.starttime,
    (one.starttime - two.starttime) AS diff
FROM
     fullvna one
        INNER JOIN
    fullvna two ON two.id = one.id + 1;
I'm receiving the following printout, and am not sure what it means or what I'm doing wrong:
ERROR 1064 (42000): You have an error in your SQL syntax; check the
manual that corresponds to your MySQL server version for the right
syntax to use near '  one.starttime,
    two.starttime,
    (one.starttime - two.starttime' at line 3
You have hidden characters that are displayed as spaces, but they're not and they're causing the error. Copy the query from my answer. And as Juan suggested, it is recommended to use the TIMEDIFF() function instead of subtracting them:
CREATE TABLE difference AS
SELECT one.id,
one.starttime AS starttime,
two.starttime AS endtime,
TIMEDIFF(one.starttime, two.starttime) AS diff
FROM fullvna one
INNER JOIN fullvna two ON two.id = one.id + 1;
EDIT As xQbert mentioned, you need to use different names for the starttime column, so I corrected the query above accordingly.
Don't use alias one as it's a keyword pick a different one
alias startime as two columns with same name in a create table will not work.
timediff (as others mentioned in comments)
.
CREATE TABLE difference as
SELECT a1.id
, a1.starttime as OneStartTime
, a2.starttime as TwoStartTime
, TIMEDIFF(a1.starttime, a2.starttime) AS diff
FROM fullvna a1
INNER JOIN fullvna a2
ON a2.id = a1.id + 1;

Copying all entries from one table to another with a hard coded value

I currently am copying multiple tables from different mysql schemas into 1 table. While trying to copy all of the entries, I am having issues with the "Insert" into temp table.
cua010.doc_table
| ID | _FilePath |
testing.temp_entries
| ID | File | Schema |
Here is my query
INSERT INTO testing.temp_entries (File, Schema )
SELECT _FilePath, 'CU010'
FROM cua010.doc_table
In the end I would like to results to be
| ID | File | Schema |
| 1 | test | cua010 |
| 2 | test2| cua010 |...
This is the error message i get
0 84 14:49:47 INSERT INTO testing.temp_entries (File, Schema )
SELECT _FilePath, 'cua010'
FROM cua010.doc_table Error Code: 1064. You have an error in your SQL
syntax; check the manual that corresponds to your MySQL server version for
the right syntax to use near 'Schema)
SELECT _FilePath, 'cua010'
FROM cua010.doc_table' at line 1 0.031 sec
use "`" around schema (alt +96 in windows)
INSERT INTO testing.temp_entries (File, `Schema` )
SELECT _FilePath, 'cua010'
FROM cua010.doc_table ;
Schema is a reserved word.
INSERT INTO `testing`.`temp_entries` (`File`, `Schema` )
SELECT `_FilePath`, 'CU010'
FROM `cua010`.`doc_table`

strict mode to avoid type conversion

Is there any sql mode that will return an error instead of implicitly converting the string to integer?
mysql> select * from todel ;
+------+--------+
| id | name |
+------+--------+
| 1 | abc |
| 2 | xyz |
| 0 | ABCxyz |
+------+--------+
3 rows in set (0.00 sec)
I expect an error message instead of a row with id 0
mysql> select * from todel where id = 'abc';
+------+--------+
| id | name |
+------+--------+
| 0 | ABCxyz |
+------+--------+
1 row in set, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+-----------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'abc' |
+---------+------+-----------------------------------------+
1 row in set (0.01 sec)
I understand your concerns, but it's for this very reason you should never have an id set to 0. In the long run I think you should reconsider your table rows before the behavior which isn't a problem in ideal situations. I haven't found anything relevant to this through a little searches, and that's probably because it's probably not a problem unless you make it one.
Apart from that, you could read relevant column data and act accordingly in php/whatev. From the table COLUMNS in information_schema, you can filter by TABLE_SCHEMA (database), TABLE_NAME and COLUMN_NAME to get DATATYPE (double). If the column you're changing has a certain DATATYPE, let the script give error before running the MySQL query.
Another way to do it would simply be to convert input before parsing:
if ( ! is_numeric($id))
$id = 'NULL';
To prevent incorrect INSERTs or UPDATEs, you already have that mode.
In the end I can't come up with many practical ways that this strict mode you're after would benefit the MySQL users.
You can use STRICT_ALL_TABLES sql mode:
set ##GLOBAL.sql_mode = "STRICT_ALL_TABLES";
set ##SESSION.sql_mode = "STRICT_ALL_TABLES";
However it works just on write operations:
MariaDB [(none)]> insert into test.test values ( "abc", "lol" );
--------------
insert into test.test values ( "abc", "lol" )
--------------
ERROR 1366 (22007): Incorrect integer value: 'abc' for column 'id' at row 1
There is no such thing to disable implicit conversions for read queries; instead you can just check if there are warnings and if yes, just free the result, abort the statement, and threat those warnings as errors.