메인 μ½˜ν…μΈ λ‘œ κ±΄λ„ˆλ›°κΈ°
limitκ³Ό offset은 쿼리 결과의 개수λ₯Ό μ œν•œν•˜κ³  νŽ˜μ΄μ§€λ„€μ΄μ…˜μ„ κ΅¬ν˜„ν•˜λŠ” λ©”μ„œλ“œμž…λ‹ˆλ‹€.

limit

μ‘°νšŒν•  λ ˆμ½”λ“œ 수λ₯Ό μ œν•œν•©λ‹ˆλ‹€.

κΈ°λ³Έ μ‚¬μš©λ²•

// μƒμœ„ 10개만
const users = await puri
  .table("users")
  .select({ id: "users.id", name: "users.name" })
  .orderBy("users.created_at", "desc")
  .limit(10);

// LIMIT 10

μ΅œμ‹  N개

// μ΅œμ‹  κ²Œμ‹œλ¬Ό 20개
const recentPosts = await puri
  .table("posts")
  .select({
    id: "posts.id",
    title: "posts.title",
    created_at: "posts.created_at",
  })
  .orderBy("posts.created_at", "desc")
  .limit(20);

offset

κ±΄λ„ˆλ›Έ λ ˆμ½”λ“œ 수λ₯Ό μ§€μ •ν•©λ‹ˆλ‹€.

κΈ°λ³Έ μ‚¬μš©λ²•

// 처음 10개λ₯Ό κ±΄λ„ˆλ›°κ³  κ·Έ λ‹€μŒ 10개
const users = await puri
  .table("users")
  .select({ id: "users.id", name: "users.name" })
  .orderBy("users.id", "asc")
  .limit(10)
  .offset(10); // 11~20번째 λ ˆμ½”λ“œ

// LIMIT 10 OFFSET 10

νŽ˜μ΄μ§€λ„€μ΄μ…˜

limitκ³Ό offset을 μ‘°ν•©ν•˜μ—¬ νŽ˜μ΄μ§€λ„€μ΄μ…˜μ„ κ΅¬ν˜„ν•©λ‹ˆλ‹€.

κΈ°λ³Έ νŽ˜μ΄μ§€λ„€μ΄μ…˜

function getPage(page: number, pageSize: number = 20) {
  return puri
    .table("posts")
    .select({
      id: "posts.id",
      title: "posts.title",
      created_at: "posts.created_at",
    })
    .orderBy("posts.created_at", "desc")
    .limit(pageSize)
    .offset((page - 1) * pageSize);
}

// 1νŽ˜μ΄μ§€ (1~20)
const page1 = await getPage(1); // offset: 0, limit: 20

// 2νŽ˜μ΄μ§€ (21~40)
const page2 = await getPage(2); // offset: 20, limit: 20

// 3νŽ˜μ΄μ§€ (41~60)
const page3 = await getPage(3); // offset: 40, limit: 20

총 κ°œμˆ˜μ™€ ν•¨κ»˜

async function getPageWithTotal(page: number, pageSize: number = 20) {
  // 총 개수
  const [{ total }] = await puri
    .table("posts")
    .select({ total: Puri.count() })
    .where("posts.published", true);

  // νŽ˜μ΄μ§€ 데이터
  const posts = await puri
    .table("posts")
    .select({
      id: "posts.id",
      title: "posts.title",
    })
    .where("posts.published", true)
    .orderBy("posts.created_at", "desc")
    .limit(pageSize)
    .offset((page - 1) * pageSize);

  return {
    data: posts,
    pagination: {
      page,
      pageSize,
      total,
      totalPages: Math.ceil(total / pageSize),
    },
  };
}

μ‹€μ „ μ˜ˆμ‹œ

async function getInfiniteScroll(cursor: number = 0, limit: number = 20) {
  const posts = await puri.table("posts")
    .select({
      id: "posts.id",
      title: "posts.title",
      created_at: "posts.created_at"
    })
    .where("posts.published", true)
    .where("posts.id", ">", cursor)  // μ»€μ„œ 이후
    .orderBy("posts.id", "asc")
    .limit(limit);

  return {
    data: posts,
    nextCursor: posts.length > 0 ? posts[posts.length - 1].id : null,
    hasMore: posts.length === limit
  };
}

// μ‚¬μš© μ˜ˆμ‹œ
const result1 = await getInfiniteScroll(0, 20);     // 첫 νŽ˜μ΄μ§€
const result2 = await getInfiniteScroll(result1.nextCursor, 20);  // λ‹€μŒ νŽ˜μ΄μ§€

limit(0)의 의미

limit(0)은 κ²°κ³Όλ₯Ό λ°˜ν™˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
// 0개 λ°˜ν™˜ (빈 λ°°μ—΄)
const users = await puri.table("users").limit(0);

// []
일뢀 λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ limit(0)은 ꡬ쑰 ν™•μΈμš©μœΌλ‘œ μ‚¬μš©λ˜μ§€λ§Œ, Puriμ—μ„œλŠ” 빈 배열을 λ°˜ν™˜ν•©λ‹ˆλ‹€.

μ„±λŠ₯ μ΅œμ ν™”

1. 인덱슀 ν™œμš©

// βœ… μ’‹μŒ: μ •λ ¬ μ»¬λŸΌμ— 인덱슀
await puri
  .table("posts")
  .orderBy("posts.created_at", "desc") // created_at 인덱슀 ν•„μš”
  .limit(20);

// ❌ λ‚˜μ¨: 인덱슀 μ—†λŠ” 컬럼 μ •λ ¬
await puri
  .table("posts")
  .orderBy("posts.content", "desc") // 인덱슀 μ—†μŒ
  .limit(20);

2. Offset 크기

// ⚠️ 주의: 큰 offset은 느림
await puri.table("posts").limit(20).offset(100000); // 10만 개λ₯Ό κ±΄λ„ˆλ›°κ³  20개 쑰회 (느림)

// βœ… μ’‹μŒ: μ»€μ„œ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜
await puri.table("posts").where("posts.id", ">", lastId).limit(20);

3. COUNT(*) μ΅œμ ν™”

// ❌ λ‚˜μ¨: 맀번 전체 카운트
const [{ total }] = await puri.table("posts").select({ total: Puri.count() });

// βœ… μ’‹μŒ: 캐싱
const cachedTotal = await cache.get("posts:total");
if (!cachedTotal) {
  const [{ total }] = await puri.table("posts").select({ total: Puri.count() });
  await cache.set("posts:total", total, 300); // 5λΆ„ μΊμ‹œ
}

μ»€μ„œ 기반 νŽ˜μ΄μ§€λ„€μ΄μ…˜

λŒ€μš©λŸ‰ λ°μ΄ν„°μ—λŠ” offset λŒ€μ‹  μ»€μ„œλ₯Ό μ‚¬μš©ν•˜μ„Έμš”.

Offset 기반 (느림)

// ❌ 큰 offset은 μ„±λŠ₯ μ €ν•˜
async function getPage(page: number, pageSize: number = 20) {
  return puri
    .table("posts")
    .select({ id: "posts.id", title: "posts.title" })
    .orderBy("posts.id", "desc")
    .limit(pageSize)
    .offset((page - 1) * pageSize); // νŽ˜μ΄μ§€κ°€ 컀질수둝 느렀짐
}

await getPage(1000, 20); // offset: 19980 (맀우 느림)

μ»€μ„œ 기반 (빠름)

// βœ… μ»€μ„œ μ‚¬μš© (항상 빠름)
async function getNextPage(cursor: number | null, pageSize: number = 20) {
  let query = puri
    .table("posts")
    .select({ id: "posts.id", title: "posts.title" })
    .orderBy("posts.id", "desc")
    .limit(pageSize);

  if (cursor !== null) {
    query = query.where("posts.id", "<", cursor);
  }

  const posts = await query;

  return {
    data: posts,
    nextCursor: posts.length > 0 ? posts[posts.length - 1].id : null,
    hasMore: posts.length === pageSize,
  };
}

// 항상 빠름 (인덱슀 μ‚¬μš©)
const page1 = await getNextPage(null, 20);
const page2 = await getNextPage(page1.nextCursor, 20);
const page100 = await getNextPage(page99.nextCursor, 20); // μ—¬μ „νžˆ 빠름

μ£Όμ˜μ‚¬ν•­

1. ORDER BY ν•„μˆ˜

// ❌ μœ„ν—˜: μ •λ ¬ 없이 limit (μˆœμ„œ λΆˆν™•μ‹€)
await puri.table("users").limit(10);

// βœ… μ˜¬λ°”λ¦„: μ •λ ¬ ν›„ limit
await puri.table("users").orderBy("users.id", "asc").limit(10);

2. offset은 0λΆ€ν„°

// βœ… μ˜¬λ°”λ¦„
.offset(0)   // 첫 번째 λ ˆμ½”λ“œλΆ€ν„°
.offset(10)  // 11번째 λ ˆμ½”λ“œλΆ€ν„°

// ❌ μ—λŸ¬: 음수 λΆˆκ°€
.offset(-1)  // Error: Invalid offset: must be >= 0

3. limit도 0 이상

// βœ… μ˜¬λ°”λ¦„
.limit(0)    // 빈 λ°°μ—΄
.limit(10)   // 10개

// ❌ μ—λŸ¬: 음수 λΆˆκ°€
.limit(-1)   // Error: Invalid limit: must be >= 0

4. μΌκ΄€λœ μ •λ ¬

// ⚠️ 주의: 정렬이 μΌκ΄€λ˜μ§€ μ•ŠμœΌλ©΄ 쀑볡/λˆ„λ½ κ°€λŠ₯
await puri
  .table("posts")
  .orderBy("posts.created_at", "desc") // 같은 μ‹œκ°„ μ—¬λŸ¬ 개 μžˆμ„ 수 있음
  .limit(20)
  .offset(20); // 쀑볡 λ˜λŠ” λˆ„λ½ κ°€λŠ₯

// βœ… μ’‹μŒ: 고유 컬럼 μΆ”κ°€
await puri
  .table("posts")
  .orderBy("posts.created_at", "desc")
  .orderBy("posts.id", "desc") // 동일 μ‹œκ°„μ€ ID둜 ꡬ뢄
  .limit(20)
  .offset(20);

νƒ€μž… μ•ˆμ •μ„±

// βœ… μ˜¬λ°”λ₯Έ μ‚¬μš©
await puri.table("users").limit(10).offset(0);

// ❌ νƒ€μž… μ—λŸ¬: 음수
await puri.table("users").limit(-10); // Error: Invalid limit

// ❌ νƒ€μž… μ—λŸ¬: 음수
await puri.table("users").offset(-5); // Error: Invalid offset

λ‹€μŒ 단계

order-by

κ²°κ³Ό μ •λ ¬ν•˜κΈ°

where

쑰건 ν•„ν„°λ§ν•˜κΈ°

advanced-methods

κ³ κΈ‰ 쿼리 λ©”μ„œλ“œ

Subset

μ„œλΈŒμ…‹μœΌλ‘œ 관계 데이터 λ‘œλ”©