You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义主键的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设为主键约束,导致外键关联时找不到合法约束。

修复步骤:

  1. 修正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
    

    主键本身自带唯一性,无需再单独创建唯一索引。

  2. 修正Game模型的关联定义:
    移除belongs_to里的primary_key: true——该参数用于标记当前Game模型的主键,和关联的User主键无关,属于误用:

    belongs_to :user, MyApp.Store.User,
      foreign_key: :user_address,
      references: :address,
      type: :string
    
  3. 重新执行迁移:
    开发环境下可先回滚再迁移:

    mix ecto.rollback
    mix ecto.migrate
    

    生产环境操作前请务必备份数据,避免数据丢失。

完成以上修改后,PostgreSQL会识别到users表的address是合法的主键约束,Game表的外键关联就能正常工作了。

内容的提问来源于stack exchange,提问作者ntonnelier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 11:27:27