An explanation for BNF rule - mysql

I'm investigating the mysql's SQL parser at the moment.
And here is the interesting thing I have noticed and cannot explain:
(in sql_yacc.yy)
predicate:
...
| bit_expr BETWEEN_SYM bit_expr AND_SYM predicate
{
$$= new (YYTHD->mem_root) Item_func_between($1,$3,$5);
if ($$ == NULL)
MYSQL_YYABORT;
}
The same is on the Expression Syntax page:
predicate:
...
| bit_expr [NOT] BETWEEN bit_expr AND predicate
That means that
foo BETWEEN 1 AND bar BETWEEN 1 AND 2
query syntactically correct, while it makes no sense at all.
My question: what this could be used for? What would we miss if used
bit_expr [NOT] BETWEEN bit_expr AND bit_expr
instead?
LOL (not a LOL anymore actually)
this query executes WITHOUT errors:
select * from users where id between 1 and id between 1 and 10;
// returns row with id = 1
select * from users where id between 2 and id between 2 and 10;
Empty set (0.00 sec)
(update is added here) ... and actually it is expected.
Presumably it converts the second expression straight to 0 or 1 and uses it as an operand.
UPD:
I've filed a bug - http://bugs.mysql.com/bug.php?id=69208
It's definitely not an expected syntax at all
UPD 2: so looks like it's just a minor typo that doesn't change a parser behaviour at all (well, to be clear it makes it unnoticeable slower for a common BETWEEN expression).

Your analysis is basically correct:
foo BETWEEN 1 AND bar BETWEEN 1 AND 2
is parsed as:
foo BETWEEN 1 AND (bar BETWEEN 1 AND 2)
and the second (parenthesized) predicate will presumably evaluate to either 0 or 1 (for false or true). Therefore, if bar is not between 1 and 2, the set of selected values from foo will be empty (because foo BETWEEN 1 AND 0 is a shorthand for foo >= 1 AND f <= 0 and there are no values for which that is true, even allowing for NULLs). Contrariwise, if bar is between 1 and 2, then the set of selected values from foo will be the set where `foo1 equals 1.
And alternative question to your "what would you lose if you replaced the 'predicate' term with 'bit_expr'?" might be "would you gain anything if you replaced the 'bit_expr' with 'predicate'?"
Without a careful scrutiny of the complete grammar (or, at least, scrutiny of the parts referenced by bit_expr and predicate, and possibly a review of places where bit_expr and predicate are used), it is hard to know the answer to either question.

Related

Conditional WHERE statements based on parameters?

I am trying to conditionally create a WHERE clause in a stored procedure. It is to act as a filter on a boolean column and there's only two outcomes I want - either only take the true values, or take all of them (no situation where I only need the false values).
The clause I am trying to use is this -
WHERE
(#customerInactive = -1 OR `listcustomers`.`active` = 1)
with the idea either the parameter is a -1 (no filter) or we do have a filter and should do listcustomers.active = 1.
I tried being more explicit as well
WHERE
((#customerInactive = 1 AND `listcustomers`.`active` = 1) OR
(#customerInactive <> 1 AND 1=1))
The second one ends up not returning anything then. How can I fix this?
This is in a stored procedure, using MySQL 5.6.
Edit: Given that I've been told my first query should do it, but it always returns listcustomers.active = 1, is this possibly a type issue? I made customerInactive a int(11). I also just tried it as a bit(1) but I am still getting the same issue, no matter my paraemter I get the TRUE filter. Or in the case of the second query, no results.
Edit 2: I don't think this should matter but the final result is the union of multiple tables of which I am going to have to do this same sort of filtering. The whole SQL query can be seen here - https://pastebin.com/6wL4ZtnF
Considering your comments, this is the results you want depending of customerInactive and the value of listcustomers.active
#customerInactive | listcustomers.active | result
------------------+----------------------+--------
0 | 0 | 1
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0
This is a NAND (Not And) that can be written such way in SQL :
NOT (#customerInactive = 1 AND listcustomers.active = 1)
The correct answer is that I confused parameters and user defined variables. I am passing in customerInactive as a parameter to the stored procedure, so I should reference it directly by name. Using the # made it look like a User defined variable which was NOT defined, hence the constant failing of my initial equality and why the true filter was always on. Thanks everyone.

Creating an encryption in MySQL

I am trying to create a an encryption in MySQL.
Lets say there is a string of characters.
"I can run for as many".
I want to replace each letter with its fourth letter.
For example,
'a' replaced with 'e'
'b' replaced with 'f'
and so on.
The Final output of the above would look something like this.
"M gen vyr jsr ew qerc"
The best I could come up is the below.
Since it is a nested function, it is not giving me the right result.
The below function replaces 'a' with 'e' and then again replaces 'e' with 'i'
until it reaches the end.
select messagetext,
replace(replace(replace(replace(replace(replace(replace(replace
(replace(replace(replace(replace(replace(replace(replace
(replace(replace(replace(replace(replace(replace(replace
(replace(replace(replace(replace(messagetext,'a','e'),
'b','f'),'c','g'),'d','h'),'e','i'),'f','j'),'g','k'),
'h','l'),'i','m'),'j','n'),'k','o'),'l','p'),'m','q'),
'n','r'),'o','s'),'p','t'),'q','u'),'r','v'),'s','w'),
't','x'),'u','y'),'v','z'),'w','a'),'x','b'),'y','c'),
'z','d')
from chat;
Any help would be much appreciated.
Reached partial solution, now stuck.
Putting it here so that others can work on this.
Query:
SELECT UNHEX(HEX(val) + REPEAT('04', LENGTH(val))) AS rot4 FROM caeser;
+--------+
| rot4 |
+--------+
| efghip |
| aq?? | <-- Need to rotate / mod hex value for this. (Stuck here)
+--------+
Table create queries:
CREATE TABLE caeser (val VARCHAR(255));
INSERT INTO caeser ('abcdef');
INSERT INTO caeser VALUES ('uvwxyz');
PS: will convert to community wiki, if others also contribute.

MySQL query seems to give different results when wrapped in perl DBI

The query below works fine in when I try it in the console.
mysql> SELECT COUNT(l.ID), a.MAX_PER_PRD, a.PLURAL, d.TIME_DENOM FROM logro l, challenge c, lib_accomp_type a, lib_deporte d WHERE l.PERIOD=3 AND l.GAME_ID=2 AND l.PLR_ID=3 AND l.ACC_TYPE_ID=11 AND a.sport=d.ID AND c.ACC_TYPE_ID=a.ID AND l.ACC_TYPE_ID=c.ACC_TYPE_ID;
+-------------+-------------+--------------------+------------+
| COUNT(l.ID) | MAX_PER_PRD | PLURAL | TIME_DENOM |
+-------------+-------------+--------------------+------------+
| 0 | 3 | general commodities| quarter |
+-------------+-------------+--------------------+------------+
1 row in set (0.01 sec)
However, when I wrap it in a perl->DBI statement handle and fetch it with $sth->fetchrow_array the second value is undefined.
my $q = "SELECT COUNT(l.ID), a.MAX_PER_PRD, a.PLURAL, d.TIME_DENOM
FROM logro l, challenge c, lib_accomp_type a, lib_deporte d
WHERE l.PERIOD=?
AND l.GAME_ID=?
AND l.PLR_ID=?
AND l.ACC_TYPE_ID=?
AND a.sport=d.ID
AND c.ACC_TYPE_ID=a.ID
AND l.ACC_TYPE_ID=c.ACC_TYPE_ID";
my $sth = $dbh->prepare($q);
$sth->execute(3, 2, 3, 11);
my ($CNT, $MAX, $ANAMEP, $TD) = $sth->fetchrow_array;
print "COUNT: ", $CNT;
print "MAX: ", $MAX;
$ perl test_sql2.pl
Use of uninitialized value $MAX in print at test_sql2.pl line 29.
COUNT: 0MAX:
Any idea as to what I could be doing wrong?
Depending on your mysql client/library version, mysql handles this situation differently.
For mysql <= 5.6, see Group By Handling
For mysql = 5.7, see Group By Handling
You query has a count of 0, yet there are values returned for the other columns. That doesn't seem to make any sense. It seems that running through perl is actually doing the logical thing and mysql is just populating the MAX_PER_PRD, PLURAL, TIME_DENOM columns with arbitrary values.
The main issue here, is that you referencing non-aggregated columns without them being part of a group by clause.
Perhaps if you include a sample data set, it could help us get to the result your are looking for.

How to define a custom ORDER BY order in mySQL

In MySQL how do I define a custom sorting order.
To try to explain what I want consider this table:
ID Language Text
0 ENU a
0 JPN b
0 DAN c
1 ENU d
1 JPN e
1 DAN f
2 etc...
here I want to return all rows sorted by Language and ascending ID so that Language = ENU comes first, then JPN and lastly DAN.
The result should be: a,d,b,e,c,f etc.
Is this even possible?
MySQL has a handy function called FIELD() which is excellent for tasks like this.
ORDER BY FIELD(Language,'ENU','JPN','DAN'), ID
Note however, that
It makes your SQL less portable, as other DBMSs might not have such function
When your list of languages (or other values to sort by) gets much longer, it's better to have a separate table with sortorder column for them, and join it to your queries for ordering.
If those are the only three values, then you can use a CASE expression:
ORDER BY `ID`,
CASE `Language`
WHEN 'ENU' THEN 1
WHEN 'JPN' THEN 2
WHEN 'DAN' THEN 3
END
(If there could be other values, then you may want to add some extra logic to keep the ordering consistent; for example, you might add ELSE 4 to that CASE expression, and then order by Language itself as the third ordering criterion:
ORDER BY `ID`,
CASE `Language`
WHEN 'ENU' THEN 1
WHEN 'JPN' THEN 2
WHEN 'DAN' THEN 3
ELSE 4
END,
`Language`
)
You have a couple of options offhand, the first is to change Language to be ENUM (assuming this is possible, and you only expect a few variations)
If you specify it as ENUM('ENU','JPN','DAN') then ORDER Language ASC will order in the order you specify.
The second will involve a case somewhere, i.e.
SELECT * FROM table
ORDER BY CASE Language
WHEN 'ENU' THEN 3
WHEN 'JPN' THEN 2
WHEN 'DAN' THEN 1
ELSE 0
END DESC, ID ASC
Performance-wise the ENUM method will return faster results, but be more hassle if you need to add more languages. A third option would be to add a normalisation table for the Languages however that may be overkill in this instance.
For Yii2 framework we can achieve by following way
Project::find()
->orderBy([
new Expression('FIELD(pid_is_t_m,2,0,1)'),
'task_last_work'=> SORT_ASC
])->all();

MySql ordering problem

Consider the situation i have a table name "test"
-------
content (varchar(30))
-------
1
abc
2
bcd
-------
if i use order by
Select * from test order by content asc
i could get result like
--------
content
--------
1
2
abc
bcd
---------
but is there any way i could get the following result using query
--------
content
--------
abc
bcd
1
2
---------
To get by the collation, you can do by testing the first character... it appears you want anything starting with a numeric to be after anything alhpa oriented... something like the ISNUMERIC() representation by Ted, but my quick check doesn't show such function in MySQL.. So an alternative... because numerics in ASCII list are less than "A" (char 65)
Select *
from test
order by
case when left( content, 1 ) < "A" then 2 else 1 end,
content
Although I've seen different CONVERT() calls, I don't have MySQL available to confirm. However, in addition to the above case/when, you can add a SECOND case/when and call some UDF() or other convert function on the "content" value. If the string starts as alpha, it should return a zero value so the first case/when will keep them to the top of the list, then since all are all non-convertible to numeric would have a value of zero... no impact on the sort, then finally the content itself which will keep in alpha order.
HOWEVER, if your second case/when / convert function call DOES return a numeric value, then it will be properly sorted within the numeric grouping segment... which will then supercede that of the content... However, if content was something like
100 smith rd and
100 main st
they will sort in the same "100" category numeric value, but then alphabetically by the content as
100 main st
100 smith rd
100
this will do it:
SELECT *
FROM test
ORDER BY CAST(field AS UNSIGNED), field ASC
select * from sometable order by content between '0' and '9', content
Not sure on MySql but on SQL Server you can do this...
SELECT * FROM test
ORDER BY IsNumeric(content), content
The order of results is defined by collation used, so if you can find the right collation then yes.
http://dev.mysql.com/doc/refman/5.0/en/charset-collate.html
//edit
This is tricky. I've done some research and it seems that no currently available collation can do that. However there's also possibility to add new collation to MySQL. Here's how.