Drizzle Queries

WARNING

이 페이지는 Drizzle 버전 1.0.0-beta.1 이상에서 사용 가능한 개념을 설명합니다.

npm
yarn
pnpm
bun
npm i drizzle-orm@beta
npm i drizzle-kit@beta -D

PostgreSQL
SQLite
MySQL
SingleStore

Drizzle ORM은 SQL 위에 구축된 얇은 타입 레이어로 설계되었습니다. 우리는 TypeScript에서 SQL 데이터베이스를 운영하는 최고의 방법을 설계했다고 믿으며, 이제 이를 더욱 개선할 때입니다.

관계형 쿼리는 SQL 데이터베이스에서 중첩된 관계형 데이터를 쿼리할 때 여러 조인과 복잡한 데이터 매핑을 피하면서 뛰어난 개발자 경험을 제공하기 위해 만들어졌습니다.

이것은 기존 스키마 정의 및 쿼리 빌더의 확장입니다. 필요에 따라 선택적으로 사용할 수 있습니다. 우리는 최고 수준의 개발자 경험과 성능을 모두 제공할 수 있도록 했습니다.

index.ts
schema.ts
import { relations } from './schema';
import { drizzle } from 'drizzle-orm/...';

const db = drizzle({ relations });

const result = await db.query.users.findMany({
	with: {
		posts: true			
	},
});
[{
	id: 10,
	name: "Dan",
	posts: [
		{
			id: 1,
			content: "SQL is awesome",
			authorId: 10,
		},
		{
			id: 2,
			content: "But check relational queries",
			authorId: 10,
		}
	]
}]

관계형 쿼리는 Drizzle의 원래 **쿼리 빌더**의 확장입니다. drizzle() 초기화 시 스키마 파일에서 모든 tablesrelations를 제공한 다음 db.query API를 사용하면 됩니다.

drizzle import 경로는 사용 중인 **데이터베이스 드라이버**에 따라 다릅니다.

index.ts
schema.ts
relations.ts
import { relations } from './relations';
import { drizzle } from 'drizzle-orm/...';

const db = drizzle({ relations });

await db.query.users.findMany(...);

Drizzle은 .findMany().findFirst() API를 제공합니다.

Find many

const users = await db.query.users.findMany();
// result type
const result: {
	id: number;
	name: string;
	verified: boolean;
	invitedBy: number | null;
}[];

Find first

.findFirst()는 쿼리에 limit 1을 추가합니다.

const user = await db.query.users.findFirst();
// result type
const result: {
	id: number;
	name: string;
	verified: boolean;
	invitedBy: number | null;
};

Include relations

With 연산자를 사용하면 여러 관련 테이블의 데이터를 결합하고 결과를 적절하게 집계할 수 있습니다.

댓글이 있는 모든 게시물 가져오기:

const posts = await db.query.posts.findMany({
	with: {
		comments: true,
	},
});

댓글이 있는 첫 번째 게시물 가져오기:

const post = await db.query.posts.findFirst({
	with: {
		comments: true,
	},
});

필요한 만큼 중첩된 with 문을 연결할 수 있습니다. 중첩된 with 쿼리에 대해 Drizzle은 Core Type API를 사용하여 타입을 추론합니다.

게시물이 있는 모든 사용자 가져오기. 각 게시물에는 댓글 목록이 포함되어야 합니다:

const users = await db.query.users.findMany({
	with: {
		posts: {
			with: {
				comments: true,
			},
		},
	},
});

Partial fields select

columns 매개변수를 사용하면 데이터베이스에서 가져올 컬럼을 포함하거나 제외할 수 있습니다.

Drizzle은 쿼리 수준에서 부분 선택을 수행하므로 데이터베이스에서 추가 데이터가 전송되지 않습니다.

Drizzle은 단일 SQL 문을 출력합니다.

id, content만 포함하고 comments를 포함하는 모든 게시물 가져오기:

const posts = await db.query.posts.findMany({
	columns: {
		id: true,
		content: true,
	},
	with: {
		comments: true,
	}
});

content 없이 모든 게시물 가져오기:

const posts = await db.query.posts.findMany({
	columns: {
		content: false,
	},
});

truefalse 선택 옵션이 모두 있으면 모든 false 옵션은 무시됩니다.

name 필드를 포함하고 id 필드를 제외하면 id 제외는 중복됩니다. name을 제외한 모든 필드는 어쨌든 제외됩니다.

동일한 쿼리에서 필드 제외 및 포함:

const users = await db.query.users.findMany({
	columns: {
		name: true,
		id: false //ignored
	},
});
// result type
const users: {
	name: string;
};

중첩된 관계에서 컬럼만 포함:

const res = await db.query.users.findMany({
	columns: {},
	with: {
		posts: true
	}
});
// result type
const res: {
	posts: {
		id: number,
		text: string
	}
}[];

Nested partial fields select

**partial select**와 마찬가지로 중첩된 관계의 컬럼을 포함하거나 제외할 수 있습니다:

const posts = await db.query.posts.findMany({
	columns: {
		id: true,
		content: true,
	},
	with: {
		comments: {
			columns: {
				authorId: false
			}
		}
	}
});

Select filters

SQL과 유사한 쿼리 빌더와 마찬가지로, 관계형 쿼리 API를 사용하면 연산자 목록으로 필터와 조건을 정의할 수 있습니다.

drizzle-orm에서 가져오거나 콜백 구문에서 사용할 수 있습니다:

const users = await db.query.users.findMany({
	where: {
		id: 1
	}
});
select * from users where id = 1

id=1이고 특정 날짜 이전에 생성된 댓글이 있는 게시물 찾기:

await db.query.posts.findMany({
  where: {
    id: 1,
  },
  with: {
    comments: {
      where: {
        createdAt: { lt: new Date() },
      },
    },
  },
});

모든 필터 연산자 목록

where: {
    OR: [],
    AND: [],
    NOT: {},
    RAW: (table) => sql`${table.id} = 1`,

    // filter by relations
    [relation]: {},

	  // filter by columns
    [column]: {
      OR: [],
      AND: [],
      NOT: {},
      eq: 1,
      ne: 1,
      gt: 1,
      gte: 1,
      lt: 1,
      lte: 1,
      in: [1],
      notIn: [1],
      like: "",
      ilike: "",
      notLike: "",
      notIlike: "",
      isNull: true,
      isNotNull: true,
      arrayOverlaps: [1, 2],
      arrayContained: [1, 2],
      arrayContains: [1, 2]
    },
},

예제

simple eq
using AND
using OR
using NOT
complex example using RAW
const response = db.query.users.findMany({
  where: {
    age: 15,
  },
});
select "users"."id" as "id", "users"."name" as "name"
from "users" 
where ("users"."age" = 15)

Relations Filters

Drizzle Relations를 사용하면 쿼리하는 테이블뿐만 아니라 쿼리에 포함하는 모든 테이블로도 필터링할 수 있습니다.

예제: ID>10이고 “M”으로 시작하는 콘텐츠가 있는 게시물이 하나 이상 있는 모든 users 가져오기

const usersWithPosts = await db.query.usersTable.findMany({
  where: {
    id: {
      gt: 10
    },
    posts: {
      content: {
        like: 'M%'
      }
    }
  },
});

예제: 사용자에게 게시물이 하나 이상 있는 경우에만 게시물이 있는 모든 users 가져오기

const response = db.query.users.findMany({
  with: {
    posts: true,
  },
  where: {
    posts: true,
  },
});

Limit & Offset

Drizzle ORM은 쿼리 및 중첩 엔티티에 대해 limitoffset API를 제공합니다.

5개의 게시물 찾기:

await db.query.posts.findMany({
	limit: 5,
});

게시물을 찾고 최대 3개의 댓글 가져오기:

await db.query.posts.findMany({
	with: {
		comments: {
			limit: 3,
		},
	},
});
IMPORTANT

이제 offset을 with 테이블에서도 사용할 수 있습니다!

await db.query.posts.findMany({
	limit: 5,
	offset: 2, // correct ✅
	with: {
		comments: {
			offset: 3, // correct ✅
			limit: 3,
		},
	},
});

5번째에서 10번째 게시물의 댓글이 있는 게시물 찾기:

await db.query.posts.findMany({
	with: {
		comments: true,
	},
  limit: 5,
  offset: 5,
});

Order By

Drizzle은 관계형 쿼리 빌더에서 정렬을 위한 API를 제공합니다.

동일한 정렬 **core API**를 사용하거나 import 없이 콜백에서 order by 연산자를 사용할 수 있습니다.

important

동일한 테이블에서 여러 orderBy 문을 사용하면 추가한 순서대로 쿼리에 포함됩니다

await db.query.posts.findMany({
  orderBy: {
    id: "asc",
  },
});

asc + desc로 정렬:

  await db.query.posts.findMany({
    orderBy: { id: "asc" },
    with: {
      comments: {
        orderBy: { id: "desc" },
      },
    },
  });

order by 문에서 사용자 지정 sql을 사용할 수도 있습니다:

await db.query.posts.findMany({
  orderBy: (t) => sql`${t.id} asc`,
  with: {
    comments: {
      orderBy: (t, { desc }) => desc(t.id),
    },
  },
});

Include custom fields

관계형 쿼리 API를 사용하면 사용자 지정 추가 필드를 추가할 수 있습니다. 데이터를 검색하고 추가 함수를 적용해야 할 때 유용합니다.

IMPORTANT

현재 extras에서는 집계가 지원되지 않으므로 **core queries**를 사용하세요.

import { sql } from 'drizzle-orm';

await db.query.users.findMany({
	extras: {
		loweredName: sql`lower(${users.name})`,
	},
})
await db.query.users.findMany({
	extras: {
		loweredName: (users, { sql }) => sql`lower(${users.name})`,
	},
})

키로서의 lowerName은 반환된 객체의 모든 필드에 포함됩니다.

IMPORTANT

extras 필드에 .as("<alias>")를 지정하면 drizzle이 이를 무시합니다

그룹이 있는 모든 사용자를 검색하되 fullName 필드(firstName과 lastName의 연결)를 포함하려면, Drizzle 관계형 쿼리 빌더와 함께 다음 쿼리를 사용할 수 있습니다.

const res = await db.query.users.findMany({
	extras: {
		fullName: (users, { sql }) => sql<string>`concat(${users.name}, " ", ${users.name})`,
	},
	with: {
		usersToGroups: {
			with: {
				group: true,
			},
		},
	},
});
// result type
const res: {
	id: number;
	name: string;
	verified: boolean;
	invitedBy: number | null;
	fullName: string;
	usersToGroups: {
			group: {
					id: number;
					name: string;
					description: string | null;
			};
	}[];
}[];

댓글이 있는 모든 게시물을 검색하고 게시물 콘텐츠 크기와 각 댓글 콘텐츠 크기를 계산하는 추가 필드를 추가하려면:

const res = await db.query.posts.findMany({
	extras: {
		contentLength: (table, { sql }) => sql<number>`length(${table.content})`,
	},
	with: {
		comments: {
			extras: {
				commentSize: (table, { sql }) => sql<number>`length(${table.content})`,
			},
		},
	},
});
// result type
const res: {
	id: number;
	createdAt: Date;
	content: string;
	authorId: number | null;
	contentLength: number;
	comments: {
			id: number;
			createdAt: Date;
			content: string;
			creator: number | null;
			postId: number | null;
			commentSize: number;
	}[];
};

Include subqueries

관계형 쿼리 내에서 서브쿼리를 사용하여 사용자 지정 SQL 구문의 강력함을 활용할 수도 있습니다

각 사용자의 게시물 및 총 게시물 수와 함께 사용자 가져오기

import { posts } from './schema';
import { eq } from 'drizzle-orm';

await db.query.users.findMany({
  with: {
    posts: true
  },
  extras: {
    totalPostsCount: (table) => db.$count(posts, eq(posts.authorId, table.id)),
  }
});
select "d0"."id" as "id", "d0"."name" as "name", "posts"."r" as "posts", 
((select count(*) from "posts" where "posts"."author_id" = "d0"."id")) as "totalPostsCount" 
from "users" as "d0" 
left join lateral(
  select coalesce(json_agg(row_to_json("t".*)), '[]') as "r" 
  from (select "d1"."id" as "id", "d1"."content" as "content", "d1"."author_id" as "authorId" from "posts" as "d1" where "d0"."id" = "d1"."author_id") as "t"
) as "posts" on true

Prepared statements

Prepared statements는 쿼리 성능을 크게 향상시키도록 설계되었습니다 — 여기를 참조하세요.

이 섹션에서는 Drizzle 관계형 쿼리 빌더를 사용하여 플레이스홀더를 정의하고 prepared statements를 실행하는 방법을 배울 수 있습니다.

where에서 플레이스홀더
PostgreSQL
MySQL
SQLite
const prepared = db.query.users.findMany({
    where: { id: { eq: sql.placeholder("id") } },
    with: {
      posts: {
        where: { id: 1 },
      },
    },
}).prepare("query_name");

const usersWithPosts = await prepared.execute({ id: 1 });
limit에서 플레이스홀더
PostgreSQL
MySQL
SQLite
const prepared = db.query.users.findMany({
    with: {
      posts: {
        limit: sql.placeholder("limit"),
      },
    },
  }).prepare("query_name");

const usersWithPosts = await prepared.execute({ limit: 1 });
offset에서 플레이스홀더
PostgreSQL
MySQL
SQLite
const prepared = db.query.users.findMany({
	offset: sql.placeholder('offset'),
	with: {
		posts: true,
	},
}).prepare('query_name');

const usersWithPosts = await prepared.execute({ offset: 1 });
여러 플레이스홀더
PostgreSQL
MySQL
SQLite
const prepared = db.query.users.findMany({
    limit: sql.placeholder("uLimit"),
    offset: sql.placeholder("uOffset"),
    where: {
      OR: [{ id: { eq: sql.placeholder("id") } }, { id: 3 }],
    },
    with: {
      posts: {
        where: { id: { eq: sql.placeholder("pid") } },
        limit: sql.placeholder("pLimit"),
      },
    },
}).prepare("query_name");

const usersWithPosts = await prepared.execute({ pLimit: 1, uLimit: 3, uOffset: 1, id: 2, pid: 6 });