Skip to content

Query Operations

In lieu of more extensive documentation, this page documents query operations on the prisma client such as creating, finding, updating and deleting records.

Schema

The examples use the following prisma schema models:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id      String   @id @default(cuid())
  name    String
  points  Float    @default(0)
  meta    Json     @default("{}")
  number  Decimal  @default(0)
  emails  String[]
  posts   Post[]
  profile Profile?
}

model Profile {
  id      Int     @id @default(autoincrement())
  user    User    @relation(fields: [user_id], references: [id])
  user_id String  @unique
  bio     String
  image   Bytes?
  city    String?
  country String?
  views   Int     @default(0)
}

model Post {
  id          String     @id @default(cuid())
  created_at  DateTime   @default(now())
  updated_at  DateTime   @updatedAt
  title       String
  published   Boolean
  views       Int        @default(0)
  description String?
  author      User?      @relation(fields: [author_id], references: [id])
  author_id   String?
  categories  Category[]
}

model Category {
  id    Int    @id @default(autoincrement())
  posts Post[]
  name  String
}

Creating

Single Record

user = await db.user.create(
    data={
        'name': 'Robert',
    },
)

Many Records

users = await db.user.create_many(
    data=[
        {'name': 'Tegan'},
        {'name': 'Alfie'},
        {'name': 'Robert'},
    ]
)

Warning

the skip_duplicates argument is not supported when using MongoDB, SQLServer, or SQLite

users = await db.user.create_many(
    data=[
        {'id': 'abc', 'name': 'Tegan'},
        {'id': 'def', 'name': 'Alfie'},
        {'id': 'ghi', 'name': 'Robert'},
    ],
    skip_duplicates=True,
)

Relational Records

user = await db.user.create(
    data={
        'name': 'Robert',
        'profile': {
            'create': {
                'bio': 'My very cool bio!',
            }
        }
    }
)
user = await db.user.create(
    data={
        'name': 'Robert',
        'posts': {
            'create': [
                {
                    'title': 'My first post!',
                    'published': True,
                },
                {
                    'title': 'My draft post!',
                    'published': False,
                },
            ]
        }
    }
)

Finding

Unique Records

There are two different methods you can use here to find a unique record. Which one you use depends on the context surrounding the query:

  • Use find_unique if it is expected for the record to not exist in the database
  • Use find_unique_or_raise if it is unexpected for the record to not exist in the database

user = await db.user.find_unique(
    where={
        'id': '1',
    }
)
user = await db.user.find_unique_or_raise(
    where={
        'id': '2',
    },
    include={
        'posts': True,
    },
)

A Single Record

There are two different methods you can use here to find a single record. Which one you use depends on the context surrounding the query:

  • Use find_first if it is expected for the record to not exist in the database
  • Use find_first_or_raise if it is unexpected for the record to not exist in the database

post = await db.post.find_first(
    where={
        'title': {'contains': 'Post'},
    },
)
post = await db.post.find_first_or_raise(
    where={
        'title': {'contains': 'Post'},
    },
)
post = await db.post.find_first(
    skip=2,
    where={
        'title': {
            'contains': 'Post'
        },
    },
    cursor={
        'id': 'abcd',
    },
    include={
        'author': True,
    },
    order={
        'id': 'asc',
    }
)

Multiple Records

posts = await db.post.find_many(
    where={
        'published': True,
    },
)
posts = await db.post.find_many(
    take=5,
    skip=1,
    where={
        'published': True,
    },
    cursor={
        'id': 'desc',
    },
    include={
        'categories': True,
    },
    order={
        'id': 'desc',
    }
)

Distinct Records

The following query will find all Profile records that have a distinct city field.

profiles = await db.profiles.find_many(
    distinct=['city'],
)
# [
#  { city: 'Paris' },
#  { city: 'Lyon' },
# ]

You can also filter by distinct combinations, for example the following query will return all records that have a distinct city and country combination.

profiles = await db.profiles.find_many(
    distinct=['city', 'country'],
)
# [
#  { city: 'Paris', country: 'France' },
#  { city: 'Paris', country: 'Denmark' },
#  { city: 'Lyon', country: 'France' },
# ]

You can currently only use distinct with find_many() and find_first() queries.

Filtering by Relational Fields

Within the filter you can query for everything you would normally query for, like it was a find_first() call on the relational field, for example:

post = await db.post.find_first(
    where={
        'author': {
            'is': {
                'name': 'Robert',
            },
        },
    },
)
user = await db.user.find_first(
    where={
        'name': 'Robert',
    },
)

One to One

post = await db.post.find_first(
    where={
        'author': {
            'is': {
                'name': 'Robert',
            },
            'is_not': {
                'name': 'Tegan',
            },
        },
    },
)

One to Many

Excluding
post = await db.post.find_first(
    where={
        'categories': {
            'none': {
                'name': 'Exclude Category',
            },
        },
    },
)
At Least One
post = await db.post.find_first(
    where={
        'categories': {
            'some': {
                'name': {
                    'contains': 'Special',
                },
            },
        },
    },
)
Every
post = await db.post.find_first(
    where={
        'categories': {
            'every': {
                'name': {
                    'contains': 'Category',
                },
            },
        },
    },
)

Filtering by Field Values

Note

The examples for filtering fields are simply to showcase possible arguments, all the arguments passed together will result in either an invalid query or no records being found.

String Fields

Warning

Case insensitive filtering is only available on PostgreSQL and MongoDB Full-text search is only available on PostgreSQL and MySQL

post = await db.post.find_first(
    where={
        'description': 'Must be exact match',
        # or
        'description': {
            'equals': 'example_string',
            'not_in': ['ignore_string_1', 'ignore_string_2'],
            'lt': 'z',
            'lte': 'y',
            'gt': 'a',
            'gte': 'b',
            'contains': 'string must be present',
            'startswith': 'must start with string',
            'endswith': 'must end with string',
            'in': ['find_string_1', 'find_string_2'],
            'mode': 'insensitive',
            'search': 'full-text query',
            'not': {
                # recursive type
                'contains': 'string must not be present',
                'mode': 'default',
                ...
            },
        },
    },
)

Please visit Prisma's documentation for more information on full-text search.

Integer Fields

post = await db.post.find_first(
    where={
        'views': 10,
        # or
        'views': {
            'equals': 1,
            'in': [1, 2, 3],
            'not_in': [4, 5, 6],
            'lt': 10,
            'lte': 9,
            'gt': 0,
            'gte': 1,
            'not': {
                # recursive type
                'gt': 10,
                ...
            },
        },
    },
)

Float Fields

user = await db.user.find_first(
    where={
        'points': 10.0,
        # or
        'points': {
            'equals': 10.0,
            'in': [1.2, 1.3, 1.4],
            'not_in': [4.7, 53.4, 6.8],
            'lt': 100.5,
            'lte': 9.9,
            'gt': 0.0,
            'gte': 1.2,
            'not': {
                # recursive type
                'gt': 10.0,
                ...
            },
        },
    },
)

DateTime Fields

from datetime import datetime

post = await db.post.find_first(
    where={
        'updated_at': datetime.now(),
        # or
        'updated_at': {
            'equals': datetime.now(),
            'not_in': [datetime.now(), datetime.utcnow()],
            'lt': datetime.now(),
            'lte': datetime.now(),
            'gt': datetime.now(),
            'gte': datetime.now(),
            'in': [datetime.now(), datetime.utcnow()],
            'not': {
                # recursive type
                'equals': datetime.now(),
                ...
            },
        },
    },
)

Boolean Fields

post = await db.post.find_first(
    where={
        'published': True,
        # or
        'published': {
            'equals': True,
            'not': False,
        },
    },
)

Json Fields

Note

Json fields must match exactly.

Warning

Json fields are not supported on SQLite

from prisma import Json

user = await db.user.find_first(
    where={
        'meta': Json({'country': 'Scotland'})
        # or
        'meta': {
            'equals': Json.keys(country='Scotland'),
            'not': Json(['foo']),
        }
    }
)

Bytes Fields

Note

Bytes fields are encoded to and from Base64

from prisma import Base64

profile = await db.profile.find_first(
    where={
        'image': Base64.encode(b'my binary data'),
        # or
        'image': {
            'equals': Base64.encode(b'my binary data'),
            'in': [Base64.encode(b'my binary data')],
            'not_in': [Base64.encode(b'my other binary data')],
            'not': Base64(b'WW91IGZvdW5kIGFuIGVhc3RlciBlZ2chIExldCBAUm9iZXJ0Q3JhaWdpZSBrbm93IDop'),
        },
    },
)

Decimal Fields

from decimal import Decimal

user = await db.user.find_first(
    where={
        'number': Decimal(1),
        # or
        'number': {
            'equals': Decimal('1.23823923283'),
            'in': [Decimal('1.3'), Decimal('5.6')],
            'not_in': [Decimal(10), Decimal(20)],
            'gte': Decimal(5),
            'gt': Decimal(11),
            'lt': Decimal(4),
            'lte': Decimal(3),
            'not': Decimal('123456.28'),
            # or
            'not': {
                # recursive type
                ...
            }
        },
    },
)

Lists fields

Warning

Scalar list fields are only supported on PostgreSQL, CockroachDB and MongoDB

Every scalar type can also be defined as a list, for example:

user = await db.user.find_first(
    where={
        'emails': {
            # only one of the following fields is allowed at the same time
            'has': 'robert@craigie.dev',
            'has_every': ['email1', 'email2'],
            'has_some': ['email3', 'email4'],
            'is_empty': True,
        },
    },
)

Combining arguments

All of the above mentioned filters can be combined with other filters using AND, NOT and OR.

AND

The following query will return the first post where the title contains the words prisma and test.

post = await db.post.find_first(
    where={
        'AND': [
            {
                'title': {
                    'contains': 'prisma',
                },
            },
            {
                'title': {
                    'contains': 'test',
                },
            },
        ],
    },
)

OR

The following query will return the first post where the title contains the word prisma or is published.

post = await db.post.find_first(
    where={
        'OR': [
            {
                'title': {
                    'contains': 'prisma',
                },
            },
            {
                'published': True,
            },
        ],
    },
)

NOT

The following query will return the first post where the title is not My test post

post = await db.post.find_first(
    where={
        'NOT' [
            {
                'title': 'My test post',
            },
        ],
    },
)

Deleting

Unique Record

post = await db.post.delete(
    where={
        'id': 'cksc9m7un0028f08zwycxtjr1',
    },
)
post = await db.post.delete(
    where={
        'id': 'cksc9m1vu0021f08zq0066pnz',
    },
    include={
        'categories': True,
    }
)

Multiple Records

total = await db.post.delete_many(
    where={
        'published': False,
    }
)

Updating

Unique Record

post = await db.post.update(
    where={
        'id': 'cksc9lp7w0014f08zdkz0mdnn',
    },
    data={
        'views': {
            'increment': 1,
        }
    },
    include={
        'categories': True,
    }
)

Multiple Records

total = await db.post.update_many(
    where={
        'published': False
    },
    data={
        'views': 0,
    },
)

Creating On Not Found

post = await db.post.upsert(
    where={
        'id': 'cksc9ld4z0007f08z7obo806s',
    },
    data={
        'create': {
            'title': 'This post was created!',
            'published': False,
        },
        'update': {
            'title': 'This post was updated',
            'published': True,
        },
    },
    include={
        'categories': True,
    }
)

Updating Atomic Fields

If a field is an int or float type then it can be atomically updated, i.e. mathematical operations can be applied without knowledge of the previous value.

Integer Fields

post = await db.post.update(
    where={
        'id': 'abc',
    },
    data={
        'views': 1,
        # or
        'views': {
            'set': 5,
            'increment': 1,
            'decrement': 2,
            'multiply': 5,
            'divide': 10,
        },
    },
)

Float Fields

user = await db.user.update(
    where={
        'id': 'abc',
    },
    data={
        'points': 1.0,
        # or
        'points': {
            'set': 1.0,
            'increment': 1.5,
            'decrement': 0.5,
            'multiply': 2.5,
            'divide': 3.0,
        },
    },
)

Updating List Fields

Warning

Scalar list fields are only supported on PostgreSQL, CockroachDB and MongoDB

Warning

The push operation is not supported on CockroachDB

user = await db.user.update(
    where={
        'id': 'cksc9lp7w0014f08zdkz0mdnn',
    },
    data={
        'email': {
            'set': ['robert@craigie.dev', 'robert@example.com'],
            # or
            'push': ['robert@example.com'],
        },
    }
)

Aggregrating

Counting Records

total = await db.post.count(
    where={
        'published': True,
    },
)
total = await db.post.count(
    take=10,
    skip=1,
    where={
        'published': True,
    },
    cursor={
        'id': 'cksca3xm80035f08zjonuubik',
    },
)

Grouping Records

Warning

You can only order by one field at a time however this is not possible to represent with python types

results = await db.profile.group_by(['country'])
# [
#   {'country': 'Denmark'},
#   {'country': 'Scotland'},
# ]

results = await db.profile.group_by(['country'], count=True)
# [
#   {'country': 'Denmark', '_count': {'_all': 20}},
#   {'country': 'Scotland', '_count': {'_all': 1}},
# ]

results = await db.profile.group_by(
    by=['country', 'city'],
    count={
        '_all': True,
        'city': True,
    },
    sum={
        'views': True,
    },
    order={
        'country': 'desc',
    },
    having={
        'views': {
            '_avg': {
                'gt': 200,
            },
        },
    },
)
# [
#   {
#       'country': 'Scotland',
#       'city': 'Edinburgh',
#       '_sum': {'views': 250},
#       '_count': {'_all': 1, 'city': 1}
#   },
#   {
#       'country': 'Denmark',
#       'city': None,
#       '_sum': {'views': 6000},
#       '_count': {'_all': 12, 'city': 0}
#   },
#   {
#       'country': 'Denmark',
#       'city': 'Copenhagen',
#       '_sum': {'views': 8000},
#       '_count': {'_all': 8, 'city': 8}
#   },
# ]

Batching Write Queries

async with db.batch_() as batcher:
    batcher.user.create({'name': 'Robert'})
    batcher.user.create({'name': 'Tegan'})

Raw Queries

Note

SQL queries are sent directly to the database so you must use the syntax for your specific database provider

Warning

Raw query results are raw dictionaries unless the model argument is specified

Write Queries

total = await db.execute_raw(
    '''
    SELECT *
    FROM User
    WHERE User.id = ?
    ''',
    'cksca3xm80035f08zjonuubik'
)

Selecting Multiple Records

posts = await db.query_raw(
    '''
    SELECT *
    FROM Post
    WHERE Post.published IS TRUE
    '''
)

Type Safety

from prisma.models import Post

posts = await db.query_raw(
    '''
    SELECT *
    FROM Post
    WHERE Post.published IS TRUE
    ''',
    model=Post,
)

Selecting a Single Record

post = await db.query_first(
    '''
    SELECT *
    FROM Post
    WHERE Post.published IS TRUE
    LIMIT 1
    '''
)

Type Safety

from prisma.models import Post

post = await db.query_first(
    '''
    SELECT *
    FROM Post
    WHERE Post.views > 50
    LIMIT 1
    ''',
    model=Post
)