Rails迁移:试图将列的类型从字符串更改为整数

32

我使用rails generate migrations命令在我的rails应用程序中创建了一个表。以下是该迁移文件:

class CreateListings < ActiveRecord::Migration
  def change
    create_table :listings do |t|
      t.string :name
      t.string :telephone
      t.string :latitude
      t.string :longitude

      t.timestamps
    end
  end
end

然后我想把纬度和经度存储为整数,所以尝试运行:

rails generate migration changeColumnType

文件内容如下:

class ChangeColumnType < ActiveRecord::Migration
  def up
    #change latitude columntype from string to integertype
    change_column :listings, :latitude, :integer
    change_column :listings, :longitude, :integer
    #change longitude columntype from string to integer type
  end

  def down  
  end
end

我原本期望改变列的类型,但是rake被中止了并出现了以下错误信息。我想知道为什么无法执行?我的应用程序使用postgresql数据库。
rake db:migrate
==  ChangeColumnType: migrating ===============================================
-- change_column(:listings, :latitude, :integer)
rake aborted!
An error has occurred, this and all later migrations canceled:

PG::Error: ERROR:  column "latitude" cannot be cast to type integer
: ALTER TABLE "listings" ALTER COLUMN "latitude" TYPE integer

Tasks: TOP => db:migrate
(See full trace by running task with --trace)

注意:该表格没有数据。

谢谢。

确保您没有任何数据,并尝试进行回滚。 - Ismael
3
如果没有数据,您可以简单地删除列,并使用正确的类型重新添加它们。整个经纬度度数相当大,因此您可能需要考虑一下这些列中真正需要的类型是什么。 - mu is too short
6个回答

31

关于 ALTER TABLE,手册中的一段话:

如果旧类型不能隐式或赋值转换为新类型,则必须提供 USING 子句。

您需要执行以下操作:

ALTER TABLE listings ALTER longitude TYPE integer USING longitude::int;
ALTER TABLE listings ALTER latitude  TYPE integer USING latitude::int;

或者更短并且更快(适用于大表)的单个命令:

ALTER TABLE listings
  ALTER longitude TYPE integer USING longitude::int
, ALTER latitude  TYPE integer USING latitude::int;

只要所有的输入都是有效的整数,无论是否有数据,此方法都可以使用。
如果该列有一个DEFAULT,则可能需要在上述操作之前删除它,并为新类型重新创建(在上述操作之后)。

这是一个关于如何使用ActiveRecord执行此操作的博客文章
或者采用评论中@mu的建议。他熟悉Ruby,而我只熟悉PostgreSQL部分。


4
对于一次性的操作,最简单的方法是将SQL语句封装在connection.execute('...')中,而不是费心地进行猴子补丁。 - mu is too short
1
请查看此链接以获取Rails迁移中的解决方案:http://stackoverflow.com/questions/10690289/rails-gmaps4rails-gem-on-postgres - cintrzyk

26

我建议您在迁移文件中添加原始SQL,如下所示,以便更新schema.rb。

class ChangeColumnType < ActiveRecord::Migration
  def up
    execute 'ALTER TABLE listings ALTER COLUMN latitude TYPE integer USING (latitude::integer)'
    execute 'ALTER TABLE listings ALTER COLUMN longitude TYPE integer USING (longitude::integer)'
  end

  def down
    execute 'ALTER TABLE listings ALTER COLUMN latitude TYPE text USING (latitude::text)'
    execute 'ALTER TABLE listings ALTER COLUMN longitude TYPE text USING (longitude::text)'
  end
end

请注意,下面的 ALTER 应该是 varchar 而不是 text,以匹配字符串类型。 - Martin

24

我知道这样有点丑陋,但我更喜欢只是删除该列,然后使用新类型再次添加:

 def change
     remove_column :mytable, :mycolumn
     add_column :mytable, :mycolumn, :integer, default: 0
 end

我运行了一个迁移 change_column :mytable, :mycolumn, :float。然后运行了几个其他的迁移,推送到 Github,再推送到 Heroku,结果出现了这个错误。我按照你上面建议的运行了一个新的迁移,但是由于之前的迁移,错误仍然存在。现在我无法回滚,因为 remove_column 是不可逆的。我现在该怎么办? - tomb
更新之前的评论:我使用命令 'rails destroy migration migration_name' 删除了之前的迁移,然后运行 'rake db:migrate',现在一切正常。 - tomb
经过数小时的尝试,这个方法帮助我将布尔值转换为枚举类型。 - Jose Kj

12

以下是更符合 rails 规范 的方法来解决这个问题。对于我的情况,我需要将购买表中的两列从字符串类型转换为浮点数类型。

def change
    change_column :purchases, :mc_gross, 'float USING CAST(mc_gross AS float)'
    change_column :purchases, :mc_fee, 'float USING CAST(mc_fee AS float)'
end

那对我起了作用。


虽然了解如何干预postgreSQL是好的,但人们可能会忘记这样的更改。这种Rails方式为操作留下了痕迹。 - Jerome

2
  1. 您在这些列中是否有现有的数据?
  2. 您不应该使用整数表示纬度和经度,而应该改为使用浮点数。

如果您事先知道所需的分辨率,使用整数或长整型表示纬度/经度(例如)度*10^12 可以更快速、更节省存储空间。但是,这种方式很难处理,并且容易出现转换错误,除非所有算法都可以在该形式下本地工作。我同意,除非您知道需要不同的东西并有充分的理由,否则双精度或浮点数更安全。 - Craig Ringer
以下是Google Maps的解释和建议:https://developers.google.com/maps/articles/phpsqlajax#createtable - Victor

0

纬度和经度是十进制数

rails g scaffold client name:string email:string 'latitude:decimal{12,3}' 'longitude:decimal{12,3}' 

class CreateClients < ActiveRecord::Migration[5.0]
  def change
    create_table :clients do |t|
      t.string :name
      t.string :email
      t.decimal :latitude, precision: 12, scale: 3
      t.decimal :longitude, precision: 12, scale: 3

      t.timestamps
    end
  end
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接