I am trying to change a column in my database so that it can use the Postgres array data type.
Currently the table column is of type string.
I am using the following migration to convert it:
def change
change_column :table, :dummy_column, :text, array: true, default: []
end
But I get the following error:
bundle exec rake db:migrate
rake aborted!
An error has occurred, this and all later migrations canceled:
PG::Error: ERROR: column "dummy_column" cannot be cast automatically to type character varying[]
HINT: Specify a USING expression to perform the conversion.
: ALTER TABLE "table" ALTER COLUMN "dummy_column" TYPE character varying(255)
Tasks: TOP => db:migrate
PostgreSQL doesn't know how to automatically convert a column of varchar into an array of varchar. It doesn't know what you might intend, because it has no way to know what format you think the current values are in.
So you need to tell it; that's what the USING clause is for.
ActiveRecord doesn't seem to explicitly support the USING clause (not surprising, as it barely supports even the most basic database features). You can specify your own SQL text for the migration, though.
Assuming your strings are comma separated and may not themselves contain commas, for example:
def change
change_column :table, :dummy_column, "varchar[] USING (string_to_array(dummy_column, ','))"
end
(I don't use Rails myself and haven't tested this, but it's consistent with the syntax used in examples elsewhere).
Using Rails 4.2 on postgresql 9.4 I was looking to do this and preserve my pre-existing string data as the first element in one element arrays.
It turns out that postgresql cannot coerce a string into a text array without a USING expression to tell it how.
After much fiddling with delicate postgres syntax, I found a good middle way with active record:
def change
change_column :users, :event_location, :text, array: true, default: [], using: "(string_to_array(event_location, ','))"
end
The only direct postgresql there is the (string_to_array() ) function call. Here are the docs on that--note that you have to supply a delimiter.
Using Rails 4.2 on postgresql 9.4 with a down and a up, base on lrrthomas response.
Note: your starting column should have a default of nil
class ChangeEmailAndNumberColumnForContact < ActiveRecord::Migration
def up
change_column :contacts, :mobile_number, :text, array: true, default: [], using: "(string_to_array(mobile_number, ','))"
change_column :contacts, :email, :text, array: true, default: [], using: "(string_to_array(email, ','))"
end
def down
change_column :contacts, :mobile_number, :text, array: false, default: nil, using: "(array_to_string(mobile_number, ','))"
change_column :contacts, :email, :text, array: false, default: nil, using: "(array_to_string(email, ','))"
end
end
def change
change_column :table, :dummy_column, :string, array: true, default: '{}'
end
Notice:
it's specified as data type :string with array: true to default the column to an empty array ( [] ), you use default: '{}'
It can be done like below:
change_column :table, :column, :string, array: true, default: {}, using: "(string_to_array(column, ','))"
add_column :table, :dummy_column, :string, array: true
change_column_default :table, :dummy_column, []
This fixed it for me.
Related
I have a benefit_type integer column in Provider Model Which is a enum column.
Provider.rb
enum: ['abc', 'bcd']
Now I want to migrate to array_enum
Provider.rb
array_enum: {'abc': 0, 'bcd': 1}
So, to accommodate this change I want to change my column to array of integer. In my migration I have,
change_column :providers, :benefit_type, :integer, array: true, default: {}, using: "(string_to_array(benefit_type, ','))"
Error:
Caused by:
PG::UndefinedFunction: ERROR: function string_to_array(integer, unknown) does not exist
LINE 1: ...ALTER COLUMN "benefit_type" TYPE integer[] USING (string_to_...
^
HINT: No function matches the given name and argument types. You might need to add explicit
type casts.
Also tried:
change_column :providers, :benefit_type, :integer, array: true, default: []
Error:
Caused by:
ActiveRecord::StatementInvalid: PG::DatatypeMismatch: ERROR: column "benefit_type" cannot
be cast automatically to type integer[]
HINT: You might need to specify "USING benefit_type::integer[]".
: ALTER TABLE "providers" ALTER COLUMN "benefit_type" TYPE integer[], ALTER COLUMN
"benefit_type" SET DEFAULT '{}'
You need to specify integer array with column name in using keyword.
change_column :providers, :benefit_type, :integer, array: true, default: [], using: 'ARRAY[benefit_type]::INTEGER[]'
Initially you have to remove column defaults if you have and You need to specify integer array with column name that your'e needed and it defaults whenever it required.
change_column_default :table_name, :column_name, from: default_value, to: [default_value]
change_column :providers, :benefit_type, :integer, array: true, default: [], using: 'ARRAY[benefit_type]::INTEGER[]'
You can use this to convert existing column with integer type to an integer array
change_column :providers, :benefit_type, 'integer[] USING ARRAY[benefit_type]::INTEGER[]', array: true, null: false, default: []
How do I change an integer column (which already has values) to an integer array column? When I try to migrate this:
def change
change_column :cards, :value, :integer, array: true, default: []
end
I get this error:
PG::DatatypeMismatch: ERROR: column "value" cannot be cast automatically to type integer[]
HINT: You might need to specify "USING value::integer[]"
So I tried this:
change_column :cards, :value, :integer, array: true, default: [], using: 'value::integer[]'
Then I get this error:
PG::CannotCoerce: ERROR: cannot cast type integer to integer[]
After digging around for postgres casting functions & trying several things that didn't work, I found the answer.
using: 'ARRAY[value]::INTEGER[]'
The hint was close, but off by just enough to be confusing.
I'm trying to change gender column to integer in my rails app. It is said that when using postgresql, we have to write a little bit different way. i.e.
change_column :users, :gender, :integer, using: 'gender::integer'
or
change_column :users, :gender, 'integer USING CAST(gender AS integer)'
However, in my case, the aboves are still not working and got the below error.
PG::DatatypeMismatch: ERROR: default for column "gender" cannot be cast automatically to type integer
: ALTER TABLE "users" ALTER COLUMN "gender" TYPE integer USING gender::integer
Please tell me why it doesn't work. Or some weird things I've got? For example typo.
My environment
Ruby: 2.5.0
Rails: 5.1.6
Postgres: 11.1
Based on the error message you're seeing, Postgres does not know how to keep the default value you've set while changing the database type to integer. I'd suggest:
Dropping the default
Changing the datatype on the column
Adding the default in the new type
You may be able to do steps 2 and 3 in the same call. You'll want to use something like the below in the up migration:
def up
execute "ALTER TABLE users ALTER gender DROP DEFAULT;"
change_column :users, :gender, :integer, using: 'gender::integer', default: 0
end
I have this migration where I convert a column from integer to an array of string.
class ChangeWdayFromIntegerToStringInResourceWeekDayStart < ActiveRecord::Migration[4.2]
def up
change_column :resource_week_day_starts, :wday, :string, default: []
add_column :resource_week_day_starts, :number_days, :integer, default: 7
end
def down
change_column :resource_week_day_starts, :wday, :string, default: nil
change_column :resource_week_day_starts, :wday, 'integer USING CAST(wday AS integer)'
remove_column :resource_week_day_starts, :number_days
end
end
This migration works pretty well when we where in rails 3, but we have migrate to rails 5 and now we try to setup a new server. When running the migration in rails 5 we got this error message:
PG::DatatypeMismatch: ERROR: column "wday" cannot be cast automatically to type character varying[]
HINT: You might need to specify "USING wday::character varying[]".
: ALTER TABLE "resource_week_day_starts" ALTER COLUMN "wday" TYPE character varying[]
/home/ruby/src/mapsbooking/db/migrate/20170307000000_change_wday_from_integer_to_string_in_resource_week_day_start.rb:3:in `up'
I have try many ways to fix this up. But nothing works.
Can somebody help me
Thanks
You have three problems:
As max says in the comments, you need to include array: true in the options so that you get an array column.
You need an SQL expression to convert a single integer to an array of strings so that you can include a suitable USING clause in the ALTER TABLE.
change_column wants to change the type and the default separately.
(1) is easy, add array: true to the change_column options.
(2) is a little harder but a couple options come to mind. You could use the element-to-array concatenation operator and a type cast:
wday::varchar || array[]::varchar[]
:: is a type cast, || is the concatenation operator, and array[] is an empty array. Or, if that's too much punctuation, you could use the array_append function to do the same thing:
array_append(array[]::varchar[], wday::varchar)
(3) can be dealt with by dropping the old default with a change_column_default call before the change_column.
Putting them together:
change_column_default :resource_week_day_starts, :wday, nil
change_column :resource_week_day_starts,
:wday,
:string,
array: true,
default: [],
using: 'array_append(array[]::varchar[], wday::varchar)'
This could leave you with array[null] values in wday if you currently have nulls inwday`. You can clean those up after if necessary.
Using the Postgres database, added a text field staff_ids to branches table:
add_column :branches, :staff_ids, :text
In controller added this field to branch_params list:
:staff_ids => []
Data has been saved in this column like ["","32","52"]. When querying this field I got an error saying staff_ids should be an array
Branch.where("? = ANY(staff_ids)", '20')
ActiveRecord::StatementInvalid: PG::WrongObjectType: ERROR: op ANY/ALL (array) requires array on right side
Actually, I forgot to add array: true option in the migration when adding staff_ids field.
Now added another migration to change this column and tried to add array: true option:
change_column :branches, :staff_ids, :text, array: true
But the migration failed with an error:
PG::DatatypeMismatch: ERROR: column "staff_ids" cannot be cast automatically to type text[]
Now either I want to update the where clause so that it filters the branches based on staff_ids without adding the array: true or fix the migration mentioned above.
Any suggestion / idea ?
You can add the following in your migration,
def up
change_column :branches, :staff_ids, :text, array: true, default: [], using: "(string_to_array(staff_ids, ','))"
end
def down
change_column :branches, :staff_ids, :text, array: false, default: nil, using: "(array_to_string(staff_ids, ','))"
end
Defining up and down methods will help to reverse your migration at any time, if the change to an array was not required.