自定义主键的Elixir-Phoenix关联触发PostgreSQL外键无效错误
问题:PostgreSQL外键约束错误:引用表"users"无匹配的唯一约束
User模型迁移代码
def change do create table(:users, primary_key: false) do add :name, :string add :email, :string add :address, :string, primary_key: true # blockchain type address timestamps() end create unique_index(:users, [:address]) end
User模型定义
defmodule MyApp.Store.User do use Ecto.Schema import Ecto.Changeset @primary_key {:address, :string, autogenerate: false} @derive {Phoenix.Param, key: :address} schema "users" do field :name, :string field :email, :string has_many :games, MyApp.Store.Game timestamps() end @doc false def changeset(user, attrs) do user |> cast(attrs, [:name, :address]) |> validate_required([:address]) |> unique_constraint(:address) end end
Game模型迁移代码
def change do create table(:games) do add :name, :string, null: false add :user_address, references(:users, column: :address, type: :string, on_delete: :nothing), null: false add :description, :string timestamps() end create index(:games, [:user_address]) end
Game模型定义
defmodule MyApp.Store.Game do use Ecto.Schema import Ecto.Changeset schema "games" do field :name, :string field :description, :string belongs_to :user, MyApp.Store.User, foreign_key: :user_address, references: :address, type: :string, primary_key: true timestamps() end @doc false def changeset(game_bet, attrs) do game_bet |> cast(attrs, [:name, :description]) |> validate_required([:name]) end end
运行时错误
** (Postgrex.Error) ERROR 42830 (invalid_foreign_key) there is no unique constraint matching given keys for referenced table "users"
问题原因与解决办法
问题核心是:PostgreSQL要求外键必须引用主键约束或唯一约束,当前User表的address字段虽设置了primary_key: true,但迁移中额外创建的unique_index只是普通唯一索引,且Ecto可能未正确将address设为主键约束,导致外键关联时找不到合法约束。
修复步骤:
修正User表迁移,确保
address是主键约束:
移除多余的unique_index,显式创建主键约束(避免Ecto自动处理的偏差):def change do create table(:users, primary_key: false) do add :name, :string add :email, :string add :address, :string timestamps() end # 显式将address设为主键约束 create constraint(:users, :users_pkey, primary_key: [:address]) end主键本身自带唯一性,无需再单独创建唯一索引。
修正Game模型的关联定义:
移除belongs_to里的primary_key: true——该参数用于标记当前Game模型的主键,和关联的User主键无关,属于误用:belongs_to :user, MyApp.Store.User, foreign_key: :user_address, references: :address, type: :string重新执行迁移:
开发环境下可先回滚再迁移:mix ecto.rollback mix ecto.migrate生产环境操作前请务必备份数据,避免数据丢失。
完成以上修改后,PostgreSQL会识别到users表的address是合法的主键约束,Game表的外键关联就能正常工作了。
内容的提问来源于stack exchange,提问作者ntonnelier
相关产品推荐
相关产品推荐

