Weird results when trying to select not 0 - mysql

In one of my tables I have a column which is varchar(255). The where clause looks like this
SELECT ….. WHERE ctd_ref != 0.
Well this works fine if the value is an integer greater than zero but does not select the entries which have ctd_ref value = N8IJVW
What I'm doing wrong?

If you cast strings to zero, you get zero almost all the times, and even when you don't I don't think you get the results you want:
mysql> select cast('abc' as decimal), cast('' as decimal), cast('7zip' as decimal);
+------------------------+---------------------+-------------------------+
| cast('abc' as decimal) | cast('' as decimal) | cast('7zip' as decimal) |
+------------------------+---------------------+-------------------------+
| 0 | 0 | 7 |
+------------------------+---------------------+-------------------------+
1 row in set, 2 warnings (0.00 sec)
Just don't force such conversions, which I suspect you never intended to do:
SELECT … WHERE ctd_ref <> '0'
... and also try the opposite clause for debugging purposes:
SELECT … WHERE ctd_ref = '0'

If it's a varchar try WHERE ctd_ref != '0'
If you leave it as 0, you're comparing a number to a string field, which will give you, as you say, weird results.

Related

IS !value a shortcut for IS NOT NULL MySQL?

When playing with MySQL I noticed that if I run
SELECT * FROM table WHERE !value;
returns the same thing as
SELECT * FROM table WHERE value IS NOT NULL;
The table is "employees" and the column is "MiddleInitial" (strings).
So I get all employees who's MiddleInitial is not null.
Is this a proper shorthand or just a coincidence? I am wondering if this is a safe way to write? I cannot seem to find any information on this.
I was expecting
SELECT * FROM table WHERE !value;
to return all null values. Oddly enough
SELECT * FROM table WHERE value;
returns nothing.
No, it's not.
It might seem like it because it's doing type coercion to force whatever is in the column into a boolean value. NULL values will coerce to false when forced as a boolean predicate, and most non-null column values will coerce to true. But some column values (that are not null) will also coerce to false.
You can see examples here:
https://dbfiddle.uk/ABLUgLex
Notice the last example is missing the 0 row. Also notice the one before that does not include the null row, which leads me to suspect your server might have an option set for non-standard null handling.
Here's a few more samples:
https://dbfiddle.uk/BNxiujKt
Notice the treatment of the '1' row.
In SQL, NULL is not the same as false.
Negating NULL is not true, it's still NULL.
mysql> select null;
+------+
| NULL |
+------+
| NULL |
+------+
mysql> select !(null);
+---------+
| !(null) |
+---------+
| NULL |
+---------+
Think of NULL as the value "unknown." If some piece of information is unknown, how can its opposite be known? It can't — the opposite is also unknown, because we don't know what we started with.
When used in a WHERE clause condition, NULL acts more or less like false because neither are strictly true. Only rows where the conditions are true become part of the result set of the query.
There are other values that act like false in MySQL:
mysql> select 1 where '';
Empty set (0.01 sec)
mysql> select 1 where 0;
Empty set (0.01 sec)
MySQL is a bit nonstandard because the boolean values true and false are literally the same as the integer values 1 and 0 respectively (this is not the way booleans are implemented in most other brands of SQL database).
These values are not NULL, so they can be negated and you can treat their opposites as true.
mysql> select 1 where !0;
+---+
| 1 |
+---+
| 1 |
+---+
mysql> select 1 where !'';
+---+
| 1 |
+---+
| 1 |
+---+
As the comment above said, the ! operator is deprecated in MySQL 8.0. It's not standard SQL, and using it makes your code less clear than if you use more explicit language like IS NOT NULL or <>.

SQL : Order by when a value looks like a date

Table :
Value:
gte
2016-06-07
aze
2015-07-08
(...)
Important :
Value stores Varchar (because it can be a string, a number, a date etc...)
I need to "sort by" the value that look like a date
Something that i imagine like this ?
ORDER BY (value LIKE "%Y-%m-%d") DESC
If the date is formatted as you describe as YYYY-MM-DD, you just need to remove the - separator. It is then sortable as a string into numeric order.
Try something like
SELECT *
FROM Table
ORDER BY REPLACE(Value,'-','') DESC
if you try to convert it to date you will get null for other none date values
example
mysql> mysql> select STR_TO_DATE('5/15/2012 8:06:26 AM','%c/%e/%Y %r');
+---------------------------------------------------+
| STR_TO_DATE('5/15/2012 8:06:26 AM','%c/%e/%Y %r') |
+---------------------------------------------------+
| 2012-05-15 08:06:26 |
+---------------------------------------------------+
1 row in set (0.00 sec)
mysql> select STR_TO_DATE('dfsddfs','%c/%e/%Y %r');
+--------------------------------------+
| STR_TO_DATE('dfsddfs','%c/%e/%Y %r') |
+--------------------------------------+
| NULL |
+--------------------------------------+
1 row in set, 1 warning (0.00 sec)
1st try to convert it to date and get not null results and the sort using ORDER BY
SELECT * FROM (SELECT STR_TO_DATE(value, "%Y-%e-%c") AS DT FROM TABLE) T WHERE T.DT IS NOT NULL ORDER BY DT;
This should order a recordset by placing all the "date-looking" values at the top. It uses a RegEx pattern in the "LIKE" clause:
SELECT * FROM (
SELECT VALUE,
case when VALUE LIKE '[0-9]{4}-[0-9]{2}-[0-9]{2}' then 1 else 0 END AS LOOKS_LIKE_DATE
FROM TABLE
) ORDER BY LOOKS_LIKE_DATE DESC

MySQL select query with between for second character having range of numbers from 0 to 9

I would like to select data from a table having first character as * and second character having numbers from 0 to 9
I using this code which is working and giving strings like *0123456* *34567* etc.:
SELECT * FROM `MyTable` WHERE SUBSTRING(MyColumn,1,1) = "*" AND
(SUBSTRING(MyColumn,2,1) ="0" OR SUBSTRING(MyColumn,2,1) BETWEEN 1 AND 10) ;
But when I change the query to shorten it like this, it is giving strings having alphabets which I do not want
SELECT * FROM `MyTable` WHERE SUBSTRING(MyColumn,1,1) = "*" AND
(SUBSTRING(MyColumn,2,1) BETWEEN 0 AND 10) ;
Why the 0 is not working with between in this query as expected?
You are seeing this problem because MySQL will attempt to cast the non-numeric character to an integer before comparing it to 0 and 10. Due to MySQL's casting rules, non-numeric strings are always considered equal to zero, so the condition BETWEEN 0 AND 10 is true.
-- The second character 'B' is equal to 0 after casting
> SELECT SUBSTRING('*BC', 2, 1) = 0;
+----------------------------+
| SUBSTRING('ABC', 2, 1) = 0 |
+----------------------------+
| 1 |
+----------------------------+
Since you are matching a specific pattern, I would recommend doing this with a REGEXP match instead of chopping it up into substrings.
SELECT *
FROM `MyTable`
WHERE MyColumn REGEXP '(^\\*[0-9])'
Examples:
> SELECT '*123' REGEXP '^\\*[0-9]';
+---------------------------+
| '*123' REGEXP '^\\*[0-9]' |
+---------------------------+
| 1 |
+---------------------------+
> SELECT '*A23' REGEXP '^\\*[0-9]';
+---------------------------+
| '*A23' REGEXP '^\\*[0-9]' |
+---------------------------+
| 0 |
+---------------------------+
The pattern match here breaks down to:
^ start of the string
\\* match a literal '*', requiring an escaping backslash
[0-9] followed by a digit.
It is possible to use your method and avoid the casting if you use quoted strings in the BETWEEN as in (SUBSTRING(msgbody,2,1) BETWEEN '0' AND '9')
> SELECT SUBSTRING('*BC', 2, 1) BETWEEN '0' AND '9';
+--------------------------------------------+
| SUBSTRING('*BC', 2, 1) BETWEEN '0' AND '9' |
+--------------------------------------------+
| 0 |
+--------------------------------------------+
> SELECT SUBSTRING('*99', 2, 1) BETWEEN '0' AND '9';
+--------------------------------------------+
| SUBSTRING('*99', 2, 1) BETWEEN '0' AND '9' |
+--------------------------------------------+
| 1 |
+--------------------------------------------+
But I prefer the REGEXP method because it expresses the whole pattern you wish to match as one condition. I find it much easier to read because the character positions are built into the expression, rather than needing to decode them from substring(). In either of these options, I expect MySQL will not utilize an index.
You're over-complicating things. In this specific case, if you say
would like to select data from a table having first character as * and second character having numbers from 0 to 9
then you want everything from '*0...' to '*9....'.
So you want,
WHERE MyColumn >= '*0' AND SUBSTR(MyColumn, 1, 2) <= '*9';
The query would be more efficient if you knew that no value of MyColumn will ever exceed, say, "*9ZZZZZZZZ". Then you would ask
WHERE MyColumn >= '*0' AND MyColumn <= '*9ZZZZZZZZ'
or since usually 9 is followed lexicographically by ':', and you don't want it,
WHERE MyColumn >= '*0' AND MyColumn <= '*:'
which allows better use of an index on MyColumn.

Is there a way to retrieve the minimum value of a datatype in MySQL?

I'm trying to retrieve the latest date stored in any of three columns in a row.
This query works fine when all three dates are not NULL:
SELECT GREATEST(date_A, date_B, date_C) FROM mytable
But when any of those values are NULL, GREATEST returns NULL.
A way around this would be to use:
SELECT GREATEST(
COALESCE(date_A, '1000-01-01'),
COALESCE(date_B, '1000-01-01'),
COALESCE(date_C, '1000-01-01')
) FROM mytable
This works because '1000-01-01' is the minimum value of the DATE datatype. For readability's sake, however I'd much rather express the query in terms of a MIN_VALUE function or constant.
Does something like this exist in MySQL?
There is no such function or constant in MySQL. The obvious one to have would be MAXINT, but even that is not available.
If possible, my suggestion would be, as mentioned in comments, to not allow NULL values in the column. Instead use MySQL's zero value for dates which is 0000-00-00, and will work as expected with GREATEST and LEAST:
mysql> SELECT GREATEST ('2015-01-01', '2041-11-13', '1844-05-30', '0000-00-00');
+-------------------------------------------------------------------+
| GREATEST ('2015-01-01', '2041-11-13', '1844-05-30', '0000-00-00') |
+-------------------------------------------------------------------+
| 2041-11-13 |
+-------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT LEAST ('2015-01-01', '2041-11-13', '1844-05-30', '0000-00-00');
+----------------------------------------------------------------+
| LEAST ('2015-01-01', '2041-11-13', '1844-05-30', '0000-00-00') |
+----------------------------------------------------------------+
| 0000-00-00 |
+----------------------------------------------------------------+
1 row in set (0.00 sec)
As #miken mentions this is not possible unfortunately. YOu can consider declaring a variable
SET #MIN_DATE = '1001-01-01';
SELECT GREATEST(
COALESCE(DATE_A, #MIN_DATE),
COALESCE(DATE_B, #MIN_DATE),
COALESCE(DATE_C, #MIN_DATE)
) FROM mytable
Still not very pretty, but at least it is somewhat more readable
See fiddle

ActiveRecord / MySQL Select Condition Comparing String Components

I have a string that is defined as one or more dot-separated integers like 12345, 543.21, 109.87.654, etc. I'm storing values in a MySQL database and then need to find the rows that compare with a provided value. What I want is to select rows by comparing each component of the string against the corresponding component of the input string. With standard string comparison in MySQL, here's where this breaks down:
mysql> SELECT '543.21' >= '500.21'
-> 1
mysql> SELECT '543.21' >= '5000.21'
-> 1
This is natural because the string comparison is a "dictionary" comparison that doesn't account for string length, but I want a 0 result on the second query.
Is there a way to provide some hint to MySQL on how to compare these? Otherwise, is there a way to hint to ActiveRecord how to do this for me? Right now, the best solution I have come up with is to select all the rows and then filter the results using Ruby's split and reject methods. (The entire data set is quite small and not likely to grow terribly much for the foreseeable future, so it is a reasonable option, but if there's a simpler way I'm not considering I'd be glad to know it.)
You can use REPLACE to remove dots and CAST to convert string to integer:
SELECT CAST(REPLACE("543.21", ".", "") AS SIGNED) >= CAST(REPLACE("5000.21", ".", "") AS SIGNED)
mysql> SELECT '543.21' >= '5000.21';
+-----------------------+
| '543.21' >= '5000.21' |
+-----------------------+
| 1 |
+-----------------------+
1 row in set (0.00 sec)
mysql> SELECT '543.21'+0 >= '5000.21'+0;
+---------------------------+
| '543.21'+0 >= '5000.21'+0 |
+---------------------------+
| 0 |
+---------------------------+
1 row in set (0.00 sec)
This indeed only works for valid floats. Doing it for more then 1 dot would require a LOT of comparing of SUBSTRING_INDEX(SUBSTRING_INDEX(field, '.', <positionnumber you're comparing>), '.', -1) (with a manual repeat for the maximum number of position's you are comparing)