How to alter a column from integer to datetime in Rails Migration ? - mysql

I have a column named record_time to store recorded time currently column data type is integer and the data is saved as unix timestamp now i want to find a way to convert this unix timestamp to datetime field without losing data in that column. right now i have created a migration file as follows:
class ChangeRecordTimeToDatetime < ActiveRecord::Migration
def up
as = Audio.all.map {|a| {id: a.id, record_time: Time.at(a.record_time)}}
Audio.all.update_all("record_time = NULL")
change_column :audios, :record_time, :datetime
as.map {|a| Audio.find(a[:id]).update(record_time: a[:record_time])}
end
def down
as = Audio.all.map {|a| {id: a.id, record_time: a.record_time.to_i}}
Audio.all.update_all("record_time = NULL")
change_column :audios, :record_time, :integer
as.map {|a| Audio.find(a[:id]).update(record_time: a[:record_time])}
end
end
and this throws me an error like this
Mysql2::Error: Incorrect datetime value: '1493178889' for column 'record_time' at row 1: ALTER TABLE `audios` CHANGE `record_time` `record_time` datetime DEFAULT NULL
Thanks in advance.

I'd skip ActiveRecord completely for this sort of thing and do it all inside the database. Some databases will let specify how to transform old values into new values while changing a column's type but I don't see how to do that with MySQL; instead, you can do it by hand:
Add a new column with the new data type.
Do a single UPDATE to copy the old values to the new column while transforming the date type. You can use MySQL's from_unixtime for this.
Drop the original column.
Rename the new column to the old name.
Rebuild any indexes you had on the original column.
Translating that to a migration:
def up
connection.execute(%q{
alter table audios
add record_time_tmp datetime
})
connection.execute(%q{
update audios
set record_time_tmp = from_unixtime(record_time)
})
connection.execute(%q{
alter table audios
drop column record_time
})
connection.execute(%q{
alter table audios
change record_time_tmp record_time datetime
})
# Add indexes and what not...
end
You're well into database-specific code here so going with straight SQL seems reasonable to me. You can of course translate that to change_column and update_all calls (possibly with reset_column_information calls to update the model classes) but I don't see the point: changing a column type will almost always involve database-specific code (if you want to be efficient) and migrations are meant to be temporary bridges anyway.

You need to convert the UNIX timestamps to DateTime objects before inserting them. You can do so with this: DateTime.strptime(<timestamp>,'%s').
So to apply this to your question, try this:
def up
as = Audio.all.map {|a| {id: a.id, record_time: DateTime.strptime(a.record_time.to_s, '%s')}}
remove_column :audios, :record_time
add_column :audios, :record_time, :datetime
as.map {|a| Audio.find(a[:id]).update(record_time: a[:record_time])}
end

Related

Confusion of creation_time field of mysql table in cakephp

Any idea why I cannot automatically update creation_time field in my table:
id | name | creation_time
creation_time-->
Type: Timestamp,
Default Value: CURRENT_TIMESTAMP
When I try to save data in cakephp by using a statement like this:
$this->Model->create();
$this->Model->save(array('name'=>'...'));
I got new data inserting to the table but without the creation time. It is abnormal that when i run an insert into Mysql statement then the field is automatically update as current timestamp.
I know that I could use created field as in Cakephp's documentation but for my case, i don't want to change the existing field name because it is a table used by other members working in the same project.
Please advise me.
In cakephp it is better to use the conventional names like "created" in your case.
The field will be datetime type. But if you wanna make your own field you have to insert manually into your field the actually date and time with a normal set example:
$this->Model->set('creation_time', CURRENT_TIMESTAMP);
where current_timestamp is your variable that you have to create and after you save your model
But the best way in cakephp is to use the convention names, isn't recommended to use unconvention names
When you call $this->Model->create(), Cake populates the data contained in $this->Model with default values. If you debug $this->Model->data
debug($this->Model->data);
You will that the creation_time is already set, but with a string:
array(
'Model' => array(
...,
'creation_time' => 'CURRENT_TIMESTAMP'
)
)
So when Cake generates the INSERT query, this one contain the creation_time field, with CURRENT_TIMESTAMP as a string:
INSERT INTO `models` (..., `creation_time`, ...) VALUES (..., 'CURRENT_TIMESTAMP', ...)
If you want to keep your creation_time field in the datatable, to generate a working query you could delete the default value before saving the data:
$this->Model->create();
unset($this->Model->data['Model']['creation_time']);
$this->Model->save(...);

Rails ActiveRecord Mysql2::Error: Unknown column 'objectname.'

I have an update to an existing MySQL table that is failing under Rails. Here's the relevant controller code:
on = ObjectName.find_by_object_id(params[:id])
if (on) #edit existing
if on.update_attributes(params[:param_type] => params[:value])
respond_to do |format|
...
end
The ObjectName model class has 3 values (object_id, other_id, and prop1). When the update occurs, the SQL generated is coming out as
UPDATE `objectname` SET `other_id` = 245 WHERE `objectname`.`` IS NULL
The SET portion of the generated SQL is correct. Why is the WHERE clause being set to .`` IS NULL ?
I ran into the same error when working with a table with no primary key defined. There was a unique key set up on the field but no PK. Setting the PK in the model fixed it for me:
self.primary_key = :object_id

How to convert a varchar column type to date type without losing the dates

I am looking for a way to change the datatype of a column. Currently, in my database, the date columns types were defined as varchar and I need to convert them back to the date type.
Any idea how to do it?
You will need to adapt this based your your exact table structure but something like;
CREATE TABLE temp (startdate varchar(255), stuff varchar(255));
INSERT INTO temp
SELECT startdate,stuff
FROM mytable;
TRUNCATE TABLE mytable;
ALTER TABLE mytable ALTER COLUMN startdate DATETIME NOT NULL;
INSERT INTO mytable
SELECT CAST(startdate AS DATETIME), stuff FROM temp;
DROP TABLE temp;
First, create the new column with type data
Next, run update query, to populate the new column with the value of the old one, applying any conversion if needed
Next, drop the old column
Finally, rename the new column to the old one
Create a new DATE column with a temporary name
Populate the new column with an UPDATE query that makes use of STR_TO_DATE()
If everything's right, remove the VARCHAR column and rename the DATE column.
Mysql default date format is : YYYY-MM-DD . If your try to insert the date otherwise, as you actually did, the date will be inserted with these values : 000-00-00, giving you a hint to the acceptable date format for mySql.
Wanna share this for SQL server users. For me this method is much convenient and safer.
In your Table create new column "NewDate" (temporarily or name whatever you want).
Make sure no invalid Datetime format in the Table you want to convert. Try those formats here: https://www.w3schools.com/sql/trysqlserver.asp?filename=trysql_func_sqlserver_cast3 <--- you need to check thoroughly otherwise there would be an error executing the command below.
Execute this command:
UPDATE myTable
SET NewDate = CAST(OldDate AS datetime)
WHERE (OldDate <> '') AND (OldDate IS NOT NULL) --to make sure you cast only what is needed otherwise there would be an error.
You can now delete the old column i.e. "OldDate".
Finally you can drag and drop the new table you've created to the slot where you just deleted the old column in the table design.
If the field of your column is VARCHAR and stored date as DD-MM-YYYY then we have to convert the date in YYYY-MM-DD format by following PHP code.
$cd = array();
$cd1 = array();
$cdf = array();
$getdata = mysqli_query($link,"SELECT columnname FROM tablename");
while($row=mysqli_fetch_array($getdata))
{
$cd = $row['columnname'];
$cd1 = strtotime($cd);
$cdf = date('Y-m-d',$cd1);
mysqli_query($link,"UPDATE tablename SET columnname =
REPLACE(columnname,'$cd','$cdf')");
}
After running this PHP code, in your MySQL table change the datatype of your column to 'DATE'.
It works for me without losing or truncate data.

Migration to change default value for a field and change all existing record's value to new default value only if it has old default value.

I need to change the default value of a field from 0 to 3, but the catch is i have thousands of records already and want those records to change the value to 3 from 0 only if the record has default value 0 but for other values like 1, 2 it should remain the same. how can i do it?
In the migration you should use the method change_column to alter the table settings like this:
change_column :my_models, :attribute_name, :integer, :default => 3
And then to update all existing records, instead of looping through all records and updating them individually you could use the method update_all like this:
MyModel.update_all({ :attribute_name => 3 }, { :attribute_name => 0 })
The first argument tells the method what value to set and the second tells it the condition for which rows to update.
ALTER TABLE your_table MODIFY your_column tinyint(1) NOT NULL DEFAULT 3;
UPDATE your_table SET your_column=3 WHERE your_column=0;
assuming your column is tinyint(1), replace your self if not the same
NOT NULL is assuming you always force the column to be NOT NULl

MySQL (DEFAULT + ON UPDATE) TIMESTAMPs

I've a table where I've two fields:
dateCreated
dateUpdated
I want both fields to hold timestamps, dateCreated to have a DEFAULT CURRENT TIMESTAMP flag and dateUpdated to have a ON UPDATE CURRENT TIMESTAMP behavior, however seems that I can't have more than one timestamps field types on the same table.
It would be really useful if the database could take care of this for me, can I circumvent this issue somehow?
YES WE CAN.
You can use a trigger to update the field, like so:
create trigger CreationTimestamp after insert on MyTable for each row
begin
update MyTable set CreateField = UpdateField where id = new.id
end
This way, you can set your auto-update field using the native MySQL method, and use this to capture the creation date.