複合型別
複合型別僅適用於 MongoDB。
複合型別,在 MongoDB 中稱為嵌入式文件,允許你將記錄嵌入到其他記錄中。
我們在 v3.12.0 版本中將複合型別正式釋出 (GA)。此前,它們在 v3.10.0 版本中處於預覽階段。
本頁解釋如何
- 使用
findFirst和findMany查詢包含複合型別的記錄 - 使用
create和createMany建立帶有複合型別的新記錄 - 使用
update和updateMany更新現有記錄中的複合型別 - 使用
delete和deleteMany刪除帶有複合型別的記錄
示例 schema
我們將使用以下 schema 作為示例
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
model Product {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
price Float
colors Color[]
sizes Size[]
photos Photo[]
orders Order[]
}
model Order {
id String @id @default(auto()) @map("_id") @db.ObjectId
product Product @relation(fields: [productId], references: [id])
color Color
size Size
shippingAddress Address
billingAddress Address?
productId String @db.ObjectId
}
enum Color {
Red
Green
Blue
}
enum Size {
Small
Medium
Large
XLarge
}
type Photo {
height Int @default(200)
width Int @default(100)
url String
}
type Address {
street String
city String
zip String
}
在此 schema 中,Product 模型具有一個 Photo[] 複合型別,而 Order 模型具有兩個複合 Address 型別。shippingAddress 是必填項,但 billingAddress 是可選項。
使用複合型別時的注意事項
目前在 Prisma Client 中使用複合型別時存在一些限制
findUnique()無法根據複合型別進行篩選aggregate、groupBy()、count不支援複合操作
複合型別上必填欄位的預設值
從 4.0.0 版本開始,如果你在滿足以下所有條件時對複合型別執行資料庫讀取操作,Prisma Client 會將預設值插入到結果中。
條件
注意
- 這與模型欄位的行為相同。
- 在讀取操作中,Prisma Client 將預設值插入到結果中,但不會將預設值插入到資料庫中。
在我們的示例 schema 中,假設你為 photo 添加了一個必填欄位。此欄位 bitDepth 具有預設值
...
type Photo {
...
bitDepth Int @default(8)
}
...
假設你接著執行 npx prisma db push 來更新資料庫,並使用 npx prisma generate 重新生成 Prisma Client。然後,你執行以下應用程式程式碼
console.dir(await prisma.product.findMany({}), { depth: Infinity })
bitDepth 欄位沒有內容,因為你剛剛新增此欄位,因此查詢返回預設值 8。
** 早期版本 **
在 4.0.0 版本之前,Prisma ORM 會丟擲如下 P2032 錯誤
Error converting field "bitDepth" of expected non-nullable
type "int", found incompatible value of "null".
使用 find 和 findMany 查詢包含複合型別的記錄
記錄可以透過 where 操作中的複合型別進行篩選。
以下部分描述了用於按單一型別或多種型別進行篩選的可用操作,並提供了每個操作的示例。
篩選單個複合型別
使用 is、equals、isNot 和 isSet 操作來更改單個複合型別
is:透過匹配複合型別來篩選結果。要求存在一個或多個欄位 (例如,按送貨地址中的街道名稱篩選訂單)equals:透過匹配複合型別來篩選結果。要求所有欄位都存在。 (例如,按完整的送貨地址篩選訂單)isNot:透過不匹配的複合型別來篩選結果isSet:篩選可選欄位以僅包含已設定(設定為某個值或明確設定為null)的結果。將此過濾器設定為true將排除根本未設定的undefined結果。
例如,使用 is 篩選街道名稱為 '555 Candy Cane Lane' 的訂單
const orders = await prisma.order.findMany({
where: {
shippingAddress: {
is: {
street: '555 Candy Cane Lane',
},
},
},
})
使用 equals 篩選送貨地址中所有欄位都匹配的訂單
const orders = await prisma.order.findMany({
where: {
shippingAddress: {
equals: {
street: '555 Candy Cane Lane',
city: 'Wonderland',
zip: '52337',
},
},
},
})
你也可以使用此查詢的簡寫形式,省略 equals
const orders = await prisma.order.findMany({
where: {
shippingAddress: {
street: '555 Candy Cane Lane',
city: 'Wonderland',
zip: '52337',
},
},
})
使用 isNot 篩選郵政編碼不是 '52337' 的訂單
const orders = await prisma.order.findMany({
where: {
shippingAddress: {
isNot: {
zip: '52337',
},
},
},
})
使用 isSet 篩選可選的 billingAddress 已設定(設定為某個值或 null)的訂單
const orders = await prisma.order.findMany({
where: {
billingAddress: {
isSet: true,
},
},
})
篩選多個複合型別
使用 equals、isEmpty、every、some 和 none 操作來篩選多個複合型別
equals:檢查列表的精確相等性isEmpty:檢查列表是否為空every:列表中的每個專案都必須匹配條件some:列表中一個或多個專案必須匹配條件none:列表中沒有任何專案可以匹配條件isSet:篩選可選欄位以僅包含已設定(設定為某個值或明確設定為null)的結果。將此過濾器設定為true將排除根本未設定的undefined結果。
例如,你可以使用 equals 查詢具有特定照片列表的產品(所有 url、height 和 width 欄位必須匹配)
const product = prisma.product.findMany({
where: {
photos: {
equals: [
{
url: '1.jpg',
height: 200,
width: 100,
},
{
url: '2.jpg',
height: 200,
width: 100,
},
],
},
},
})
你也可以使用此查詢的簡寫形式,省略 equals,只指定要篩選的欄位
const product = prisma.product.findMany({
where: {
photos: [
{
url: '1.jpg',
height: 200,
width: 100,
},
{
url: '2.jpg',
height: 200,
width: 100,
},
],
},
})
使用 isEmpty 篩選沒有照片的產品
const product = prisma.product.findMany({
where: {
photos: {
isEmpty: true,
},
},
})
使用 some 篩選其中一個或多個照片的 url 為 "2.jpg" 的產品
const product = prisma.product.findFirst({
where: {
photos: {
some: {
url: '2.jpg',
},
},
},
})
使用 none 篩選沒有照片的 url 為 "2.jpg" 的產品
const product = prisma.product.findFirst({
where: {
photos: {
none: {
url: '2.jpg',
},
},
},
})
使用 create 和 createMany 建立帶有複合型別的記錄
當你建立帶有唯一約束的複合型別記錄時,請注意 MongoDB 不會在記錄內部強制執行唯一值。瞭解更多。
複合型別可以使用 set 操作在 create 或 createMany 方法中建立。例如,你可以在 create 中使用 set 在 Order 內部建立 Address 複合型別
const order = await prisma.order.create({
data: {
// Normal relation
product: { connect: { id: 'some-object-id' } },
color: 'Red',
size: 'Large',
// Composite type
shippingAddress: {
set: {
street: '1084 Candycane Lane',
city: 'Silverlake',
zip: '84323',
},
},
},
})
你也可以使用簡寫形式,省略 set,只指定要建立的欄位
const order = await prisma.order.create({
data: {
// Normal relation
product: { connect: { id: 'some-object-id' } },
color: 'Red',
size: 'Large',
// Composite type
shippingAddress: {
street: '1084 Candycane Lane',
city: 'Silverlake',
zip: '84323',
},
},
})
對於可選型別,如 billingAddress,你也可以將值設定為 null
const order = await prisma.order.create({
data: {
// Normal relation
product: { connect: { id: 'some-object-id' } },
color: 'Red',
size: 'Large',
// Composite type
shippingAddress: {
street: '1084 Candycane Lane',
city: 'Silverlake',
zip: '84323',
},
// Embedded optional type, set to null
billingAddress: {
set: null,
},
},
})
要為包含多個 photos 列表的 product 建模,你可以同時 set 多個複合型別
const product = await prisma.product.create({
data: {
name: 'Forest Runners',
price: 59.99,
colors: ['Red', 'Green'],
sizes: ['Small', 'Medium', 'Large'],
// New composite type
photos: {
set: [
{ height: 100, width: 200, url: '1.jpg' },
{ height: 100, width: 200, url: '2.jpg' },
],
},
},
})
你也可以使用簡寫形式,省略 set,只指定要建立的欄位
const product = await prisma.product.create({
data: {
name: 'Forest Runners',
price: 59.99,
// Scalar lists that we already support
colors: ['Red', 'Green'],
sizes: ['Small', 'Medium', 'Large'],
// New composite type
photos: [
{ height: 100, width: 200, url: '1.jpg' },
{ height: 100, width: 200, url: '2.jpg' },
],
},
})
這些操作也適用於 createMany 方法。例如,你可以建立多個 product,每個 product 都包含一個 photos 列表
const product = await prisma.product.createMany({
data: [
{
name: 'Forest Runners',
price: 59.99,
colors: ['Red', 'Green'],
sizes: ['Small', 'Medium', 'Large'],
photos: [
{ height: 100, width: 200, url: '1.jpg' },
{ height: 100, width: 200, url: '2.jpg' },
],
},
{
name: 'Alpine Blazers',
price: 85.99,
colors: ['Blue', 'Red'],
sizes: ['Large', 'XLarge'],
photos: [
{ height: 100, width: 200, url: '1.jpg' },
{ height: 150, width: 200, url: '4.jpg' },
{ height: 200, width: 200, url: '5.jpg' },
],
},
],
})
在 update 和 updateMany 中更改複合型別
當你更新帶有唯一約束的複合型別記錄時,請注意 MongoDB 不會在記錄內部強制執行唯一值。瞭解更多。
複合型別可以在 update 或 updateMany 方法中設定、更新或刪除。以下部分描述了用於一次更新單個型別或多個型別的可用操作,並提供了每個操作的示例。
更改單個複合型別
使用 set、unset、update 和 upsert 操作來更改單個複合型別
- 使用
set設定複合型別,覆蓋任何現有值 - 使用
unset取消設定複合型別。與set: null不同,unset會完全刪除該欄位 - 使用
update更新複合型別 - 使用
upsert如果存在則update現有複合型別,否則set複合型別
例如,使用 update 更新 Order 中具有 Address 複合型別的必填 shippingAddress
const order = await prisma.order.update({
where: {
id: 'some-object-id',
},
data: {
shippingAddress: {
// Update just the zip field
update: {
zip: '41232',
},
},
},
})
對於可選的嵌入式型別,如 billingAddress,使用 upsert 如果記錄不存在則建立新記錄,如果存在則更新記錄
const order = await prisma.order.update({
where: {
id: 'some-object-id',
},
data: {
billingAddress: {
// Create the address if it doesn't exist,
// otherwise update it
upsert: {
set: {
street: '1084 Candycane Lane',
city: 'Silverlake',
zip: '84323',
},
update: {
zip: '84323',
},
},
},
},
})
你也可以使用 unset 操作來刪除可選的嵌入式型別。以下示例使用 unset 從 Order 中刪除 billingAddress
const order = await prisma.order.update({
where: {
id: 'some-object-id',
},
data: {
billingAddress: {
// Unset the billing address
// Removes "billingAddress" field from order
unset: true,
},
},
})
你可以在 updateMany 中使用過濾器來更新所有匹配複合型別的記錄。以下示例使用 is 過濾器匹配訂單列表中送貨地址的街道名稱
const orders = await prisma.order.updateMany({
where: {
shippingAddress: {
is: {
street: '555 Candy Cane Lane',
},
},
},
data: {
shippingAddress: {
update: {
street: '111 Candy Cane Drive',
},
},
},
})
更改多個複合型別
使用 set、push、updateMany 和 deleteMany 操作來更改複合型別列表
set:設定嵌入式複合型別列表,覆蓋任何現有列表push:將值推送到嵌入式複合型別列表的末尾updateMany:一次更新多個複合型別deleteMany:一次刪除多個複合型別
例如,使用 push 將新照片新增到 photos 列表
const product = prisma.product.update({
where: {
id: '62de6d328a65d8fffdae2c18',
},
data: {
photos: {
// Push a photo to the end of the photos list
push: [{ height: 100, width: 200, url: '1.jpg' }],
},
},
})
使用 updateMany 更新 url 為 1.jpg 或 2.png 的照片
const product = prisma.product.update({
where: {
id: '62de6d328a65d8fffdae2c18',
},
data: {
photos: {
updateMany: {
where: {
url: '1.jpg',
},
data: {
url: '2.png',
},
},
},
},
})
以下示例使用 deleteMany 刪除所有高度為 100 的照片
const product = prisma.product.update({
where: {
id: '62de6d328a65d8fffdae2c18',
},
data: {
photos: {
deleteMany: {
where: {
height: 100,
},
},
},
},
})
使用 upsert 插入複合型別
當你建立或更新具有唯一約束的複合型別中的值時,請注意 MongoDB 不會在記錄內部強制執行唯一值。瞭解更多。
要建立或更新複合型別,請使用 upsert 方法。你可以使用與上述 create 和 update 方法相同的複合操作。
例如,使用 upsert 來建立新產品或向現有產品新增照片
const product = await prisma.product.upsert({
where: {
name: 'Forest Runners',
},
create: {
name: 'Forest Runners',
price: 59.99,
colors: ['Red', 'Green'],
sizes: ['Small', 'Medium', 'Large'],
photos: [
{ height: 100, width: 200, url: '1.jpg' },
{ height: 100, width: 200, url: '2.jpg' },
],
},
update: {
photos: {
push: { height: 300, width: 400, url: '3.jpg' },
},
},
})
使用 delete 和 deleteMany 刪除包含複合型別的記錄
要刪除嵌入複合型別的記錄,請使用 delete 或 deleteMany 方法。這也會刪除嵌入的複合型別。
例如,使用 deleteMany 刪除所有 size 為 "Small" 的產品。這也會刪除任何嵌入的 photos。
const deleteProduct = await prisma.product.deleteMany({
where: {
sizes: {
equals: 'Small',
},
},
})
你還可以使用過濾器來刪除匹配複合型別的記錄。以下示例使用 some 過濾器刪除包含特定照片的產品
const product = await prisma.product.deleteMany({
where: {
photos: {
some: {
url: '2.jpg',
},
},
},
})
複合型別排序
你可以使用 orderBy 操作以升序或降序對結果進行排序。
例如,以下命令查詢所有訂單並按送貨地址中的城市名稱以升序排序
const orders = await prisma.order.findMany({
orderBy: {
shippingAddress: {
city: 'asc',
},
},
})
複合型別唯一欄位中的重複值
當你對具有唯一約束的複合型別記錄執行任何以下操作時,請務必小心。在這種情況下,MongoDB 不會在記錄內部強制執行唯一值。
- 當你建立記錄時
- 當你向記錄新增資料時
- 當你更新記錄中的資料時
如果你的 schema 有一個帶有 @@unique 約束的複合型別,MongoDB 會阻止你在包含此複合型別的兩個或多個記錄中為約束值儲存相同的值。然而,MongoDB 並不會阻止你在單個記錄中儲存同一欄位值的多個副本。
請注意,你可以使用 Prisma ORM 關係來解決此問題。
例如,在以下 schema 中,MailBox 有一個複合型別 addresses,它在 email 欄位上有一個 @@unique 約束。
type Address {
email String
}
model MailBox {
name String
addresses Address[]
@@unique([addresses.email])
}
以下程式碼建立了一個記錄,其中 address 中有兩個相同的值。MongoDB 在這種情況下不會丟擲錯誤,它會將 alice@prisma.io 儲存在 addresses 中兩次。
await prisma.MailBox.createMany({
data: [
{
name: 'Alice',
addresses: {
set: [
{
address: 'alice@prisma.io', // Not unique
},
{
address: 'alice@prisma.io', // Not unique
},
],
},
},
],
})
注意:如果你嘗試在兩個獨立的記錄中儲存相同的值,MongoDB 會丟擲錯誤。在上面的示例中,如果你嘗試為使用者 Alice 和使用者 Bob 儲存相同的電子郵件地址 alice@prisma.io,MongoDB 將不會儲存資料並丟擲錯誤。
使用 Prisma ORM 關係強制執行記錄中的唯一值
在上面的示例中,MongoDB 沒有強制執行巢狀地址名稱上的唯一約束。但是,你可以透過不同方式建模資料來強制執行記錄中的唯一值。為此,請使用 Prisma ORM 關係將複合型別轉換為集合。設定與此集合的關係,並在要唯一的欄位上放置唯一約束。
在以下示例中,MongoDB 會強制執行記錄中的唯一值。Mailbox 和 Address 模型之間存在關係。此外,Address 模型中的 name 欄位具有唯一約束。
model Address {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
mailbox Mailbox? @relation(fields: [mailboxId], references: [id])
mailboxId String? @db.ObjectId
@@unique([name])
}
model Mailbox {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
addresses Address[] @relation
}
await prisma.MailBox.create({
data: {
name: 'Alice',
addresses: {
create: [
{ name: 'alice@prisma.io' }, // Not unique
{ name: 'alice@prisma.io' }, // Not unique
],
},
},
})
如果你執行上述程式碼,MongoDB 將強制執行唯一約束。它不允許你的應用程式新增兩個名稱為 alice@prisma.io 的地址。