SQL Query giving wrong results - mysql

The query executed should match the story_id with the provided string but when I execute the query it's giving me a wrong result. Please refer to the screenshot.

story_id column in your case is of INT (or numeric) datatype.
MySQL does automatic typecasting in this case. So, 5bff82... gets typecasted to 5 and thus you get the row corresponding to story_id = 5
Type Conversion in Expression Evaluation
When an operator is used with operands of different types, type
conversion occurs to make the operands compatible. Some conversions
occur implicitly. For example, MySQL automatically converts strings to
numbers as necessary, and vice versa.
Now, ideally your application code should be robust enough to handle this input. If you expect the input to be numeric only, then your application code can use validation operations on the data (to ensure that it is only a number, without typecasting) before sending it to MySQL server.
Another way would be to explicitly typecast story_id as string datatype and then perform the comparison. However this is not recommended approach as this would not be able to utilize Indexing.
SELECT * FROM story
WHERE (CAST story_id AS CHAR(12)) = '5bff82...'
If you run the above query, you would get no results.

you can also use smth like this:
SELECT * FROM story
WHERE regexp_like(story_id,'^[1-5]{1}(.*)$');
for any story_ids starting with any number and matching any no of charatcers after that it wont match with story_id=5;
AND if you explicitly want to match it with a string;

Related

Find the Number of Books in each format

I have a query to run for coursework that has to show the number of books for each format.
Here is the table I am trying to query format can be (hardback, softback, audio, ecopy)
booktable
Here is the code I have tried, I am unaware how to expand to include all format types:
SELECT format, COUNT(format) FROM book WHERE format = 'hardback' OR 'softback' OR 'audio' OR 'ecopy'
I know this is incorrect but it only shows the hardback format and how many hardback books are included.
I've decided to write an answer, because you must be wondering what happens in your query. I suppose you think it should either work or fail, but instead it works correctly for one format, but then it doesn't show any other. Why?
Your query works as follows: A where clause consists of a boolean expresssion. This can be multiple sub expressions combined with AND and OR. Your sub conditions are: format = 'hardback', 'softback', 'audio', 'ecopy'. Now, 'softback' is not really a condition. format = 'softback' would be. And here it gets weird. Rather then reporting a syntax error, MySQL wants a boolean, so it brashly converts your string.
It does so in two steps, because a string cannot be converted to boolean, but a string can be converted to number and a number to boolean. Hence the DBMS first converts your string 'softback' into a number. That should fail, but it doesn't obviously. This is the second time we expect a syntax error, but it isn't happening. MySQL takes the liberty to convert non-numeric strings into a zero.
Then MySQL converts this number into a boolean. In MySQL true = 1 and false = 0. So you have: WHERE format = 'hardback' OR false OR false OR false. Thus you only get 'hardback' books and count these. As there is just one format you select, it can be shown along with the count. I don't know whether MySQL really detects that this is valid, because the query only selects one format. I find it more likely that you are in MySQL's cheat mode (i.e. you haven't SET sql_mode = 'ONLY_FULL_GROUP_BY', which is a bad idea, because by working outside ONLY_FULL_GROUP_BY mode, you tell MySQL to let certain invalid queries pass and muddle through.) So MySQL sees there is a format to be selected, but it must be chosen which row to pick it from, and MySQL muddles through with silently applying ANY_VALUE(format).
What you want is an aggregation (count) with one result row per format. "Per ____" translates to GROUP BY ____ in SQL. So you want:
SELECT format, COUNT(*)
FROM book
GROUP BY format;
You just need to add GROUP BY format at the end of the query.
You have to write select query for each format, seperately!

Why does a query return a result if the field is numeric and the WHERE clause is a string?

I am running a query on a db table which is returning one record when I expect it to return no records.
SELECT yeargroupID FROM tbl_yeargroup WHERE yeargroup='S' AND schoolID=2.
The yeargroup field is a tinyint field. Thefore the WHERE clause is looking the letter 'S' in the numeric field, so should not find anything. Yet it returns the record with the yeargroup = 0, and yeargroupID=17 (the bottom record in the table)
I'm confused as to why it is returning this record and how to avoid it.
Thanks
This logic, as you have pointed out, is comparing a number and a string:
WHERE yeargroup = 'S'
Handling such situations is an important part of most SQL compilers, and it is well documented. The solution is to implicitly convert values to "conforming" types. This is sad. My preference would be for the compiler to generate an error and force the user to use correct types. I find that implicit conversion creates more problems than it solves.
In any case, the rules in this case are pretty simple. The string is converted to an integer. But, how is a string with no digits converted? Well, the rule in MySQL is that the leading digits are converted to a number. And if there are none, the value is 0. So, this turns into:
where yeargroup = 0
You can see the results more clearly if you run:
select 'S', 'S' + 0
Note that most databases would return an error in this case (a type conversion error). But even those would accept the string if it looked like a number, so this would be allowed:
where yeargroup = '5'
What is the proper solution? Never mix types. Do not construct queries by munging constant values. Instead, queries from an application should always be using parameters.

What is wrong in SQL syntax statement [duplicate]

This question already has an answer here:
What is going on with MySQL integer field matching string?
(1 answer)
Closed 2 years ago.
I have a table name workspaces with id has data type = bigint(20) unsigned
I'm trying to query my database as the following:
SELECT * FROM workspaces WHERE id = 1;
SELECT * FROM workspaces WHERE id = '1.a';
Both of them are returns the correct result. But I think the (2) statement is wrong, why sql still return correct value right? What is the reason?
Could you help me to understand why? Thank you so much.
Here is test case on db<>fiddle.
MySQL has complex casting rules for what happens when you try to compare a string literal (e.g. 1.a) against an integer column (id). What is happening here is that MySQL is taking the leading numbers from the string and then forming a number based on that. As a result, the check becomes 1 = 1, which is true for that particular record which is being returned.
On most other databases, your second query would not even execute, which is generally all the better for you. You should not mix numeric and non numeric types in the same comparison.
It extracts the number from string.
For example "1.a"=1
I hope it helps.
In a situation where the sides of a comparison operator (= in this case) don't match, the database will do its best to convert one side the other's type. If there's absolutely no way of doing it, it will throw an error, but it still won't be a syntax error (since the query itself has a valid form), but some error about type conversion.
MySQL, specifically, notoriously plays fast and loose with type conversions. In the case of converting a character literal to a numeric type, if the string starts with a digit, it will convert the starting sequence of digits to a number and ignore anything after it.

Select statement returns data although given value in the where clause is false

I have a table on my MySQL db named membertable. The table consists of two fields which are memberid and membername. The memberid field has the type of integer and uses auto_increment function starting from 2001. The membername table has the type of varchar.
The membertable has two records with the same order as described above. The records look like this :
memberid : 2001
membername : john smith
memberid : 2002
membername : will smith
I found something weird when I ran a SELECT statement against the memberid field. Running the following statement :
SELECT * FROM `membertable` WHERE `memberid` = '2001somecharacter'
It returned the first data.
Why did that happen? There's no record with memberid = 2001somecharacter. It looks like MySQL only search the first 4 character (2001) and when It's found related data, which is the returned data above, it denies the remaining characters.
How could this happen? And is there any way to turn off this behavior?
--
membertable uses innodb engine
This happens because mysql tries to convert "2001somecharacter" into a number which returns 2001.
Since you're comparing a number to a string, you should use
SELECT * FROM `membertable` WHERE CONVERT(`memberid`,CHAR) = '2001somecharacter';
to avoid this behavior.
OR to do it properly, is NOT put your search variable in quotes so that it has to be a number otherwise it'll blow up because of syntax error and then in front end making sure it's a number before passing in the query.
sqlfiddle
Your finding is an expexted MySQL behaviour.
MySQL converts a varchar to an integer starting from the beginning. As long as there are numeric characters wich can easily be converted, they are icluded in the conversion process. If there's a letter, the conversion stops returning the integer value of the numeric string read so far...
Here's some description of this behavior on the MySQL documentation Site. Unfortunately, it's not mentioned directly in the text, but there's an example which exactly shows this behaviour.
MySQL is very liberal in converting string values to numeric values when evaluated in numeric context.
As a demonstration, adding 0 causes the string to evaluated in a numeric context:
SELECT '2001foo' + 0 --> 2001
, '01.2-3E' + 0 --> 1.2
, 'abc567g' + 0 --> 0
When a string is evaluated in a numeric context, MySQL reads the string character by character, until it encounters a character where the string can no longer be interpreted as a numeric value, or until it reaches the end of the string.
I don't know of a way to "turn off" or disable this behavior. (There may be a setting of sql_mode that changes this behavior, but likely that change will impact other SQL statements that are working, which may stop working if that change is made.
Typically, this kind of check of the arguments is done in the application.
But if you need to do this in the SELECT statement, one option would be cast/convert the column as a character string, and then do the comparison.
But that can have some significant performance consequences. If we do a cast or convert (or any function) on a column that's in a condition in the WHERE clause, MySQL will not be able to use a range scan operation on a suitable index. We're forcing MySQL to perform the cast/convert operation on every row in the table, and compare the result to the literal.
So, that's not the best pattern.
If I needed to perform a check like that within the SQL statement, I would do something like this:
WHERE t.memberid = '2001foo' + 0
AND CAST('2001foo' + 0 AS CHAR) = '2001foo'
The first line is doing the same thing as the current query. And that can take advantage of a suitable index.
The second condition is converting the same value to a numeric, then casting that back to character, and then comparing the result to the original. With the values shown here, it will evaluate to FALSE, and the query will not return any rows.
This will also not return a row if the string value has a leading space, ' 2001'. The second condition is going to evaluate as FALSE.
When comparing an INT to a 'string', the string is converted to a number.
Converting a string to a number takes as many of the leading characters as it can and still be a number. So '2001character' is treated as the number 2001.
If you want non-numeric characters in member_id, make it VARCHAR.
If you want only numeric ids, then reject '200.1character'

Single Quotes in MySQL queries

If I have a MySQL query like:
SELECT this FROM that WHERE id='10'
and
SELECT this FROM that WHERE id=10
both seem to work correctly.
What is the use of the single speech marks in MySQL queries? When is it correct to use them?
When MySQL performs the query, there is an implicit conversion of the argument.
If id is INT, then '10' is cast to an integer.
If id is VARCHAR or another text type, 10 is cast to string.
In both cases both queries will work (unless you are running in STRICT mode).
From a performance point of view, you have to use the right data type (do not use quotes for integer arguments) - the implicit cast adds overhead and in some cases, it may hurt the performance of index lookups.
From security perspective, it easier to always use quotes and mysql_real_escape_string (in case the argument is not quoted, mysql_real_escape_string won't stop any attack, that do not use quotes, for example 'UNION SELECT password FROM users'. However, better approach is to cast your variable to int, when it's expected to be int, or use prepared statements
If the value is a string, you have to use ' or ".
If the value is a number, like in your example, you have not to use ', but MySQL handles it if you put it around 's.
Assuming that id is a numeric column, what happens is that MySQL casts your parameter to number automatically so data types match before comparing. It works flawlessly unless casting provides unexpected results. E.g., these expressions with match the row with id=10 because all the strings cast to 10:
id='10'
id=' 10'
id='00010'
id='10foo'
The following will not match the row because non-parseable strings cast to 0 and 10<>0:
id='foo10'
id='bar'
When to use each? If you want a string, you need to quote it (there's no other way to type a string and get valid SQL). If you want a number, it must be unquoted (otherwise, you'll get a string that happens to contain a number). Of course, you can always provide numbers as strings and let MySQL do the conversion, but it doesn't really add anything to the query apart from one extra step and possibly incorrect results that go unnoticed.
You should always use them. They can help to stop SQL injection attacks because mysql_real_escape_string isn't enough on its own.
That is assuming you are running a query via PHP.