I used to use the standard mysql_connect(), mysql_query(), etc statements for doing MySQL stuff from PHP. Lately I've been switching over to using the wonderful MDB2 class. Along with it, I'm using prepared statements, so I don't have to worry about escaping my input and SQL injection attacks.
However, there's one problem I'm running into. I have a table with a few VARCHAR columns, that are specified as not-null (that is, do not allow NULL values). Using the old MySQL PHP commands, I could do things like this without any problem:
INSERT INTO mytable SET somevarchar = '';
Now, however, if I have a query like:
INSERT INTO mytable SET somevarchar = ?;
And then in PHP I have:
$value = "";
$prepared = $db->prepare($query, array('text'));
$result = $prepared->execute($value);
This will throw the error "null value violates not-null constraint"
As a temporary workaround, I check if $value is empty, and change it to " " (a single space), but that's a horrible hack and might cause other issues.
How am I supposed to insert empty strings with prepared statements, without it trying to instead insert a NULL?
EDIT: It's too big of a project to go through my entire codebase, find everywhere that uses an empty string "" and change it to use NULL instead. What I need to know is why standard MySQL queries treat "" and NULL as two separate things (as I think is correct), but prepared statements converts "" into NULL.
Note that "" and NULL are not the same thing. For Example, SELECT NULL = ""; returns NULL instead of 1 as you'd expect.
Thanks to some of the answers, I realized that the problem may be in the MDB2 API, and not in the PHP or MYSQL commands themselves. Sure enough, I found this in the MDB2 FAQ:
Why do empty strings end up as NULL in the database? Why do I get an NULL
not allowed in NOT NULL text fields
eventhough the default value is ""?
The problem is that for some RDBMS (most noteably Oracle) an empty
string is NULL. Therefore MDB2
provides a portability option to
enforce the same behaviour on all
RDBMS.
Since all portability options are enabled by default you will have
to disable the feature if you dont
want to have this behaviour:
$mdb2->setOption('portability',
MDB2_PORTABILITY_ALL ^
MDB2_PORTABILITY_EMPTY_TO_NULL);
Thanks to everyone who provided thoughtful answers.
This sounds like a problem with the MDB2 API fumbling PHP's duck typing semantics. Because the empty string in PHP is equivalent to NULL, MDB2 is probably mis-treating it as such. The ideal solution would be to find a workaround for it within it's API, but I'm not overly familiar with it.
One thing that you should consider, though, is that an empty string in SQL is not a NULL value. You can insert them into rows declared 'NOT NULL' just fine:
mysql> CREATE TABLE tbl( row CHAR(128) NOT NULL );
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO tbl VALUES( 'not empty' ), ( '' );
Query OK, 2 rows affected (0.02 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> SELECT row, row IS NULL FROM tbl;
+-----------+-------------+
| row | row IS NULL |
+-----------+-------------+
| not empty | 0 |
| | 0 |
+-----------+-------------+
2 rows in set (0.00 sec)
mysql> INSERT INTO tbl VALUES( NULL );
ERROR 1048 (23000): Column 'row' cannot be null
If you're unable to find (or implement) a workaround in the MDB2 API, one hackish solution (though slightly better than the one you're currently using) might be to define a user variable for the empty string --
SET #EMPTY_STRING = "";
UPDATE tbl SET row=#EMPTY_STRING;
Finally, if you need to use the empty string in an INSERT statement but find yourself unable to, the default value for string types in MySQL is an empty string. So you could simply omit the column from INSERT statement and it would automatically get set to the empty string (provided the column has a NOT NULL constraint).
I realize this question is pretty much answered and retired, but I found it while looking for answers to a similar situation and I can't resist throwing my hat in the ring.
Without knowing what the NULL/"" column relates to, I can't know how the true significance of an empty string. Does empty string mean something unto itself (like, if I convinced a judge to let me change my name to simply nothing, I would be really irritated if my name showed up on my Driver's License as NULL. My name would be !
However, the empty string (or blank, or the nothingness that is SOMETHING, not simply the lack of anything (like NULL)) could also simply just mean "NOT NULL" or "Nothing, but still not Null". You could even go the other direction and suggest that the absence of the value NULL makes it even LESS something than Null, cuz at least Null has a name you can say aloud!
My point is, that if the empty string is a direct representation of some data (like a name, or what I prefer be inserted between the numbers in my phone number, etc), then your options are either to argue until you're sore for the legitimate use of empty string or to use something that represents an empty string that isn't NULL (Like an ASCII control character or some unicode equivalent, a regex value of some kind, or, even worse, an arbitrary yet totally unused token, like: ◘
If the empty cell really just means NOT NULL, then you could think of some other way of expressing it. One silly and obvious way is the phrase "Not NULL". But I have a hunch that NULL means something like "Not part of this group at all" while the empty string means something like "this guy is cool, he just hasn't gotten his gang tattoos yet". In which case I would come up with a term/name/idea for this situation, like "default" or "rookie" or "Pending".
Now, if by some crazy chance you actually want empty string to represent that which is not even worthy of NULL, again, come up with a more significant symbol for that, such as "-1" or "SUPERNULL" or "UGLIES".
In the Indian Caste System, the lowest Caste are Shudra: Farmers and Laborers. Beneath this caste are the Dalit: "The Untouchables". They are not considered a lower caste, because setting them as the lowest caste would be considered a contamination of the entire system.
So don't call me crazy for thinking empty strings may be WORSE than NULL!
'Til next time.
I found the solution!
MDB2 converts empty strings to NULL because portability option MDB2_PORTABILITY_EMPTY_TO_NULL is on by default (thanks to Oracle which considers empty strings to be null).
Switch this options off when you connect to the database:
$options = array(
'portability' => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL
);
$res= & MDB2::connect("mysql://user:password#server/dbase", $options);
While 0 and empty strings are variables NULL is the absence of data. And trust me, it's a lot easier to write a query to
SELECT * from table where mything IS NULL than to try to query for empty strings :/
Doesn't an empty set of quotes, "" do that?
I'm confused. It looks like you're using mysqli OO (from the tags and style), but the syntax is different than the manual on php.net, which says to do this instead:
$query = "INSERT INTO mytable SET somevarchar = ?";
$value = "";
$prepared = $db->prepare($query);
$prepared->bind_param("s", $value);
$result = $prepared->execute();
Related
I've just noticed that if I do a MySQL request like this one:
SELECT 1 FROM myTable WHERE id = 'asdf'
Then the string 'asdf' is casted to 0.
It means that I have a record with id 0 this will match.
The format of the id field is int(8).
What is the best way to proceed:
I need to check (by PHP for example) that my value is numerical only?
There is a MySQL way to do that?
I must remove my record with id 0? (bad)
Just write your queries so that they don't use numeric fields as if they were textual ones.
If id is a numeric field, then your where clause can never be useful. Yes, it would be good if MySQL actively complained about it - but fundamentally you shouldn't be writing code which runs bad queries to start with.
How did that query enter your system? Is the 'asdf' part direct user input? Can you use parameterized SQL instead?
If you're genuinely intending to query a numeric field, you should make sure that your input is numeric first. Convert the text to an integer in your calling code, not in the database.
You must first sanitize your inputs via PHP.
$id = 'asdf';
if(is_numeric($id)){
$query("SELECT 1 FROM myTable WHERE id = $id");
}else{
die("ID is not numeric");
}
Or you can do:
SELECT 1 FROM myTable WHERE id = 'asdf' AND 'asdf' REGEXP '^-?[0-9]+$'
This would cause the regex to = false, causing no rows to return.
Since pdo prepared statements binding with correct types will not raise any error (except if mysql strict mode is enabled), your only choice is to ensure and control the types of your variables within your php to "correct" the permissivity of these languages.
[thanks to commentators]
in the mysqli php library, in the bind_param() method one binds the parameters to the query. bind_param()'s first argument is types, a string where each character represents the datatype pass, eg, 's' for string.
$query = "update users set update_usr_id = ? where user_id = ?";
$arg1 = ($update_usr_id=$usr_id) ? '2011-01-01' : 'CURRENT_DATE';
$arg2 = $update_usr_id;
how can one represent NULL or CURRENT_DATE as a parameter?
for example, tried 'CURRENT_DATE' as string, but that posted as "0000-00-00". (as far as I can tell "0000-00-00", an illegal date, is a mysqlism for NULL for people who don't use NULL, which apparently is quite a few).
using parameters, how can one use NULL and CURRENT_DATE?
#Johan pointed out that NULL does work. on testing, this appears to be true, but is not. what happens is that, starting with the first NULL parameter, all parameters are set to NULL!
default values have nothing to do with this. There are many times I would wish to set a column to NULL or CURRENT_DATE in an UPDATE - not an INSERT - so default values play no part in the transaction.
also, the idea of writing a trigger to cover the inadequacy of mysqli is pretty bad programming - having written spaghetti triggers in my day, I am one to talk. triggers to update logs are one thing - triggers like this will be a constant maintenance nightmare with little odds the code will ever be maintained correctly.
If you want a default parameter, you can either specify a default in the database schema.
For current_date you need to set the datatype to timestamp not date, that way MySQL will automatically insert now() into the field upon missing data.
Or create a trigger
DELIMITER $$
CREATE TRIGGER BEFORE INSERT ON FOR EACH ROW
BEGIN
IF new.mydate IS NULL THEN SET new.mydate = NOW();
END $$
DELIMITER ;
If you want to pass null just set the var to null:
$var = null;
Links
http://dev.mysql.com/doc/refman/5.1/en/alter-table.html
http://dev.mysql.com/doc/refman/5.1/en/create-trigger.html
mysqli parameters are limited and difficult to use. Not only do they not accept NULL and internal functions like CURRENT_DATE as parameters, each parameter to be passed by reference. (This means a parameter needs to be created for each parameter - which might be necessary for INOUT procedure arguments or SELECT INTO, but for normal dml is simply a waste of time.) On top of that, they are evidently not stored by the session or db to make subsequent calls more efficient.
(Compare to Postgres prepared statement, for example, which is named and my be re-used (howbeit only at the session level). This means the db does not need to be re-taught the statement; it also means the statement is already "planned", so not only the semantics but the execution plan is learned and re-used to increase speed.)
Currently writing something for MySQL that is, I hope, actually usable. Will post later.
i see a couple of questions on here and elsewhere that address this in some cases, but not quite what i need.
i am working with a script that stores an array of values to do either an insert or update based on changes.
some dates/times are set and some are null. i am trying with $var=NULL, then inserting with insert into ... ('{$var}') and getting 00:00:00 for the time if null and the time if time is set.
inserting with $var="NULL" and insert into ... ('{$var}') and getting 00:00:00 if null and the time if time is set.
if i remove the quotes for the insert ... ({$var}) it works if null, but if the date is not null, it of course fails.
i have another VARCHAR field i am doing the same thing with and it works fine. i.e. if there is a string it is passed in as a string and if it's empty i am setting that var=null. i am using quotes on the insert query and if it's null, it goes in as null and if not null, the string inserts.
using a conditional statement is going to require a pretty big rewrite, so i am hoping to avoid that.
any suggestions on how to make this work without IF statements (if possible), would help out.
thanks.
i have tested this several ways and i am not able to get the desired results without making the date/time fields VARCHAR, which i don't want to do. here is the db structure, insert queries and results.
id int(11) No None AUTO_INCREMENT
event_new_date_start date Yes NULL
event_new_time_start time Yes NULL
event_link varchar(150) latin1_swedish_ci Yes NULL
$event_new_date_start1 = 'NULL';
$event_new_time_start1 = 'NULL';
$event_link1 = 'NULL';
$event_new_date_start2 = '2011-04-04';
$event_new_time_start2 = '13:13';
$event_link2 = 'http://abc.com';
$event_new_date_start3 = NULL;
$event_new_time_start3 = NULL;
$event_link3 = NULL;
mysql_query("
insert into test_dates (event_new_date_start, event_new_time_start, event_link) values
('{$event_new_date_start1}', '{$event_new_time_start1}', '{$event_link1}')
");
mysql_query("
insert into test_dates (event_new_date_start, event_new_time_start, event_link) values
('{$event_new_date_start2}', '{$event_new_time_start2}', '{$event_link2}')
");
mysql_query("
insert into test_dates (event_new_date_start, event_new_time_start, event_link) values
('{$event_new_date_start3}', '{$event_new_time_start3}', '{$event_link3}')
");
1 0000-00-00 00:00:00 NULL
2 2011-04-04 13:13:00 http://abc.com
3 0000-00-00 00:00:00
when i changed the date/times fields to CHAR, it worked as expected using quotes in the query.
4 NULL NULL NULL
5 2011-04-04 13:13 http://abc.com
6
If you're using quotes, then you're saying that you want the date to be the string 'NULL'. MySQL cannot parse that to a valid date, therefore it falls back to 0.
When setting the value of $var, set it to either just NULL or a quoted string if it's not null, then simply use ({$var}) in the query instead of ('{$var}').
rdineiu is right, the problem is that you are inserting the word "null". MySQL doesn't understand that and so instead of giving an error (like many DBs would), it inserts a time of all zeros. This is especially fun in Java since trying to read that value will cause an exception to be thrown.
The best thing to do is to move to parameterized SQL queries using DBO. You can find a little more and some links with this question and answer on SO. This will not only solve your problem, but it will solve (some) SQL injection problems that could occur with other values being inserted and read back.
Without doing that (which, again, you should and is the correct answer), you could do the insert without the quotes ("({$var})"), and run the date through a function that appends the necessary quotes if it isn't null. But this won't protect you if you get date values directly from users, and you'll have this same problem if you have a string field you want to be nullable.
MySQL often does completely stupid stuff like this; see this useful list of MySQL "gotchas". In this case, I expect that your date field is NOT NULL and your SQL compliance mode is not very strict; this causes MySQL to enter a default date of 0 instead of the NULL that you actually asked it to do. Change your schema to have your date field DEFAULT NULL and this should go away.
Note that this will work if you make the change that the others suggested, e.g. insert NULL not a quoted 'NULL'.
I've inherited a project which we are trying to migrate to MySQL 5 from MySQL 4.0(!) and from myISAM to InnoDB. Queries are now falling down because they are being constructed using an ADODB connection's ->qstr() on all parameters, including ints. Where no value is provided I end up with:
INSERT INTO tablename VALUES ('', 'stuff'...)
where the first column is an auto_increment. This causes an error (fair enough since '' isn't an int). Is there a switch in MySQL to make it behave as it used to (I assume it just silently converted to 0?)
Edit:
I just ran a few tests and what I wrote below won't help you at all. The error is because of the wrong datatype, and the SQL setting I suggested doesn't change that. I'll leave this answer here though, since it might be helpful to someone else.
Firstly, double check that the column really is auto increment - a couple of times I've had CREATE TABLE files where the fact that a column is auto_increment was sadly missing.
The other thing which might help is to check that NO_AUTO_VALUE_ON_ZERO is not turned on.
SET SQL_MODE='' should turn it off.
Is there any lightweight validation library available for PHP that easily can check if a specific string or value is valid for a known database type -
Something like this:
if (is_MEDIUMINT($var)) {
$this->db->insert($anothervar);
}
Thanks!
This isn't as simple as it seems, your is_MEDIUMINT() function could easily become:
is_MEDIUMINT()
is_MEDIUMINT_NULL()
is_MEDIUMINT_NOTNULL()
is_MEDIUMINT_UNSIGNED()
is_MEDIUMINT_UNSIGNED_NULL()
is_MEDIUMINT_UNSIGNED_NOTNULL()
Then you run into the problem of different databases types, SQLite for instance has only one INT type while MySQL has at least 5 (TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT), not counting aliases (such as INTEGER, BOOL, BOOLEAN and SERIAL) and float types - which would be even harder to implement due to the variable precision argument. Bare in mind that I'm still ignoring several crucial features such as UNIQUE and Foreign Key Constrains which could only be validated on the DB.
I don't understand why you think such functions would be useful, because if you could set up your database to work in strict mode and then simply try to insert the values, if the query fails you know something is wrong, quoting the MySQL Manual:
In nonstrict mode, when an
out-of-range value is assigned to an
integer column, MySQL stores the value
representing the corresponding
endpoint of the column data type
range. If you store 256 into a TINYINT
or TINYINT UNSIGNED column, MySQL
stores 127 or 255, respectively.
What you be the point of validating the value prior to insertion anyway?
if (is_MEDIUMINT($var)) {
$this->db->insert($anothervar);
}
else {
// do what?
}
If you're trying to avoid errors run the query in a transaction or use the INSERT OR IGNORE syntax.
The INFORMATION_SCHEMA database is part of the ANSI 2003 specification, so you could use that across any DB vendor that supports it (MySQL, Postgresql, SQLite, MSSQL 2k5+).
/*public static*/ function is_dbtype($table, $column, $type) {
$db = (...)::getInstance(); // assuming PDO
$sql = 'SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS '.
'WHERE TABLE_NAME = :table AND COLUMN_NAME = :column';
$st = $db->prepare($sql);
$st->execute(array(':table' => $table, ':column' => $column));
return ($type == $st->fetchColumn());
}
Change COLUMN_TYPE to DATA_TYPE if you just want "varchar" instead of "varchar(64)". If you need more, there's plenty: IS_NULLABLE, NUMERIC_PRECISION, CHARACTER_SET_NAME, etc.
(Not sure I'd use personally use this though, the is_* functions usually do enough without an extra database call. More importantly, info_schema holds the structure of every database on the server, so granting read permissions to it might (should) be a big deal. If you're on a shared host you likely won't have access to it at all.)
MySQL-only alternate: do similar but with DESCRIBE [table]. It's pretty explicit though, you'll have to fish out the "bigint" in "bigint(21) unsigned" yourself if that's all you want.
Throw it into the database and see if it returns errors. If it doesn't, you types are good enough.
This also means that the dbms you are using will handle the valiation, which means you don't have to update all your validation functions when they decide to change theirs on a whim. And you will most likely not notice this until everything dies and you can't work out why. The less code you have to maintain yourself, the easier your life is :)
Not as far as I know.
You could, of course, create your own functions or class to do this, based on the rules of your specific database type.
Sorry I can't be more help. (And happy to be educated by any other users if there is a class/functions out there.)
Are you just looking for the is_* functions in PHP?
is_integer, is_float etc. ?
There is also get_type, but it shouldn't be used for type checking