I am migrating a database from MySQL to MSSQL.
[MySQL] I have a CHANGEDATE column that is of TIMESTAMP with default CURRENT_TIMESTAMP
[MSSQL] I have the same CHANGEDATE column that is of DATETIME and added a default constraint of GETDATE()
The codebase is PHP using CodeIgniter. I want the column to always be set so I don't allow NULL in either DBMS.
When I insert with MySQL, the property of the PHP model CHANGEDATE defaults to NULL. This triggers the default and the column entry is set to CURRENT_TIMESTAMP. The same code when configured to MSSQL however throws an error that NULL is not allowed in the column, which is valid, but I would rather MSSQL function like MySQL and insert the value of GETDATE() in that instance.
If I do unset($model->CHANGEDATE) or delete the property from my model, then it works as expected, but I wanted to know if there was a way to solve this just using MSSQL instead of updating all my PHP models.
class model {
public $CHANGEDATE;
...
}
ERROR (as described):
[Microsoft][ODBC Driver 11 for SQL Server][SQL Server]Cannot insert the value NULL into column 'CHANGEDATE'; column does not allow nulls. INSERT fails.
INSERT INTO Logs (..., CHANGEDATE, CHANGEBY) VALUES (..., NULL, NULL)
UPDATE:
CI should create support for DBMS specific keywords as #steoleary stated in his answer(for which I marked his correct). However, I found the best solution in my case was to slightly modify the core class DB_active_rec.php
function set(...){
...
foreach ($key as $k => $v)
{
if (is_null($v)) continue;
...
}
}
I assume that you already have the default set on your SQL server column and you don't allow NULLs, deafult constraints won't fire on a NULL value, they will only fire when no value is specified, or if you specify to insert the default value on insert like this:
INSERT INTO [dbo].[table]
([col1]
,[col2]
,[col3]
,[col4]) --Column with default constraint
VALUES
('bob',
'bobson',
1,
DEFAULT) --default keyword
Doing that will cause the default to fire and you shouldn't have to change your models.
I don't know how to express this in code igniter, but in SQL Server, it is really easy:
create table . . . (
changedate not null datetime default getdate()
)
No trigger is required.
I am getting the error MysqlError: Duplicate entry '1-5' for key 'PRIMARY' as shown below in the code. It only happened once (that I could detect, but it was random) and I couldn't find a cause (New Relic reported), but I cannot reproduce and I don't have much more information except the line number and the error given. The schema and code is below.
num_rows() is somehow returning a value that is not 1 even though it shouldn't. If someone can give some insight on how to debug or fix that would be helpful.
Here is my schema for location_items:
CREATE TABLE `phppos_location_items` (
`location_id` int(11) NOT NULL,
`item_id` int(11) NOT NULL,
`location` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`cost_price` decimal(23,10) DEFAULT NULL,
`unit_price` decimal(23,10) DEFAULT NULL,
`promo_price` decimal(23,10) DEFAULT NULL,
`start_date` date DEFAULT NULL,
`end_date` date DEFAULT NULL,
`quantity` decimal(23,10) DEFAULT '0.0000000000',
`reorder_level` decimal(23,10) DEFAULT NULL,
`override_default_tax` int(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`location_id`,`item_id`),
KEY `phppos_location_items_ibfk_2` (`item_id`),
CONSTRAINT `phppos_location_items_ibfk_1` FOREIGN KEY (`location_id`) REFERENCES `phppos_locations` (`location_id`),
CONSTRAINT `phppos_location_items_ibfk_2` FOREIGN KEY (`item_id`) REFERENCES `phppos_items` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci |
And the code:
//Lock tables involved in sale transaction so we do not have deadlock
$this->db->query('LOCK TABLES '.$this->db->dbprefix('customers').' WRITE, '.$this->db->dbprefix('receivings').' WRITE,
'.$this->db->dbprefix('store_accounts').' WRITE, '.$this->db->dbprefix('receivings_items').' WRITE,
'.$this->db->dbprefix('giftcards').' WRITE, '.$this->db->dbprefix('location_items').' WRITE,
'.$this->db->dbprefix('inventory').' WRITE,
'.$this->db->dbprefix('people').' READ,'.$this->db->dbprefix('items').' WRITE
,'.$this->db->dbprefix('employees_locations').' READ,'.$this->db->dbprefix('locations').' READ, '.$this->db->dbprefix('items_tier_prices').' READ
, '.$this->db->dbprefix('location_items_tier_prices').' READ, '.$this->db->dbprefix('items_taxes').' READ, '.$this->db->dbprefix('item_kits').' READ
, '.$this->db->dbprefix('location_item_kits').' READ, '.$this->db->dbprefix('item_kit_items').' READ, '.$this->db->dbprefix('employees').' READ , '.$this->db->dbprefix('item_kits_tier_prices').' READ
, '.$this->db->dbprefix('location_item_kits_tier_prices').' READ, '.$this->db->dbprefix('suppliers').' READ, '.$this->db->dbprefix('location_items_taxes').' READ
, '.$this->db->dbprefix('location_item_kits_taxes'). ' READ, '.$this->db->dbprefix('item_kits_taxes'). ' READ');
// other code for inserting data into other tables that are not relevant.
foreach($items as $line=>$item)
{
$cur_item_location_info->quantity = $cur_item_location_info->quantity !== NULL ? $cur_item_location_info->quantity : 0;
$quantity_data=array(
'quantity'=>$cur_item_location_info->quantity + $item['quantity'],
'location_id'=>$this->Employee->get_logged_in_employee_current_location_id(),
'item_id'=>$item['item_id']
);
$this->Item_location->save($quantity_data,$item['item_id']);
}
// other code for inserting data into other tables that are not relevant.
$this->db->query('UNLOCK TABLES');
class Item_location extends CI_Model
{
function exists($item_id,$location=false)
{
if(!$location)
{
$location= $this->Employee->get_logged_in_employee_current_location_id();
}
$this->db->from('location_items');
$this->db->where('item_id',$item_id);
$this->db->where('location_id',$location);
$query = $this->db->get();
return ($query->num_rows()==1);
}
function save($item_location_data,$item_id=-1,$location_id=false)
{
if(!$location_id)
{
$location_id= $this->Employee->get_logged_in_employee_current_location_id();
}
if (!$this->exists($item_id,$location_id))
{
$item_location_data['item_id'] = $item_id;
$item_location_data['location_id'] = $location_id;
//MysqlError: Duplicate entry '1-5' for key 'PRIMARY'
return $this->db->insert('location_items',$item_location_data);
}
$this->db->where('item_id',$item_id);
$this->db->where('location_id',$location_id);
return $this->db->update('location_items',$item_location_data);
}
}
function get_logged_in_employee_current_location_id()
{
if($this->is_logged_in())
{
//If we have a location in the session
if ($this->session->userdata('employee_current_location_id')!==FALSE)
{
return $this->session->userdata('employee_current_location_id');
}
//Return the first location user is authenticated for
return current($this->get_authenticated_location_ids($this->session->userdata('person_id')));
}
return FALSE;
}
It's not a good idea to check for existence prior to inserting data outside a transaction as this leaves open the possibility of data changing in the mean time. The fact that you've seen this error once but it isn't easily repeatable makes me wonder whether this might have happened.
Would suggest changing the code beneath the first if block in the save function to something that generates the following SQL instead:
INSERT INTO location_items (item_id, location_id)
VALUES ($item_id,$location_id)
ON DUPLICATE KEY UPDATE
This covers the existence check and insert or update in a single atomic statement. (To take this any further and say how to actually implement it I'd need access to the db code.)
EDIT: Sorry, only just noticed the db code is CodeIgniter. Am new to this framework but the above method looks perfectly possible from a brief look here. Something like this:
$sql = "INSERT INTO location_items (item_id, location_id)"
. " VALUES (?, ?)"
. " ON DUPLICATE KEY UPDATE";
$this->db->query($sql, array($item_id, $location_id));
(If for some reason you prefer not to do this, another way to keep it atomic would be to wrap the statements within a transaction instead ($this->db->trans_start(); before the existence check and $this->db->trans_complete(); after the insert/update. But IMO this introduces unnecessary complexity - personally much prefer the first method.)
Looks like a race condition. What likely happened is to roughly simultaneous calls to:
save($data,5);
both get to the exists check at the same time and see that there is no existing entry. Both then try to insert and the fastest gun wins.
You are not going to get a solution so long as the following conditions exist:
You cannot reproduce this problem yourself.
You do not share your source code and database for someone else to attempt to replicate.
I am not asking you to share your full source code. Rather, I am saying this to temper your expectations.
That being said, duplicates can exist for numerous reasons. It would help your question if you provided your version, but I did find one reason that could be a cause: Memory too low - could be reproducible if you lower your memory or put a high strain on your system. If you've had a hard time reproducing it, memory could well be why as you may not be trying to simulate that.
Other things to consider:
You may be wasting your time trying to duplicate something that just will not be duplicated.
If you are concerned you will experience this issue again, you should really consider logging. That can help you to track down the query which caused the issue. I would advise that you not have logging in a production environment and only in development, because it will probably lead to performance penalties that could well be significant. If this is a one-off issue you may never see it again, but it doesn't hurt to be prepared and armed with more information if the issue appears again.
Ultimately, debugging requires the ability to reproduce the error. A bug is part of a computer program, which means there are certain situations and environments in which this will occur, which can be reproduced. When you have no idea how or why a bug was caused there is nowhere to work back from. It is helpful to look to auxiliary concerns as the potential source of your issue by exploring bug reports, etc. If that fails, implement tools like logging that give you more information. This is the only way you will be able to find the root cause of this issue, or get any more specific insight from the SO community on how to do so.
I suspect that the problem could be related with the cache of where statements.
This suspect comes from this stackoverflow question.
Basically I think it could happen that:
- in one cycle this code is executed at the end of save method:
$this->db->where('item_id',$item_id);
$this->db->where('location_id',$location_id);
return $this->db->update('location_items',$item_location_data);
- in the subsequent cycle this code is executed in the exists method:
$this->db->where('item_id',$item_id);
$this->db->where('location_id',$location_id);
return $this->db->update('location_items',$item_location_data);
When executing the "exists" code the cache may still contain the where clauses of the previous statement and the new one (different) will be added.
This way the result will be empty and it seems that the row it is not in the table.
Try to use $this->db->flush_cache(); after the update in the save method.
Also try to use echo $this->db->last_query(); to see what is trying to do in the exists query.
may be ' phppos_location_items ' table is exist in past and a delete statement is executed over this table Delete from phppos_location_items;
in this case primary key column not accept previous values if you truncate the table then all previous record will be removed
Sorry its slightly long for a comment ..
What submits the form ? I assume its a button somewhere on a Page, When I have had a similar error its been due to a user double clicking on a button and 2 requests being sent, in a very close time to each other. This caused a check similar to yours to have this situatuion
Request 1 Check Insert
Request 2 Check Insert
As Request 2 was the last request (because the second click took priority) the error was shown though the first request completed all of the work.
i use the code
$("form").submit(function() {
$(this).submit(function() {
return false;
});
return true;
});
from this question
How to prevent form from submitting multiple times from client side?
Try to use
$insert = $this->db->insert('location_items',$item_location_data);
if($insert)
{
$this->db->reset();
//OR
try $this->db->_reset_write(); to flush all traces of the query
return $insert;
}
Solution 1:
Duplicate entry states that you have one more row which has a primary key same as that of some other previous row.
You can ignore this by statement
INSERT IGNORE INTO ..(rest is same, just add ignore)..
Solution 2:
If you wanna overwrite previous row with new one then you need to follow this query:
INSERT INTO TABLE (f1,f2) VALUES ('f1','f2') ON DUPLICATE KEY UPDATE f1='f1',f2='f2'
Solution: 3
Change your primary key by following these queries:
Create new field for primary key:
ALTER TABLE tablename ADD new_primary_key BIGINT NOT NULL FIRST;
Drop existing primary key:
ALTER TABLE tablename DROP PRIMARY KEY
Now make the new field created earlier as primary key with auto increment
ALTER TABLE tablename MODIFY new_primary_key BIGINT AUTO_INCREMENT PRIMARY KEY
(this will not affect other queries in the code, you can keep them as it is and just add LIMIT 1 in select statements)
I need to update two columns in a table in mysql. I have written following code in ZF2 model
$sql = new Sql($dbAdapter);
$update = $sql->update();
$update->table($table_name)
->set(array('checksum' => '', 'mailed_status' => 0))
->where('id = ' . $record_id);
$statement = $sql->prepareStatementForSqlObject($update);
$statement->execute();
Here in the above code, checksum is varchar column and mailed_status is bit column. The above query only updates checksum field but the mailed_status remains same (previously 1)
When I was updating mailed_status as 1, it works. I mean it updates 0 to 1 but the vice versa is not working.
I have printed the query and found that it makes quote around digit like this: '0' and '1'.
But I am wondering here. '1' is working but '0' is not, why?
What is the proper solution for this? Temporarily I changed the bit data type to varchar(1) in mysql database's table.
Thanks
Thats because 0 is treated like a Null value when updating your database. Changing column type to varchar(1) it's simpler than override sql update method!
I constantly run into this and there has to be a better way to do it. So I have an Order Class and an Order_db class. in the order class there is a property $orderDate that is a DateTime object.
The problem is that sometimes the $orderDate is null. So it seems I have to options:
When I do a select query I test to see if there was date before calling my setter. When I do an Insert query I check that $orderDate isn't null before trying to perform a ->format (as that would throw an error)
I test in the setter if anything was passed before creating a new DateTime object but I'm still stuck with testing if it exists before using it with ->format.
It's not that big of a deal but I deal with a lot of date fields and it gets pretty repetitive. Is there a better way to handle this? In my ideal world if I passed nothing to DateTime when creating a instance it would be null, and if I called ->format on a null DateTime it would return nothing instead of throwing an error.
try this:
CREATE TABLE IF NOT EXISTS `DB`.`Exampletable` (
`field_1` VARCHAR(255) NOT NULL DEFAULT '',
`field_2` CHAR(20) NOT NULL default 'default_text',
`field_3` INT(10) DEFAULT NULL,
`field_4` BIGINT(20),
`field_5` DATETIME DEFAULT NULL
);
Do you tried to add the Default NULL to the date's?
chances are if you are seeing this you most likely saw my last question.
So from there I have made progress. I have realized that the SQL is fine and that it is the PHPbb's DBAL that is causing problems. I sould also note that I am using usercake which initiates the dbal class and all that. For some reason the following code works:
$sql = "CREATE TABLE ideas(
id int(10) unsigned NOT NULL auto_increment,
`user` tinytext NOT NULL,
`date` int(10) unsigned NOT NULL default '0',
description text NOT NULL,
upvotes text NOT NULL,
downvotes text NOT NULL,
appreciated tinyint unsigned NOT NULL default '0',
ip tinytext NOT NULL,
PRIMARY KEY (id))";
$temp = $db->sql_query($sql);
die($temp);
But this code doesn't:
$sql = "INSERT INTO `FUideas` (`description`) VALUES ('TESTER')";
$temp = $db->sql_query($sql);
die($temp);
For information on the FUideas table see my previous post.
I know the sql works because it executes if I use plain php:
$con = mysql_connect('localhost', 'name', 'password');
mysql_select_db("db", $con);
$sql = "INSERT INTO `concepts` (`description`) VALUES ('TESTER')";
mysql_query($sql,$con) or die(mysql_error());
Any ways to fix this are much appreciated
If you need any more information just ask, also I will be online, so if you want to attempt a test just post the code and I will try it.
Edit
So I get it to work if I fill out the other fields with either a 0 or '', can anyone explain this behavior?
It seems as though you're creating a table named ideas and then trying to inser into a table named FUideas. Has FUideas been created elsewhere? In any case, try die(mysql_error()); to see if the error is with the query itself.