新手问题 为什么不能更新主键?

woody1983 · 2012年12月14日 · 最后由 Rei 回复于 2012年12月14日 · 6753 次阅读

Loading development environment (Rails 3.2.9)
irb(main):001:0> z = Zombie.last
  Zombie Load (0.2ms)  SELECT "zombies".* FROM "zombies" ORDER BY "zombies"."id" DESC LIMIT 1
=> #<Zombie id: 3, name: "n29", bit: nil, age: 29, created_at: "2012-12-14 03:05:25", updated_at: "2012-12-14 03:05:25", email: nil, rotting: false>
irb(main):002:0> z
=> #<Zombie id: 3, name: "n29", bit: nil, age: 29, created_at: "2012-12-14 03:05:25", updated_at: "2012-12-14 03:05:25", email: nil, rotting: false>
irb(main):003:0> z.id
=> 3
irb(main):004:0> z.id = 16
=> 16
irb(main):005:0> z.id
=> 16
irb(main):006:0> z.save
   (0.2ms)  begin transaction
   (0.3ms)  UPDATE "zombies" SET "updated_at" = '2012-12-14 03:58:25.810182' WHERE "zombies"."id" = 16
   (0.1ms)  commit transaction
=> true


class Zombie < ActiveRecord::Base
  attr_accessible :age, :bit, :name
  scope :fresh, where("age < 30")
  has_one :brain, dependent: :destroy
end


class Brain < ActiveRecord::Base
  attr_accessible :flavor, :status, :zombie_id
  belongs_to :zombie, foreign_key: :zombie_id
end

绑定的 foreign_key 都被更新了 主表的主键反而没有

这种需求就不应该出现!

#1 楼 @huacnlee 练习而已 说不应该出现的话 如果出现了会怎样 数据会有什么变化 或者硬要这种情况出现该怎么去做。另外 我不是专业的开发人员 我在一家航运公司做 MYSQL DBA 更换主键的情况我很负责人的告诉你 在系统的 Data Migration 动作里出现过。

我的疑惑是 为什么不能修改主键 id 但却能修改另外一张表的外键。只是在练习中看到的例子 多尝试了几种操作 遇到一些问题 不太明白 不存在将这个逻辑放到什么项目里。只是练习


irb(main):013:0> z.brain
  Brain Load (0.2ms)  SELECT "brains".* FROM "brains" WHERE "brains"."zombie_id" = 3 LIMIT 1
=> #<Brain id: 2, zombie_id: 3, status: "fol", flavor: "Mud", created_at: "2012-12-14 03:30:17", updated_at: "2012-12-14 03:30:35">
irb(main):014:0>
irb(main):015:0*
irb(main):016:0*
irb(main):017:0*
irb(main):018:0*
irb(main):019:0* z.brain
=> #<Brain id: 2, zombie_id: 3, status: "fol", flavor: "Mud", created_at: "2012-12-14 03:30:17", updated_at: "2012-12-14 03:30:35">
irb(main):020:0> z.id
=> 3
irb(main):021:0> z.id = 5
=> 5
irb(main):022:0> z.save
   (0.2ms)  begin transaction
   (0.2ms)  UPDATE "zombies" SET "updated_at" = '2012-12-14 05:54:17.712562' WHERE "zombies"."id" = 5
   (0.9ms)  UPDATE "brains" SET "zombie_id" = 5, "updated_at" = '2012-12-14 05:54:17.715295' WHERE "brains"."id" = 2
   (17.3ms)  commit transaction
=> true

刚好我今天看见了这句话 避免改动缺省的 ActiveRecord(表的名字、主键,等等),除非你有一个非常好的理由(像是不受你控制的数据库)。 不知道对你有帮忙没有哦。Ruby is Big in China

我这不仅主键能改而且还是复合主键,用 ActiveRecord 操作数据库那叫一麻烦,以至于那种需要手动更改来操作数据库的地方我都用 java 来做了

#4 楼 @smallbug 谢谢~他说避免 应该是可以实现的 😄 我的疑惑就在这里 既然这个操作没有实际更新到主键 id 那相对应的就不要修改外键的值 问题是 外键被修改了 主键没动 主键还是 3 不是 5 但外键已经被 update 成 3 了

sqlite> select * from zombies;
1|Caike Souza||27|2012-12-14 02:52:34.937489|2012-12-14 02:52:59.001395||f
2|neo||27|2012-12-14 03:05:10.530946|2012-12-14 03:05:10.530946||f
3|n29||29|2012-12-14 03:05:25.339947|2012-12-14 03:05:25.339947||f
sqlite> select * from brains;
2|5|fol|Mud|2012-12-14 03:30:17.379970|2012-12-14 05:54:17.715295

#5 楼 @ywjno 我为什么会想到这个事情 我们的业务表像提单之类的东西 ID 主键不是自增长 是有一个区段的的 比如 19 开头的是 NGB 宁波 22 开头的上海 10 月中旬做过的一次 DM 动作就是讲 NGB 的数据汇入到 SHA 里 相对的 ID 主键和从属的发票信息之类的东西都一并修改过来 实际场景中 还是会遇到这种事情的。我在 CodeSchool 今天看到的练习就是这样 via railsforzombie2 levels/2/challenges/8

OH NO! Our Database Admin turned into a Zombie and decided to rename the belongs_to field in our locations table tweeter_id instead of the intelligent default tweet_id.

那样的话,自己再加个字段比较合适吧,自己维护 ActiveRecord 的 id 太容易踩坑了;另外,我记得 ActiveRecord 里的 id,其实并不是数据库意义上的主键,只是看起来像而已

#8 楼 @dotnil 嗯 我觉得也是 加一个专用主键会好一点 命名规则神马的都可以照顾到 否则都叫 id.....

而且,这个练习看上去不是要你改主键或者外键的值哇,只是让你搞个 migration 而已:

rename_column :locations, :tweet_id, :tweeter_id

数据库字段不是你想加就能加的。。。做企业开发的各种伤不了啊。。。

这种事情一般是 DBA 要干的事情,不是框架负责的事情,所以我想 rails 的 AR 没有实现响应的功能。DBA 不应该用 rails,直接写 sql 处理了。

#10 楼 @dotnil 我看到 Hints 的时候各种泪奔

Make sure both has_one and belongs_to know about the :tweeter_id foreign_key


class Tweet < ActiveRecord::Base
  has_one :location, dependent: :destroy, foreign_key: :tweeter_id
end

class Location < ActiveRecord::Base
  belongs_to :tweet, foreign_key: :tweeter_id
end

#12 楼 @jimrokliu 呃... 兄台 你这句话反过来就是“DEV 不应该用 sql,直接写 java 就处理了”开玩笑的

#14 楼 @woody1983 我的意思是你不应该用 rails 的方法去改 AR 对象 ID,这样做意义不大啊,你要变更的“ID”应该是业务对象 ID,例如 user_id,order_id,... 你非要去改 AR 对象的 ID,我觉得只能走 sql 了,想不出来为什么要换这些号,除非你不喜欢这些数字从 1 开始。

#15 楼 @jimrokliu id 有时候是看 DW 设计的时候对于元数据怎么定义的 我们 OLAP 里面的 ID 规则是ID+Origin_ID 中间用 0 充满 18 位长度。这种设计有弊端也有好的一方面。另外 关于应该不应该 我就是这样 你说不能碰的东西我偏要碰一下看看会怎样 会爆虾米错误 也许有惊喜呢 呵呵

#16 楼 @woody1983 你可以想象 AR 的 id 是 oracle,db2 里的 rowid,AR 好像还没有方法直接改这个 ID。

#13 楼 @woody1983 所以我的理解是对的吧,哈哈

def update(attribute_names = @attributes.keys)
  attributes_with_values = arel_attributes_values(false, false, attribute_names)
  return 0 if attributes_with_values.empty?
  klass = self.class
  stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
  klass.connection.update stmt
end

 def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
  ...

AR 特地吧 primary_key 排除了,就是不建议更新。一定要更新的话用 SQL 吧。

#17 楼 @jimrokliu 其实是可以的,比如 z.update_column(:id, 9527)

#19 楼 @Rei 嗯~耗了一下午 果断放弃 准备自定义主键在每张表

#20 楼 @dotnil 不错,学习了。

#20 楼 @dotnil 相关的外键没有被修改


irb(main):007:0> Brain.all
  Brain Load (0.3ms)  SELECT "brains".* FROM "brains"
=> [#<Brain id: 2, zombie_id: 3, status: "fol", flavor: "Mud", created_at: "2012-12-14 03:30:17", updated_at: "2012-12-14 07:40:00">]
irb(main):008:0> z = Zombie.last
  Zombie Load (0.3ms)  SELECT "zombies".* FROM "zombies" ORDER BY "zombies"."id" DESC LIMIT 1
=> #<Zombie id: 3, name: "n29", bit: nil, age: 29, created_at: "2012-12-14 03:05:25", updated_at: "2012-12-14 03:05:25", email: nil, rotting: false, zombie_id: nil, id_zombier: nil>
irb(main):009:0> z.update_column(:id, 5)
  SQL (24.6ms)  UPDATE "zombies" SET "id" = 5 WHERE "zombies"."id" = 3
=> true
irb(main):010:0> z.save
   (0.1ms)  begin transaction
   (0.3ms)  commit transaction
=> true
irb(main):011:0> Zombie.all
  Zombie Load (0.4ms)  SELECT "zombies".* FROM "zombies"
=> [#<Zombie id: 1, name: "Caike Souza", bit: nil, age: 27, created_at: "2012-12-14 02:52:34", updated_at: "2012-12-14 02:52:59", email: nil, rotting: false, zombie_id: nil, id_zombier: nil>, #<Zombie id: 2, name: "neo", bit: nil, age: 27, created_at: "2012-12-14 03:05:10", updated_at: "2012-12-14 03:05:10", email: nil, rotting: false, zombie_id: nil, id_zombier: nil>, #<Zombie id: 5, name: "n29", bit: nil, age: 29, created_at: "2012-12-14 03:05:25", updated_at: "2012-12-14 03:05:25", email: nil, rotting: false, zombie_id: nil, id_zombier: nil>]
irb(main):012:0> Brain.all
  Brain Load (0.3ms)  SELECT "brains".* FROM "brains"
=> [#<Brain id: 2, zombie_id: 3, status: "fol", flavor: "Mud", created_at: "2012-12-14 03:30:17", updated_at: "2012-12-14 07:40:00">]

#23 楼 @woody1983 update_column 的意思就是脱离 AR 掌控更新一个字段

其实很明了了,AR 不支持更改 id,因为 id 的定义就是永远不改变的字段。

像订单号这样的数据应该另开一个字段,因为人工定义的字段没什么是不变的,身份证号就变过一次。

#23 楼 @woody1983 不要把 id 想象成数据库意义上的主键就可以,其他表的“外键”也需要手工维护的

#25 楼 @Rei 也就是系统用的和业务用的 ID 区分开就 OK 了~ 我现在不太清楚怎么讲其他表的外键重新关联我自定义的一个 id 字段上

#27 楼 @woody1983 http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/belongs_to

似乎是 :primary_key,我还没用过

belongs_to :person, :primary_key => "name", :foreign_key => "person_name"

如果你用默认的 id 做关联,就没必要经常更新外键了。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号