MySQL - Comparing two dates in the WHERE clause - mysql

I have a table, links that links two categories (parent & child) together. The table has five fields:
autoinc
parent_category_name year(4) NOT NULL
parent_category_year varchar(255) NOT NULL
child_category_name year(4) NOT NULL
child_category_year varchar(255) NOT NULL
I am trying to write an INSERT SELECT that grabs both the parent & child category_id, and inserts it into a temp table.
INSERT INTO temp (parent_category_id, child_category_id)
SELECT parent.parent_category_id, child.child_category_id
FROM links
JOIN categories AS parent
ON parent.name = link.parent_category_name
AND parent.year = link.parent_category_year
JOIN categories AS child
ON child.name = link.child_category_name
AND child.year = link.child_category_year
This query works fine, but I need to apply some business rules. The rules are:
The parent year must be the same as the child year
OR
The parent year must be one year less than the child year
I've added a WHERE clause to my query:
WHERE link.child_category_year = link.parent_category_year
OR link.child_category_year - link.parent_category_year = 1
When this INSERT statement executes in my Perl code, I get the following exception:
DBI Exception: DBD::mysql::db do failed: BIGINT UNSIGNED value is out of range in '(`my_database`.`links`.`child_category_year` - `my_database`.`links`.`parent_category_year`)' [for Statement "
So, I take it that the INSERT does not like my date subtraction in the WHERE clause. I explored using the DATEDIFF function, but I am not simply looking for a one year difference in dates, but rather one year less on the parent than the child.
How can I accomplish this without the insert error?

OR link.child_category_year - link.parent_category_year = 1 may produce negative substraction result. Of course negative value cannot be stored as UNSIGNED.
Simply convert your expression to
OR link.child_category_year = link.parent_category_year + 1

Totally unclear why "year" would be stored as an unsigned bigint. That is way overkill for my understanding of "year".
In any case, why not rephrase the logic to:
WHERE link.parent_category_year = link.child_category_year
link.parent_category_year = link.child_category_year + 1
In general, you should not have a problem adding 1 to an unsigned value.

This is my first time knowing that there's a YEAR datatype and after reading the official documentation I understand that:
MySQL displays YEAR values in YYYY format, with a range of 1901 to 2155, and 0000.
Although, it doesn't specifically said that the type is UNSIGNED but the last line in the documentation says:
If strict SQL mode is not enabled, MySQL converts invalid YEAR values to 0000. In strict SQL mode, attempting to insert an invalid YEAR value produces an error.
That means you received the error because one (or more) of the subtracted value in the condition of OR link.child_category_year - link.parent_category_year = 1 is returning negative.
Now, there are a few options you could try:
You can use CAST function and change the year data as SIGNED like :
WHERE link.child_category_year = link.parent_category_year
OR CAST(link.child_category_year AS SIGNED) - CAST(link.parent_category_year AS SIGNED) = 1
Or you can set NO_UNSIGNED_SUBTRACTION sql_mode and run the query as is:
SET sql_mode='NO_UNSIGNED_SUBTRACTION'
You can also consider changing the YEAR datatype to INTEGER and run the query as is:
ALTER TABLE links
MODIFY parent_category_year INT NOT NULL,
MODIFY child_category_year INT NOT NULL;

Related

MySQL WHERE clause integer comparison with alphabet characters still positive?

When I'm searching a specific row by:
SELECT * FROM table WHERE id = '0003'
Wherein column id is an INT primary key field, and it returns me the row of id = 3
Again if I search by:
SELECT * FROM table WHERE id = '3df'
It still returns me the row of id = 3
This is really weird. Why?
My Mysql version is 5.7.14 community.
MySQL does implicit conversion of strings to numbers, in a numeric context.
It does so by converting the leading digits, and stopping at the first non-digit. Most databases would return a type conversion error.
The moral? Always ensure that the types are compatible before doing such a comparison. If you like, you can change this to an explicit case:
where id = cast('3df' as unsigned)
In this case, you will get the type conversion error.

mysql function always returns null even though no null answers

my function is as follows:
DELIMITER $$
CREATE DEFINER=`root`#`localhost` FUNCTION `get_year_groups_for_visit`(id int) RETURNS varchar(200) CHARSET latin1
BEGIN
declare year_text varchar(200);
select group_concat(year_text, ' ') into year_text from a_visit_year_group yg
join year_group y on yg.year_group_id = y.year_group_id
where yg.visitid = id;
RETURN year_text;
END
if I call this using
select get_year_groups_for_visit(1918)
I get the answer null but if I type in
select group_concat(year_text, ' ') as 'Year Groups' from a_visit_year_group yg
join year_group y on yg.year_group_id = y.year_group_id
where yg.visitid = 1918;
I get the answer 'Year 13, Year 12'
if I look in the table a_visit_year_group where visitid is 1918 I get
> visitid year_group_id
1918 14
1918 15
and in the table year_group I get
> year_group_id year_text
14 Year 12
15 Year 13
In fact it is not possible to have an entry in a_visit_year_group with a null value for either year_group_id or visitid and it is not possible to have an entry in the table year_group_id with a null value for year_group_id or year_text so I can't understand why I only get null values back using this function. I have tested it where there is only one value to come back and where there are several values and it always comes back as null. I know if you concat values and one value is null, the answer is always null, but there really are no null answers here.
I'm starting to pull my hair out now as I can't see where I am going wrong, bound to be something really simple I am missing. I would really appreciate some insight. I need to get a list of visits and the year groups involved in each visit and sometimes there are several year groups for each visit and sometimes there are only 1, I don't want multiple rows for each visit so I thought I could create a function to return all the values as one
Your variable has the same name as your column year_text; in your query, it will not refer to the column, but to the variable both times, so you are concatenating the variable here.
To quote the documentation:
A local variable should not have the same name as a table column. If an SQL statement, such as a SELECT ... INTO statement, contains a reference to a column and a declared local variable with the same name, MySQL currently interprets the reference as the name of a variable.
Either (preferable) use a different name for your variable, or add the table(-alias) to the column to get the right scope:
select group_concat(y.year_text, ' ') into year_text
from ... join year_group y ...

MySQL automatic string to integer casting in where clause?

I am not new to MySQL but a strange situation happened in my code today coincidently which got me surprised. Can someone explain why this gives me identical results?
SELECT * FROM `products` WHERE id = 12
and
SELECT * FROM `products` WHERE id = '12ABC'
In both cases I get the same result with the same record being selected. I would expect that second one would return me nothing?! My ID field is int(11) unsigned with auto_increment flag turned on.
From MySQL docs:
When an operator is used with operands of different types, type conversion occurs to make the operands compatible
Documentation
So basically, '12ABC' is cast to 12.
MySQL has to make a conversion to make a compare betwen 2 different types. It tries to make the string to an int and get the digits from the string starting from the beginning.
It you had for instance
'ABC12'
the result of the string conversion to int would be 0

UNION of empty set with AVG function and any non-empty set

this is my first question ever, so please be patient.. :)
We are two developers and both have the same MySql DB with same tables and values.
One is MySql version 5.5 and works ok (apparently) as I am told by the other developer.
On my machine with MySql 5.1.44 (a basic MAMP install) I have the following weird problem.
A very huge query (not mine) fails with error "Column 'xd' cannot be null".
Removing pieces I slimmedi it down to this:
select xd, avg(media) from questionario_punteggi where somefield = 1 union select 1,2
Note, there is no record with somefield = 1 so the first select returns an empty set
We have a SELECT with AVG() function that returns an empty set UNION another SELECT that returns something (1,2 are just random values I put now as an example)
If I remove the AVG() the query works.
If I remove xd (and the 2 of 1,2 to the right) the query works.
If I remove the UNION the query works.
If I set some record with somefield = 1 the query works.
On the other machine 5.5 the query works.
Otherwise the error is:
1048 - Column 'xd' cannot be null
Fields are:
`xd` char(3) NOT NULL DEFAULT '001',
`media` decimal(7,4) NOT NULL DEFAULT '0.0000',
`somefield` tinyint(4) NOT NULL DEFAULT '0',
Gosh. Any help? Thanks.
UPDATE
It has been reported to me as a BUG in MySql <= 5.1 that was fixed before MySql 5.5. I don't have the details but I trust the source
I suggest reversing the order of the queries in the UNION.
This is because the first SELECT in a UNION determines the data type of the columns in the resultset; in your case, the first column of the UNION took the type of the questionario_punteggi.xd column: that is, CHAR(3) NOT NULL.
Since you are applying an aggregate function over the first part of the UNION, it results in a single row even though no records are matched by the filter criterion. As documented under GROUP BY (Aggregate) Functions:
AVG() returns NULL if there were no matching rows.
The value taken for the hidden xd column would normally be an indeterminately chosen record from those that match the filter (which is why you probably don't want to do that anyway); however, since in this case no records match, the server instead returns NULL (which obviously cannot go into a column with the NOT NULL attribute).
By reversing the order of the UNION, the column will not have the NOT NULL attribute. You may need to alias your columns appropriately:
SELECT 1 AS xd, 2 AS avg_media
UNION
SELECT xd, AVG(media) FROM questionario_punteggi WHERE somefield = 1
Using this to explain each of your observations in turn:
If I remove the AVG() the query works.
Since aggregation is no longer performed, the first SELECT in the UNION yields an empty recordset and therefore no NULL record in the first column.
If I remove xd (and the 2 of 1,2 to the right) the query works.
Since the hidden column is no longer selected, MySQL no longer returns NULL in its place.
If I remove the UNION the query works.
This is the bug that was likely fixed between your version of MySQL and your colleague's: the NOT NULL attribute shouldn't really apply to the UNION result.
If I set some record with somefield = 1 the query works.
The value selected for the hidden column is an indeterminate (but non-NULL value, due to the column's attributes) from the matching records.
On the other machine 5.5 the query works.
This bug (I'm still searching for it) must have been fixed between your respective versions of MySQL.
try using the SELECT IFNULL();
Select IFNULL(xd,0), avg(media) f
rom questionario_punteggi
where somefield = 1
union
select 1,2
http://dev.mysql.com/doc/refman/5.0/en/control-flow-functions.html#function_ifnull

Why does "10ddd" equal "10" in my SQL query?

I'm experiencing a strange problem..
I got a table with the following fields:
id smallint(5)
client_id smallint(5)
name varchar(50)
pass varbinary(20)
I got at row with the following values:
id = 5
client_id = 10
name = 'user'
pass = '123'
But if I put some chars in after the client_id it still returns the row???
This query ought not to return anything... hmm
SELECT id
FROM db.user
WHERE client_id='10ddd' && name='user' && pass='123'
That's because mySQL auto-casts string values that are meant for int columns, cutting off all non-integer content.
In the process, 10ddd gets truncated to 10.
See 11.2. Type Conversion in Expression Evaluation
I thought it's possible to turn off using one of the stricter server modes, but I can't see anything in the docs. Failing that, I'm not aware of a simple workaround!
Depending on where the value comes from, you could do a check on the value before you do the query, e.g. using is_int() if in PHP; exit if the value is not an integer.