複合型
複合型はMongoDBでのみ利用可能です。
複合型(MongoDBでは組み込みドキュメントとして知られる)を使用すると、レコード内に他のレコードを埋め込むことができます。
複合型はv3.12.0で一般公開されました。以前はv3.10.0からプレビュー版として利用可能でした。
このページでは、以下の方法を説明します。
findFirst
とfindMany
を使用して複合型を含むレコードを検索するcreate
とcreateMany
を使用して複合型を持つ新しいレコードを作成するupdate
とupdateMany
を使用して既存のレコード内の複合型を更新するdelete
とdeleteMany
を使用して複合型を持つレコードを削除する
スキーマ例
以下の例ではこのスキーマを使用します
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
}
このスキーマでは、Product
モデルにPhoto[]
複合型があり、Order
モデルには2つの複合Address
型があります。shippingAddress
は必須ですが、billingAddress
はオプションです。
複合型を使用する際の考慮事項
現在、Prisma Clientで複合型を使用する際にはいくつかの制限があります。
findUnique()
は複合型でフィルタリングできませんaggregate
、groupBy()
、count
は複合操作をサポートしていません
複合型の必須フィールドのデフォルト値
バージョン4.0.0以降、以下のすべての条件が満たされている場合に複合型に対してデータベース読み取りを実行すると、Prisma Clientは結果にデフォルト値を挿入します。
条件
注
- これはモデルフィールドと同じ動作です。
- 読み取り操作では、Prisma Clientはデフォルト値を結果に挿入しますが、データベースには挿入しません。
例のスキーマで、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
: 複合型が一致する結果をフィルタリングします。1つ以上のフィールドが存在する必要があります。(例:配送先住所のストリート名で注文をフィルタリングする)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
: リスト内の1つ以上の項目が条件に一致する必要があります。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
を使用して、1つ以上の写真の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はレコード内部の一意な値を強制しないことに注意してください。詳細はこちら。
複合型は、create
またはcreateMany
メソッド内でset
操作を使用して作成できます。例えば、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,
},
},
})
製品が複数の写真のリストを含むケースをモデル化するには、複数の複合型を一度に設定できます。
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
メソッド内でも機能します。例えば、それぞれ写真のリストを含む複数の製品を作成できます。
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
内の必須のshippingAddress
をAddress
複合型で更新します。
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
を使用して、サイズが"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はレコード内部の一意な値を強制しません。
- レコードを作成するとき
- レコードにデータを追加するとき
- レコードのデータを更新するとき
スキーマに@@unique
制約を持つ複合型がある場合、MongoDBは、この複合型を含む2つ以上のレコードで、制約付きの値に同じ値を保存することを防ぎます。ただし、MongoDBは1つのレコード内に同じフィールド値を複数コピー保存することを妨げません。
この問題をPrisma ORMリレーションを使用して回避できることに注意してください。
例えば、以下のスキーマでは、MailBox
は複合型addresses
を持ち、email
フィールドに@@unique
制約があります。
type Address {
email String
}
model MailBox {
name String
addresses Address[]
@@unique([addresses.email])
}
以下のコードは、address
に2つの同一の値を持つレコードを作成します。MongoDBはこの状況でエラーをスローせず、alice@prisma.io
をaddresses
に2回保存します。
await prisma.MailBox.createMany({
data: [
{
name: 'Alice',
addresses: {
set: [
{
address: 'alice@prisma.io', // Not unique
},
{
address: 'alice@prisma.io', // Not unique
},
],
},
},
],
})
注:異なる2つのレコードに同じ値を保存しようとすると、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
という名前の2つのアドレスを追加することは許可されません。