複合類型
複合類型僅適用於 MongoDB。
複合類型,在 MongoDB 中稱為 嵌入式文件,可讓您將記錄嵌入到其他記錄中。
我們在 v3.12.0 版本中將複合類型設為正式發佈。它們先前在 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,
},
},
})
若要模擬 product
包含多個 photos
列表的情況,您可以一次 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
刪除所有 height
為 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 在這種情況下不會擲回錯誤,並且會在 addresses
中儲存兩次 alice@prisma.io
。
await prisma.MailBox.createMany({
data: [
{
name: 'Alice',
addresses: {
set: [
{
address: 'alice@prisma.io', // Not unique
},
{
address: 'alice@prisma.io', // Not unique
},
],
},
},
],
})
請注意:如果您嘗試在兩個不同的記錄中儲存相同的值。在我們的範例中,如果您嘗試為使用者 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
的地址。