I'd like to change all non-date formatted strings to date nil, then change a column type. I had planned to use the below:
class SetSubmitDateForAllMyTables < ActiveRecord::Migration
def self.up
MyTable.connection.execute("UPDATE my_table SET submit_date = NULL WHERE NOT ISDATE(submit_date)")
MyTable.connection.execute("UPDATE my_table SET submit_date=created_at WHERE submit_date IS NULL")
change_column :my_table, :submit_date, :date
end
def self.down
end
end
However, the ISDATE function doesn't seem to be valid in POSTGRES. How can I accomplish this?
I am open to using:
MyTable.all.each do |list|
Postgresql can coerce strings to date, but it will error on invalid strings:
dev_db=> select '1/2/2015'::date;
date
------------
2015-01-02
dev_db=> select 'asdf'::date;
ERROR: invalid input syntax for type date: "asdf"
LINE 1: select 'asdf'::date;
^
To get around this, you could create a Postgresql function to check if a string is a valid date, but really it would be much easier to just handle the conversion on each record in your migration.
Related
In my Rails 5.0.5 app I need to convert json column into string, array: true.
The values in my json columns are like:
[ "200px-RR5219-0015R.png", "2017_03_25_2235.doc", "137555.jpg" ]
I tried this migration:
class ChangeTaskAttachmentsTypeToString < ActiveRecord::Migration[5.0]
def change
change_column :tasks, :attachments, :string, array: true
end
end
and got this error:
ActiveRecord::StatementInvalid: PG::DatatypeMismatch: ERROR: column "attachments" cannot be cast automatically to type character varying[]
HINT: You might need to specify "USING attachments::character varying[]".
: ALTER TABLE "tasks" ALTER COLUMN "attachments" TYPE character varying[]
Then I edited migration:
class ChangeTaskAttachmentsTypeToString < ActiveRecord::Migration[5.0]
def change
change_column :tasks, :attachments, 'character varying[] USING attachments::character varying[]'
end
end
And finally got this error:
PG::CannotCoerce: ERROR: cannot cast type json to character varying[]
: ALTER TABLE "tasks" ALTER COLUMN "attachments" TYPE character varying[] USING attachments::character varying[]
How can I do this migration?
I guess the array elements are filenames. If so, then you can remove all the characters []" and spaces and split the result to array, like this:
with my_table(attachments) as (
values
('[ "200px-RR5219-0015R.png", "2017_03_25_2235.doc", "137555.jpg" ]'::json)
)
select string_to_array(translate(attachments::text, '[] "', ''), ',')::varchar[]
from my_table;
string_to_array
---------------------------------------------------------
{200px-RR5219-0015R.png,2017_03_25_2235.doc,137555.jpg}
(1 row)
so use:
... USING string_to_array(translate(attachments::text, '[] "', ''), ',')::varchar[]
A more formal (and general) solution would require a custom function, e.g.:
create or replace function json_to_text_array(json)
returns text[] language sql immutable as $$
select array_agg(value)
from json_array_elements_text($1)
$$;
that could be used in
alter table tasks alter column attachments type text[]
using json_to_text_array(attachments);
Note, I have used text[] as a more natural choice for Postgres but you can replace it with varchar[] if it matters.
Test it in Db<>fiddle.
If I've got a production database that has "types" stored as string, but I want to convert that column to an integer for enum.
I've googled/SO'd, and I see that I can CAST, but not sure what that does exactly.
If it's not hard, I'd love ot use rails enum, but otherwise, maybe I should stick with my string schema...
Please advise!
You can rename existing column, create a new one called "types" (integer), then write a script that stores appropriate integer value in the new column, and then drop the old column.
The migration will look like this:
class FixTypes < ActiveRecord::Migration
def change
rename_column :table_name, :types, :old_types
add_column :table_name, :types, :integer
end
end
Then write a script that sets the value of "types" based on "old_types":
Model.all.each do |entry|
entry.types = %w(status1 status2 status3 status4).index(entry.old_types)
entry.save!
end
And then drop the "old_types" column.
In my Rails 4 app I have a goal to see all contacts, where field visible_to in contacts table equal to 1. My visible_to is :integer, array: true.
However, I get the following exception:
PG::UndefinedFunction: ERROR: operator does not exist: integer[] = integer
LINE 1: ....* FROM "contacts" WHERE "contacts"."visible_to" IN (1) OR...
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.: SELECT "contacts".* FROM "contacts" WHERE "contacts"."visible_to" IN (1) ORDER BY created_at DESC
I searched for answers and as far as I see there is an issue with a type of visible_to. However, I couldn't find the solution. I also tried to get benefit from casts hint, but in vain.
My migration:
class AddVisibleToToContacts < ActiveRecord::Migration
def change
add_column :contacts, :visible_to, :integer, array: true, default: [], using: 'gin'
end
end
Relevant variable from Controller:
#contacts_all_user_sorted = Contact.all.where(visible_to: [1]).order("created_at DESC")
From these two websites:
http://blog.relatabase.com/rails-postgres-arrays
http://adamsanderson.github.io/railsconf_2013/#11
It seems that this syntax should work:
#contacts_all_user_sorted = Contact.all.where("visible_to #> ARRAY[?]", [1])
Does it work?
P.S: As #Quertie pointed out in the comments, you may want to cast the value in the case of a String array, by replacing ARRAY[?] by ARRAY[?]::varchar[]
your migration seems pretty straight forward and correct.
can you please try this:
Contact.where('visible_to IN ?', ['1', '2'])
I already google'd aroung a little bit and seems there's no satisfying answer for my problem.
I have a table with column of type string.
I'd like to run following migration:
class ChangeColumnToBoolean < ActiveRecord::Migration
def up
change_column :users, :smoking, :boolean
end
end
When I run this I get following error
PG::Error: ERROR: column "smoking" cannot be cast automatically to type boolean
HINT: Specify a USING expression to perform the conversion.
: ALTER TABLE "users" ALTER COLUMN "smoking" TYPE boolean
I know I can perform this migration using pure SQL but still it would be nicer if I could do it with Rails. I went through Rails code and seems theres no such possibility, but maybe someone knows a way?
I'm not interested in:
- pure SQL
- dropping the column
- creating another column, converting data, dropping original and then renaming
If your strings in smoking column are already valid boolean values, the following statement will change the column type without losing data:
change_column :users, :smoking, 'boolean USING CAST(smoking AS boolean)'
Similarly, you can use this statement to cast columns to integer:
change_column :table_name, :column_name, 'integer USING CAST(column_name AS integer)'
I am using Postgres. Not sure whether this solution works for other databases.
Not all databases allow changing of column type, the generally taken approach is to add a new column of the desired type, bring any data across, remove the old column and rename the new one.
add_column :users, :smoking_tmp, :boolean
User.reset_column_information # make the new column available to model methods
User.all.each do |user|
user.smoking_tmp = user.smoking == 1 ? true : false # If smoking was an int, for example
user.save
end
# OR as an update_all call, set a default of false on the new column then update all to true if appropriate.
User.where(smoking: 1).update_all(smoking_tmp: true)
remove_column :users, :smoking
rename_column :users, :smoking_tmp, :smoking
So right for boolean in postgres:
change_column :table_name, :field,'boolean USING (CASE field WHEN \'your any string as true\' THEN \'t\'::boolean ELSE \'f\'::boolean END)'
and you may add some more WHEN - THEN condition in your expression
For other database servers, the expression will be constructed based on the syntax for your database server, but the principle is the same. Only manual conversion algorithm, entirely without SQL there is not enough unfortunately.
The syntax change_column :table, :field, 'boolean USING CAST(field AS boolean)' is suitable only if the contents of the field something like: true / false / null
Since I'm using Postgres, I went with SQL solution for now.
Query used:
execute 'ALTER TABLE "users" ALTER COLUMN "smoking" TYPE boolean USING CASE WHEN "flatshare"=\'true\' THEN \'t\'::boolean ELSE \'f\'::boolean END'
It works only if one has a field filled with true/false strings (such as default radio button collection helper with forced boolean type would generate)
I'm trying to perform the following up migration to change the column "number" in the "tweet" model's table
class ChangeDataTypeForTweetsNumber < ActiveRecord::Migration
def up
change_column :tweets do |t|
t.change :number, :integer
end
end
def down
change_table :tweets do |t|
t.change :number, :string
end
end
end
Upon performing the the following up migration to heroku....
heroku rake db:migrate:up VERSION=20120925211232
I get the following error
PG::Error: ERROR: column "number" cannot be cast to type integer
: ALTER TABLE "tweets" ALTER COLUMN "number" TYPE integer
Any thoughts you have would be very much appreciated.
Thanks everyone.
Same as above but a little bit more concise:
change_column :yourtable, :column_to_change, 'integer USING CAST("column_to_change" AS integer)'
From the fine manual:
[ALTER TABLE ... ALTER COLUMN ...]
The optional USING clause specifies how to compute the new column value from the old; if omitted, the default conversion is the same as an assignment cast from old data type to new. A USING clause must be provided if there is no implicit or assignment cast from old to new type.
There is no implicit conversion from varchar to int in PostgreSQL so it complains that column "number" cannot be cast to type integer and the ALTER TABLE fails. You need to tell PostgreSQL how to convert the old strings to numbers to match the new column type and that means that you need to get a USING clause into your ALTER TABLE. I don't know of any way to make Rails do that for you but you can do it by hand easily enough:
def up
connection.execute(%q{
alter table tweets
alter column number
type integer using cast(number as integer)
})
end
You'll want to watch out for values that can't be cast to integers, PostgreSQL will let you know if there are problems and you'll have to fix them before the migration will succeed.
Your existing down-migration should be fine, converting integer to varchar should be handled automatically.