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

构建房地产SQL Schema:显式多对多关系及替代方案咨询

Prisma + PlanetScale 下 Listing 与 ListingType 关联方案

现有显式多对多方案调整

因为PlanetScale不支持数据库外键,你之前的隐式多对多会报错,改成显式多对多后,还得调整Prisma配置,让它绕过数据库外键约束,自己处理关系。

调整后的完整Prisma模型代码:

generator client {
  provider        = "prisma-client-js"
  relationMode    = "prisma" // 关键:让Prisma自己管理关系,不依赖数据库外键
}

datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  referentialIntegrity = "prisma" // 配合上面的relationMode使用
}

enum ListingTypeEnum {
  SALE
  RENT
}

enum ListingStatus {
  ARCHIVED
  ACTIVE
}

enum ListingVisibility {
  PUBLIC
  PRIVATE
}

model User {
  id        Int      @id @default(autoincrement())
  listings  Listing[]
}

model Listing {
  id                Int                    @id @default(autoincrement())
  name              String
  slug              String                 @unique
  bio               String
  price             Int
  userId            Int
  user              User                   @relation(fields: [userId], references: [id], onDelete: Cascade)
  status            ListingStatus          @default(ARCHIVED)
  visibility        ListingVisibility      @default(PUBLIC)
  createdAt         DateTime               @default(now())
  updatedAt         DateTime               @updatedAt
  listingTypes      ListingOnListingType[]
}

model ListingType {
  id                   Int                    @id @default(autoincrement())
  name                 ListingTypeEnum        @unique
  listings             ListingOnListingType[]
}

model ListingOnListingType {
  listingId     Int      @db.Int
  listingTypeId Int      @db.Int
  assignedAt    DateTime @default(now())
  assignedBy    String

  listing     Listing     @relation(fields: [listingId], references: [id])
  listingType ListingType @relation(fields: [listingTypeId], references: [id])

  @@id([listingId, listingTypeId])
}
  • 重点就是开启relationMode = "prisma",让Prisma在应用层维护关系,不用数据库的外键约束,刚好适配PlanetScale的要求。
  • 中间表保留复合主键,保证同一个Listing和ListingType只会关联一次。

替代方案:位掩码存储

如果觉得多对多太繁琐,且ListingType只有SALE、RENT这类固定选项,可以用位掩码存储,不用额外建表。

修改Listing模型,新增typeFlags字段:

model Listing {
  // 其他原有字段不变
  typeFlags Int @default(0) // 1=SALE,2=RENT,3=同时包含两种类型
}

业务代码里这么处理:

  • 标记为SALE:typeFlags |= 1
  • 标记为RENT:typeFlags |= 2
  • 查询同时具备两种类型的Listing:
const listings = await prisma.listing.findMany({
  where: { typeFlags: 3 }
});
  • 查询包含SALE或RENT的Listing:
const listings = await prisma.listing.findMany({
  where: { typeFlags: { in: [1,2,3] } }
});

优点是无需额外表,查询逻辑简单;缺点是后续新增类型需调整位值,可读性不如多对多方案。

显式多对多的查询示例

要查询同时具备SALE和RENT的Listing,用Prisma可以这么写:

const listingsWithBothTypes = await prisma.listing.findMany({
  where: {
    AND: [
      {
        listingTypes: {
          some: {
            listingType: { name: ListingTypeEnum.SALE }
          }
        }
      },
      {
        listingTypes: {
          some: {
            listingType: { name: ListingTypeEnum.RENT }
          }
        }
      }
    ]
  },
  include: {
    listingTypes: {
      include: { listingType: true }
    }
  }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 13:35:15