SQL Select

Drizzle์€ ํƒ€์ž… ์•ˆ์ „์„ฑ๊ณผ ์กฐํ•ฉ ๊ฐ€๋Šฅ์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฐ€์žฅ SQL๊ณผ ์œ ์‚ฌํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ฐฉ์–ธ์˜ ๋Œ€๋ถ€๋ถ„์˜ ์ฟผ๋ฆฌ ๊ธฐ๋Šฅ๊ณผ ์—ญ๋Ÿ‰์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•˜๋ฉฐ, ์•„์ง ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์€ ๊ฐ•๋ ฅํ•œ sql ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋œ users ํ…Œ์ด๋ธ”์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค:

PostgreSQL
MySQL
SQLite
SingleStore
import { pgTable, serial, text } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  age: integer('age'),
});

๊ธฐ๋ณธ select

๋ชจ๋“  ์ปฌ๋Ÿผ์„ ํฌํ•จํ•˜์—ฌ ํ…Œ์ด๋ธ”์˜ ๋ชจ๋“  ํ–‰ ์„ ํƒ:

const result = await db.select().from(users);
/*
  {
    id: number;
    name: string;
    age: number | null;
  }[]
*/
select "id", "name", "age" from "users";

๊ฒฐ๊ณผ ํƒ€์ž…์€ ์ปฌ๋Ÿผ์˜ null ํ—ˆ์šฉ ์—ฌ๋ถ€๋ฅผ ํฌํ•จํ•˜์—ฌ ํ…Œ์ด๋ธ” ์ •์˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž๋™์œผ๋กœ ์ถ”๋ก ๋ฉ๋‹ˆ๋‹ค.

Drizzle์€ select *๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  ํ•ญ์ƒ select ์ ˆ์— ์ปฌ๋Ÿผ์„ ๋ช…์‹œ์ ์œผ๋กœ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค.
์ด๋Š” ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์—์„œ ํ•„๋“œ ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ๋‚ด๋ถ€์ ์œผ๋กœ ํ•„์š”ํ•˜๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ ์ข‹์€ ๊ด€ํ–‰์œผ๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค.

๋ถ€๋ถ„ select

๊ฒฝ์šฐ์— ๋”ฐ๋ผ ํ…Œ์ด๋ธ”์—์„œ ์ปฌ๋Ÿผ์˜ ์ผ๋ถ€๋งŒ ์„ ํƒํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. .select() ๋ฉ”์„œ๋“œ์— ์„ ํƒ ๊ฐ์ฒด๋ฅผ ์ œ๊ณตํ•˜์—ฌ ์ด๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

const result = await db.select({
  field1: users.id,
  field2: users.name,
}).from(users);

const { field1, field2 } = result[0];
select "id", "name" from "users";

SQL๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ž„์˜์˜ ํ‘œํ˜„์‹์„ ์„ ํƒ ํ•„๋“œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

const result = await db.select({
  id: users.id,
  lowerName: sql<string>`lower(${users.name})`,
}).from(users);
select "id", lower("name") from "users";
IMPORTANT

sql<string>์„ ์ง€์ •ํ•จ์œผ๋กœ์จ ํ•„๋“œ์˜ ์˜ˆ์ƒ ํƒ€์ž…์ด string์ž„์„ Drizzle์— ์•Œ๋ ค์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ž˜๋ชป ์ง€์ •ํ•˜๋ฉด(์˜ˆ: ๋ฌธ์ž์—ด๋กœ ๋ฐ˜ํ™˜๋  ํ•„๋“œ์— sql<number>๋ฅผ ์‚ฌ์šฉ) ๋Ÿฐํƒ€์ž„ ๊ฐ’์ด ์˜ˆ์ƒ ํƒ€์ž…๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Drizzle์€ ์ œ๊ณต๋œ ํƒ€์ž… ์ œ๋„ค๋ฆญ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํƒ€์ž… ์บ์ŠคํŒ…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ์ •๋ณด๋Š” ๋Ÿฐํƒ€์ž„์— ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๋œ ๊ฐ’์— ๋Ÿฐํƒ€์ž„ ๋ณ€ํ™˜์„ ์ ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ .mapWith() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์กฐ๊ฑด๋ถ€ select

ํŠน์ • ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋™์  ์„ ํƒ ๊ฐ์ฒด๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

async function selectUsers(withName: boolean) {
  return db
    .select({
      id: users.id,
      ...(withName ? { name: users.name } : {}),
    })
    .from(users);
}

const users = await selectUsers(true);

Distinct select (์ค‘๋ณต ์ œ๊ฑฐ ์„ ํƒ)

๋ฐ์ดํ„ฐ์…‹์—์„œ ๊ณ ์œ ํ•œ ํ–‰๋งŒ ๊ฒ€์ƒ‰ํ•˜๋ ค๋ฉด .select() ๋Œ€์‹  .selectDistinct()๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

await db.selectDistinct().from(users).orderBy(users.id, users.name);

await db.selectDistinct({ id: users.id }).from(users).orderBy(users.id);
select distinct "id", "name" from "users" order by "id", "name";

select distinct "id" from "users" order by "id";

PostgreSQL์—์„œ๋Š” distinct on ์ ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ ์œ ํ•œ ํ–‰์ด ๊ฒฐ์ •๋˜๋Š” ๋ฐฉ์‹์„ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค:

IMPORTANT

distinct on ์ ˆ์€ PostgreSQL์—์„œ๋งŒ ์ง€์›๋ฉ๋‹ˆ๋‹ค.

await db.selectDistinctOn([users.id]).from(users).orderBy(users.id);
await db.selectDistinctOn([users.name], { name: users.name }).from(users).orderBy(users.name);
select distinct on ("id") "id", "name" from "users" order by "id";
select distinct on ("name") "name" from "users" order by "name";

Advanced select

TypeScript๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Drizzle API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์–‘ํ•˜๊ณ  ์œ ์—ฐํ•œ ๋ฐฉ์‹์œผ๋กœ select ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ณ ๊ธ‰ ๋ถ€๋ถ„ select์— ๋Œ€ํ•œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ž…๋‹ˆ๋‹ค. ๋” ์ž์„ธํ•œ ๊ณ ๊ธ‰ ์‚ฌ์šฉ ์˜ˆ์ œ๋Š” ์ „์šฉ ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

example 1
example 2
example 3
example 4
import { getTableColumns, sql } from 'drizzle-orm';

await db.select({
    ...getTableColumns(posts),
    titleLength: sql<number>`length(${posts.title})`,
  }).from(posts);

---

Filters

.where() ๋ฉ”์„œ๋“œ์—์„œ ํ•„ํ„ฐ ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ํ•„ํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

import { eq, lt, gte, ne } from 'drizzle-orm';

await db.select().from(users).where(eq(users.id, 42));
await db.select().from(users).where(lt(users.id, 42));
await db.select().from(users).where(gte(users.id, 42));
await db.select().from(users).where(ne(users.id, 42));
...
select "id", "name", "age" from "users" where "id" = 42;
select "id", "name", "age" from "users" where "id" < 42;
select "id", "name", "age" from "users" where "id" >= 42;
select "id", "name", "age" from "users" where "id" <> 42;

๋ชจ๋“  ํ•„ํ„ฐ ์—ฐ์‚ฐ์ž๋Š” sql ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์—ฌ ์ž„์˜์˜ SQL ํ•„ํ„ฐ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ ์ž์ฒด ์—ฐ์‚ฐ์ž๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜๊ฐ์„ ์–ป์œผ๋ ค๋ฉด Drizzle์ด ์ œ๊ณตํ•˜๋Š” ์—ฐ์‚ฐ์ž๊ฐ€ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { sql } from 'drizzle-orm';

function equals42(col: Column) {
  return sql`${col} = 42`;
}

await db.select().from(users).where(sql`${users.id} < 42`);
await db.select().from(users).where(sql`${users.id} = 42`);
await db.select().from(users).where(equals42(users.id));
await db.select().from(users).where(sql`${users.id} >= 42`);
await db.select().from(users).where(sql`${users.id} <> 42`);
await db.select().from(users).where(sql`lower(${users.name}) = 'aaron'`);
select "id", "name", "age" from "users" where 'id' < 42;
select "id", "name", "age" from "users" where 'id' = 42;
select "id", "name", "age" from "users" where 'id' = 42;
select "id", "name", "age" from "users" where 'id' >= 42;
select "id", "name", "age" from "users" where 'id' <> 42;
select "id", "name", "age" from "users" where lower("name") = 'aaron';

ํ•„ํ„ฐ ์—ฐ์‚ฐ์ž์™€ sql ํ•จ์ˆ˜์— ์ œ๊ณต๋˜๋Š” ๋ชจ๋“  ๊ฐ’์€ ์ž๋™์œผ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ํ™”๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ ์ฟผ๋ฆฌ๋Š”:

await db.select().from(users).where(eq(users.id, 42));

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค:

select "id", "name", "age" from "users" where "id" = $1; -- params: [42]

not ์—ฐ์‚ฐ์ž๋กœ ์กฐ๊ฑด ๋ฐ˜์ „ํ•˜๊ธฐ:

import { eq, not, sql } from 'drizzle-orm';

await db.select().from(users).where(not(eq(users.id, 42)));
await db.select().from(users).where(sql`not ${users.id} = 42`);
select "id", "name", "age" from "users" where not ("id" = 42);
select "id", "name", "age" from "users" where not ("id" = 42);

์Šคํ‚ค๋งˆ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณ€๊ฒฝํ•˜๊ณ  ํ…Œ์ด๋ธ”๊ณผ ์ปฌ๋Ÿผ์˜ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ…œํ”Œ๋ฆฟ ๋ณด๊ฐ„ ๋•๋ถ„์— ์ฟผ๋ฆฌ์— ์ž๋™์œผ๋กœ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” raw SQL์„ ์ž‘์„ฑํ•  ๋•Œ ์ปฌ๋Ÿผ์ด๋‚˜ ํ…Œ์ด๋ธ” ์ด๋ฆ„์„ ํ•˜๋“œ์ฝ”๋”ฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋Œ€์กฐ์ ์ž…๋‹ˆ๋‹ค.

Combining filters

and()์™€ or() ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„ํ„ฐ ์—ฐ์‚ฐ์ž๋ฅผ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

import { eq, and, sql } from 'drizzle-orm';

await db.select().from(users).where(
  and(
    eq(users.id, 42),
    eq(users.name, 'Dan')
  )
);
await db.select().from(users).where(sql`${users.id} = 42 and ${users.name} = 'Dan'`);
select "id", "name", "age" from "users" where "id" = 42 and "name" = 'Dan';
select "id", "name", "age" from "users" where "id" = 42 and "name" = 'Dan';
import { eq, or, sql } from 'drizzle-orm';

await db.select().from(users).where(
  or(
    eq(users.id, 42), 
    eq(users.name, 'Dan')
  )
);
await db.select().from(users).where(sql`${users.id} = 42 or ${users.name} = 'Dan'`);
select "id", "name", "age" from "users" where "id" = 42 or "name" = 'Dan';
select "id", "name", "age" from "users" where "id" = 42 or "name" = 'Dan';

Advanced filters

TypeScript์™€ ๊ฒฐํ•ฉํ•˜์—ฌ Drizzle API๋Š” ์ฟผ๋ฆฌ์—์„œ ํ•„ํ„ฐ๋ฅผ ๊ฒฐํ•ฉํ•˜๋Š” ๊ฐ•๋ ฅํ•˜๊ณ  ์œ ์—ฐํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์กฐ๊ฑด๋ถ€ ํ•„ํ„ฐ๋ง์— ๋Œ€ํ•œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ž…๋‹ˆ๋‹ค. ๋” ์ž์„ธํ•œ ๊ณ ๊ธ‰ ์‚ฌ์šฉ ์˜ˆ์ œ๋Š” ์ „์šฉ ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

example 1
example 2
const searchPosts = async (term?: string) => {
  await db
    .select()
    .from(posts)
    .where(term ? ilike(posts.title, term) : undefined);
};
await searchPosts();
await searchPosts('AI');

---

Limit & offset

.limit()๊ณผ .offset()์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ์— limit๊ณผ offset ์ ˆ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค - ์˜ˆ๋ฅผ ๋“ค์–ด ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด:

await db.select().from(users).limit(10);
await db.select().from(users).limit(10).offset(10);
select "id", "name", "age" from "users" limit 10;
select "id", "name", "age" from "users" limit 10 offset 10;

Order By

.orderBy()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ์— order by ์ ˆ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์ง€์ •๋œ ํ•„๋“œ๋กœ ๊ฒฐ๊ณผ๋ฅผ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค:

import { asc, desc } from 'drizzle-orm';

await db.select().from(users).orderBy(users.name);
await db.select().from(users).orderBy(desc(users.name));

// order by multiple fields
await db.select().from(users).orderBy(users.name, users.name2);
await db.select().from(users).orderBy(asc(users.name), desc(users.name2));
select "id", "name", "age" from "users" order by "name";
select "id", "name", "age" from "users" order by "name" desc;

select "id", "name", "age" from "users" order by "name", "name2";
select "id", "name", "age" from "users" order by "name" asc, "name2" desc;

Advanced pagination

TypeScript๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Drizzle API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  SQL ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐ ์ •๋ ฌ ๋ฐฉ์‹์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ณ ๊ธ‰ ํŽ˜์ด์ง€๋„ค์ด์…˜์— ๋Œ€ํ•œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ž…๋‹ˆ๋‹ค. ๋” ์ž์„ธํ•œ ๊ณ ๊ธ‰ ์‚ฌ์šฉ ์˜ˆ์ œ๋Š” limit offset ํŽ˜์ด์ง€๋„ค์ด์…˜๊ณผ ์ปค์„œ ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

example 1
example 2
example 3
example 4
await db
  .select()
  .from(users)
  .orderBy(asc(users.id)) // order by is mandatory
  .limit(4) // the number of rows to return
  .offset(4); // the number of rows to skip

---

WITH clause

insert, update, delete์™€ ํ•จ๊ป˜ WITH ๋ฌธ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•˜์„ธ์š”

with ์ ˆ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ CTE(๊ณตํ†ต ํ…Œ์ด๋ธ” ํ‘œํ˜„์‹)๋ผ๊ณ  ํ•˜๋Š” ๋” ์ž‘์€ ํ•˜์œ„ ์ฟผ๋ฆฌ๋กœ ๋ถ„ํ• ํ•˜์—ฌ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42)));

const result = await db.with(sq).select().from(sq);
with sq as (select "id", "name", "age" from "users" where "id" = 42)
select "id", "name", "age" from sq;

with ๋‚ด๋ถ€์— insert, update, delete ๋ฌธ์„ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค

const sq = db.$with('sq').as(
    db.insert(users).values({ name: 'John' }).returning(),
);

const result = await db.with(sq).select().from(sq);
with "sq" as (insert into "users" ("id", "name") values (default, 'John') returning "id", "name") 
select "id", "name" from "sq"
const sq = db.$with('sq').as(
    db.update(users).set({ age: 25 }).where(eq(users.name, 'John')).returning(),
);
const result = await db.with(sq).select().from(sq);
with "sq" as (update "users" set "age" = 25 where "users"."name" = 'John' returning "id", "name", "age") 
select "id", "name", "age" from "sq"
const sq = db.$with('sq').as(
  db.delete(users).where(eq(users.name, 'John')).returning(),
);

const result = await db.with(sq).select().from(sq);
with "sq" as (delete from "users" where "users"."name" = $1 returning "id", "name", "age") 
select "id", "name", "age" from "sq"

CTE์—์„œ ์ž„์˜์˜ SQL ๊ฐ’์„ ํ•„๋“œ๋กœ ์„ ํƒํ•˜๊ณ  ๋‹ค๋ฅธ CTE๋‚˜ ๋ฉ”์ธ ์ฟผ๋ฆฌ์—์„œ ์ฐธ์กฐํ•˜๋ ค๋ฉด ๋ณ„์นญ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:


const sq = db.$with('sq').as(db.select({
  name: sql<string>`upper(${users.name})`.as('name'),
})
.from(users));

const result = await db.with(sq).select({ name: sq.name }).from(sq);

๋ณ„์นญ์„ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉด ํ•„๋“œ ํƒ€์ž…์ด DrizzleTypeError๊ฐ€ ๋˜๋ฉฐ ๋‹ค๋ฅธ ์ฟผ๋ฆฌ์—์„œ ์ฐธ์กฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํƒ€์ž… ์—๋Ÿฌ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ์—ฌ์ „ํžˆ ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋ฉด ๋ณ„์นญ ์—†์ด๋Š” ํ•ด๋‹น ํ•„๋“œ๋ฅผ ์ฐธ์กฐํ•  ๋ฐฉ๋ฒ•์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

Select from subquery

SQL๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์„œ๋ธŒ์ฟผ๋ฆฌ API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ๋‹ค๋ฅธ ์ฟผ๋ฆฌ์— ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

const sq = db.select().from(users).where(eq(users.id, 42)).as('sq');
const result = await db.select().from(sq);
select "id", "name", "age" from (select "id", "name", "age" from "users" where "id" = 42) "sq";

์„œ๋ธŒ์ฟผ๋ฆฌ๋Š” ํ…Œ์ด๋ธ”์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ณณ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์กฐ์ธ์—์„œ:

const sq = db.select().from(users).where(eq(users.id, 42)).as('sq');
const result = await db.select().from(users).leftJoin(sq, eq(users.id, sq.id));
select "users"."id", "users"."name", "users"."age", "sq"."id", "sq"."name", "sq"."age" from "users"
  left join (select "id", "name", "age" from "users" where "id" = 42) "sq"
    on "users"."id" = "sq"."id";

---

Aggregations

Drizzle์„ ์‚ฌ์šฉํ•˜๋ฉด raw SQL์—์„œ ํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•˜๊ฒŒ .groupBy()์™€ .having()์œผ๋กœ ๊ทธ๋ฃนํ™” ๋ฐ ํ•„ํ„ฐ๋งํ•˜์—ฌ sum, count, avg ๋“ฑ๊ณผ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ ์ง‘๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

import { gt } from 'drizzle-orm';

await db.select({
  age: users.age,
  count: sql<number>`cast(count(${users.id}) as int)`,
})
  .from(users)
  .groupBy(users.age);

await db.select({
  age: users.age,
  count: sql<number>`cast(count(${users.id}) as int)`,
})
  .from(users)
  .groupBy(users.age)
  .having(({ count }) => gt(count, 1));
select "age", cast(count("id") as int)
  from "users"
  group by "age";

select "age", cast(count("id") as int)
  from "users"
  group by "age"
  having cast(count("id") as int) > 1;

cast(... as int)๋Š” count()๊ฐ€ PostgreSQL์—์„œ bigint๋ฅผ, MySQL์—์„œ decimal์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์ด๋“ค์€ ์ˆซ์ž ๋Œ€์‹  ๋ฌธ์ž์—ด ๊ฐ’์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋˜๋Š” ๋Ÿฐํƒ€์ž„์— ๊ฐ’์„ ์ˆซ์ž๋กœ ์บ์ŠคํŒ…ํ•˜๊ธฐ ์œ„ํ•ด .mapWith(Number)๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

count ์ง‘๊ณ„๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ - $count API ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค

Aggregations helpers

Drizzle์€ ๋ž˜ํ•‘๋œ sql ํ•จ์ˆ˜ ์„ธํŠธ๋ฅผ ์ œ๊ณตํ•˜๋ฏ€๋กœ ์•ฑ์˜ ์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ์— ๋Œ€ํ•ด sql ํ…œํ”Œ๋ฆฟ์„ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

์ง‘๊ณ„ ํ•จ์ˆ˜๋Š” SELECT ๋ฌธ์˜ GROUP BY ์ ˆ๊ณผ ํ•จ๊ป˜ ์ž์ฃผ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ•œ ์ฟผ๋ฆฌ์—์„œ ์ง‘๊ณ„ ํ•จ์ˆ˜์™€ ๋‹ค๋ฅธ ์ปฌ๋Ÿผ์„ ์„ ํƒํ•˜๋Š” ๊ฒฝ์šฐ .groupBy ์ ˆ์„ ๋ฐ˜๋“œ์‹œ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

count

expression์˜ ๊ฐ’ ๊ฐœ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

import { count } from 'drizzle-orm'

await db.select({ value: count() }).from(users);
await db.select({ value: count(users.id) }).from(users);
select count("*") from "users";
select count("id") from "users";
// It's equivalent to writing
await db.select({ 
  value: sql`count('*'))`.mapWith(Number) 
}).from(users);

await db.select({ 
  value: sql`count(${users.id})`.mapWith(Number) 
}).from(users);

countDistinct

expression์˜ ์ค‘๋ณต๋˜์ง€ ์•Š์€ ๊ฐ’์˜ ๊ฐœ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

import { countDistinct } from 'drizzle-orm'

await db.select({ value: countDistinct(users.id) }).from(users);
select count(distinct "id") from "users";
// It's equivalent to writing
await db.select({ 
  value: sql`count(${users.id})`.mapWith(Number) 
}).from(users);

avg

expression์˜ ๋ชจ๋“  null์ด ์•„๋‹Œ ๊ฐ’์˜ ํ‰๊ท (์‚ฐ์ˆ  ํ‰๊ท )์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

import { avg } from 'drizzle-orm'

await db.select({ value: avg(users.id) }).from(users);
select avg("id") from "users";
// It's equivalent to writing
await db.select({ 
  value: sql`avg(${users.id})`.mapWith(String) 
}).from(users);

avgDistinct

expression์˜ ๋ชจ๋“  null์ด ์•„๋‹Œ ๊ฐ’์˜ ํ‰๊ท (์‚ฐ์ˆ  ํ‰๊ท )์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

import { avgDistinct } from 'drizzle-orm'

await db.select({ value: avgDistinct(users.id) }).from(users);
select avg(distinct "id") from "users";
// It's equivalent to writing
await db.select({ 
  value: sql`avg(distinct ${users.id})`.mapWith(String) 
}).from(users);

sum

expression์˜ ๋ชจ๋“  null์ด ์•„๋‹Œ ๊ฐ’์˜ ํ•ฉ๊ณ„๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

import { sum } from 'drizzle-orm'

await db.select({ value: sum(users.id) }).from(users);
select sum("id") from "users";
// It's equivalent to writing
await db.select({ 
  value: sql`sum(${users.id})`.mapWith(String) 
}).from(users);

sumDistinct

expression์˜ ๋ชจ๋“  null์ด ์•„๋‹ˆ๊ณ  ์ค‘๋ณต๋˜์ง€ ์•Š์€ ๊ฐ’์˜ ํ•ฉ๊ณ„๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

import { sumDistinct } from 'drizzle-orm'

await db.select({ value: sumDistinct(users.id) }).from(users);
select sum(distinct "id") from "users";
// It's equivalent to writing
await db.select({ 
  value: sql`sum(distinct ${users.id})`.mapWith(String) 
}).from(users);

max

expression์˜ ์ตœ๋Œ“๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

import { max } from 'drizzle-orm'

await db.select({ value: max(users.id) }).from(users);
select max("id") from "users";
// It's equivalent to writing
await db.select({ 
  value: sql`max(${expression})`.mapWith(users.id) 
}).from(users);

min

expression์˜ ์ตœ์†Ÿ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

import { min } from 'drizzle-orm'

await db.select({ value: min(users.id) }).from(users);
select min("id") from "users";
// It's equivalent to writing
await db.select({ 
  value: sql`min(${users.id})`.mapWith(users.id) 
}).from(users);

๋” ๊ณ ๊ธ‰ ์˜ˆ์ œ:

const orders = sqliteTable('order', {
  id: integer('id').primaryKey(),
  orderDate: integer('order_date', { mode: 'timestamp' }).notNull(),
  requiredDate: integer('required_date', { mode: 'timestamp' }).notNull(),
  shippedDate: integer('shipped_date', { mode: 'timestamp' }),
  shipVia: integer('ship_via').notNull(),
  freight: numeric('freight').notNull(),
  shipName: text('ship_name').notNull(),
  shipCity: text('ship_city').notNull(),
  shipRegion: text('ship_region'),
  shipPostalCode: text('ship_postal_code'),
  shipCountry: text('ship_country').notNull(),
  customerId: text('customer_id').notNull(),
  employeeId: integer('employee_id').notNull(),
});

const details = sqliteTable('order_detail', {
  unitPrice: numeric('unit_price').notNull(),
  quantity: integer('quantity').notNull(),
  discount: numeric('discount').notNull(),
  orderId: integer('order_id').notNull(),
  productId: integer('product_id').notNull(),
});


db
  .select({
    id: orders.id,
    shippedDate: orders.shippedDate,
    shipName: orders.shipName,
    shipCity: orders.shipCity,
    shipCountry: orders.shipCountry,
    productsCount: sql<number>`cast(count(${details.productId}) as int)`,
    quantitySum: sql<number>`sum(${details.quantity})`,
    totalPrice: sql<number>`sum(${details.quantity} * ${details.unitPrice})`,
  })
  .from(orders)
  .leftJoin(details, eq(orders.id, details.orderId))
  .groupBy(orders.id)
  .orderBy(asc(orders.id))
  .all();

$count

db.$count()๋Š” count(*)์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ž˜ํผ๋กœ, ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์„œ๋ธŒ์ฟผ๋ฆฌ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋งค์šฐ ์œ ์—ฐํ•œ ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub ํ† ๋ก ์„ ์ฐธ์กฐํ•˜์„ธ์š”.

const count = await db.$count(users);
//    ^? number

const count = await db.$count(users, eq(users.name, "Dan")); // ํ•„ํ„ฐ์™€ ํ•จ๊ป˜ ์ž‘๋™
select count(*) from "users";
select count(*) from "users" where "name" = 'Dan';

์„œ๋ธŒ์ฟผ๋ฆฌ์—์„œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:

const users = await db.select({
  ...users,
  postsCount: db.$count(posts, eq(posts.authorId, users.id)),
}).from(users);

๊ด€๊ณ„ํ˜• ์ฟผ๋ฆฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์ œ

const users = await db.query.users.findMany({
  extras: {
    postsCount: db.$count(posts, eq(posts.authorId, users.id)),
  },
});

---

Iterator

MySQL
PostgreSQL[WIP]
SQLite[WIP]
SingleStore[WIP]

์ฟผ๋ฆฌ์—์„œ ๋งค์šฐ ๋งŽ์€ ์–‘์˜ ํ–‰์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๊ณ  ๋ชจ๋“  ํ–‰์„ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œํ•˜๊ณ  ์‹ถ์ง€ ์•Š์€ ๊ฒฝ์šฐ .iterator()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ๋น„๋™๊ธฐ ์ดํ„ฐ๋ ˆ์ดํ„ฐ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

const iterator = await db.select().from(users).iterator();

for await (const row of iterator) {
  console.log(row);
}

prepared statement์™€๋„ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค:

const query = await db.select().from(users).prepare();
const iterator = await query.iterator();

for await (const row of iterator) {
  console.log(row);
}

---

Use Index

USE INDEX ํžŒํŠธ๋Š” ์ฟผ๋ฆฌ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ๊ณ ๋ คํ•  ์ธ๋ฑ์Šค๋ฅผ ์˜ตํ‹ฐ๋งˆ์ด์ €์— ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. ์˜ตํ‹ฐ๋งˆ์ด์ €๋Š” ์ด๋Ÿฌํ•œ ์ธ๋ฑ์Šค๋ฅผ ๊ฐ•์ œ๋กœ ์‚ฌ์šฉํ•˜์ง€๋Š” ์•Š์ง€๋งŒ ์ ํ•ฉํ•œ ๊ฒฝ์šฐ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.

MySQL
PostgreSQL
SQLite
SingleStore
export const users = mysqlTable('users', {
	id: int('id').primaryKey(),
	name: varchar('name', { length: 100 }).notNull(),
}, () => [usersTableNameIndex]);

const usersTableNameIndex = index('users_name_index').on(users.name);

await db.select()
  .from(users, { useIndex: usersTableNameIndex })
  .where(eq(users.name, 'David'));

์›ํ•˜๋Š” ์กฐ์ธ์—์„œ๋„ ์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

await db.select()
  .from(users)
  .leftJoin(posts, eq(posts.userId, users.id), { useIndex: usersTableNameIndex })
  .where(eq(users.name, 'David'));

Ignore Index

IGNORE INDEX ํžŒํŠธ๋Š” ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ํŠน์ • ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ์˜ตํ‹ฐ๋งˆ์ด์ €์— ์•Œ๋ฆฝ๋‹ˆ๋‹ค. MySQL์€ ๋‹ค๋ฅธ ๋ชจ๋“  ์ธ๋ฑ์Šค๋ฅผ ๊ณ ๋ คํ•˜๊ฑฐ๋‚˜(์žˆ๋Š” ๊ฒฝ์šฐ) ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ „์ฒด ํ…Œ์ด๋ธ” ์Šค์บ”์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

MySQL
PostgreSQL
SQLite
SingleStore
export const users = mysqlTable('users', {
	id: int('id').primaryKey(),
	name: varchar('name', { length: 100 }).notNull(),
}, () => [usersTableNameIndex]);

const usersTableNameIndex = index('users_name_index').on(users.name);

await db.select()
  .from(users, { ignoreIndex: usersTableNameIndex })
  .where(eq(users.name, 'David'));

์›ํ•˜๋Š” ์กฐ์ธ์—์„œ๋„ ์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

await db.select()
  .from(users)
  .leftJoin(posts, eq(posts.userId, users.id), { useIndex: usersTableNameIndex })
  .where(eq(users.name, 'David'));

Force Index

FORCE INDEX ํžŒํŠธ๋Š” ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ์ง€์ •๋œ ์ธ๋ฑ์Šค๋ฅผ ๊ฐ•์ œ๋กœ ์‚ฌ์šฉํ•˜๋„๋ก ์˜ตํ‹ฐ๋งˆ์ด์ €์— ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค. ์ง€์ •๋œ ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ MySQL์€ ๋‹ค๋ฅธ ์ธ๋ฑ์Šค๋กœ ํด๋ฐฑํ•˜์ง€ ์•Š์œผ๋ฉฐ ๋Œ€์‹  ์ „์ฒด ํ…Œ์ด๋ธ” ์Šค์บ”์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

MySQL
PostgreSQL
SQLite
SingleStore
export const users = mysqlTable('users', {
	id: int('id').primaryKey(),
	name: varchar('name', { length: 100 }).notNull(),
}, () => [usersTableNameIndex]);

const usersTableNameIndex = index('users_name_index').on(users.name);

await db.select()
  .from(users, { forceIndex: usersTableNameIndex })
  .where(eq(users.name, 'David'));

์›ํ•˜๋Š” ์กฐ์ธ์—์„œ๋„ ์ด ์˜ต์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

await db.select()
  .from(users)
  .leftJoin(posts, eq(posts.userId, users.id), { useIndex: usersTableNameIndex })
  .where(eq(users.name, 'David'));