如何用Ecto的timestamps为已有表添加时间戳并设置正确默认值
解决Ecto为已有表添加timestamps时新旧数据时间戳正确设置的问题
我之前也踩过这个坑!直接用timestamps(default: "...")确实会让数据库把这个硬编码时间当成永久默认值,导致新插入的记录也沿用它,而不是实际的操作时间。这里有两种可靠的解决方法,你可以根据需求选择:
方法一:分步骤迁移(更可控)
这种方法先给旧数据填充指定时间,再配置数据库自动用当前时间处理新记录,同时保证Ecto能正常接管时间戳设置:
def change do # 1. 先添加可空的时间戳字段,避免已有数据因无值报错 alter table(:my_table) do add :inserted_at, :utc_datetime add :updated_at, :utc_datetime end # 2. 手动为已有行填充默认时间 execute "UPDATE my_table SET inserted_at = '2018-01-01 00:00:01', updated_at = '2018-01-01 00:00:01'" # 3. 修改字段为非空,并设置数据库层面的动态默认值(可选但推荐) # 这样即使直接通过SQL插入数据,数据库也会自动用当前时间 alter table(:my_table) do modify :inserted_at, :utc_datetime, null: false, default: fragment("CURRENT_TIMESTAMP") modify :updated_at, :utc_datetime, null: false, default: fragment("CURRENT_TIMESTAMP") end end
方法二:一次性修改(更简洁)
如果你想用Ecto的timestamps宏,也可以先临时设置默认值填充旧数据,再覆盖为动态默认:
def change do # 第一步:添加字段并填充旧数据 alter table(:my_table) do timestamps(default: ~U[2018-01-01 00:00:01Z], null: false) end # 第二步:把默认值改成数据库当前时间函数 alter table(:my_table) do modify :inserted_at, :utc_datetime, default: fragment("CURRENT_TIMESTAMP"), null: false modify :updated_at, :utc_datetime, default: fragment("CURRENT_TIMESTAMP"), null: false end end
关键补充:Schema配置
别忘了在对应的Schema里加上timestamps()宏,这样Ecto在插入和更新记录时会自动设置正确的时间(会覆盖数据库的默认值,确保时间准确):
schema "my_table" do # 你的其他字段... timestamps(type: :utc_datetime) end
为什么原来的方法不行?
当你直接用timestamps(default: "...")时,Ecto会把这个固定时间设置为数据库字段的永久默认值。如果Schema里没有timestamps()宏或者插入时没有显式设置时间,数据库就会一直用这个固定值。通过上面的步骤,我们先填充旧数据,再把默认值换成动态的CURRENT_TIMESTAMP,同时让Ecto在应用层处理时间戳,就能兼顾新旧数据的需求了。
内容的提问来源于stack exchange,提问作者Katherine
相关产品推荐
相关产品推荐

