> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# orderBy

> 결과 정렬하기

`orderBy`는 쿼리 결과를 특정 컬럼 기준으로 정렬하는 메서드입니다. 오름차순(ASC) 또는 내림차순(DESC)으로 정렬할 수 있으며, 여러 컬럼으로 정렬할 수 있습니다.

## 기본 사용법

### 오름차순 (ASC)

```typescript theme={null}
const users = await puri
  .table("users")
  .select({ id: "users.id", name: "users.name" })
  .orderBy("users.name", "asc");

// ORDER BY users.name ASC
```

### 내림차순 (DESC)

```typescript theme={null}
const posts = await puri
  .table("posts")
  .select({ id: "posts.id", title: "posts.title", created_at: "posts.created_at" })
  .orderBy("posts.created_at", "desc");

// ORDER BY posts.created_at DESC
```

## 여러 컬럼 정렬

`orderBy`를 체이닝하여 여러 컬럼으로 정렬할 수 있습니다.

```typescript theme={null}
const users = await puri
  .table("users")
  .select({ id: "users.id", role: "users.role", name: "users.name" })
  .orderBy("users.role", "asc") // 첫 번째: role 오름차순
  .orderBy("users.name", "asc"); // 두 번째: name 오름차순

// ORDER BY users.role ASC, users.name ASC
```

**정렬 우선순위:**

1. 첫 번째 `orderBy` - 주 정렬
2. 두 번째 `orderBy` - 부 정렬 (주 정렬이 같을 때)
3. 세 번째 `orderBy` - 삼차 정렬 (부 정렬도 같을 때)

## NULL 정렬 순서 (nulls)

세 번째 인자 `nulls`로 NULL 값의 정렬 위치를 명시할 수 있습니다.

```typescript theme={null}
// NULL을 마지막으로
const users = await puri
  .table("users")
  .select({ id: "users.id", name: "users.name", last_login: "users.last_login" })
  .orderBy("users.last_login", "desc", "last");

// ORDER BY users.last_login DESC NULLS LAST
```

```typescript theme={null}
// NULL을 먼저
const posts = await puri
  .table("posts")
  .select({ id: "posts.id", title: "posts.title", deleted_at: "posts.deleted_at" })
  .orderBy("posts.deleted_at", "asc", "first");

// ORDER BY posts.deleted_at ASC NULLS FIRST
```

**`nulls` 옵션:**

| 값         | 설명                                                              |
| --------- | --------------------------------------------------------------- |
| `"first"` | NULL 값을 결과의 맨 앞에 배치                                             |
| `"last"`  | NULL 값을 결과의 맨 뒤에 배치                                             |
| 생략        | 데이터베이스 기본 동작 (PostgreSQL: ASC → NULLS LAST, DESC → NULLS FIRST) |

## 배열 형태의 orderBy

여러 정렬 조건을 배열로 한 번에 전달할 수 있습니다.

```typescript theme={null}
const products = await puri
  .table("products")
  .select({
    id: "products.id",
    name: "products.name",
    price: "products.price",
    rating: "products.rating",
  })
  .orderBy([
    { column: "products.rating", order: "desc", nulls: "last" },
    { column: "products.price", order: "asc" },
  ]);

// ORDER BY products.rating DESC NULLS LAST, products.price ASC
```

배열의 각 항목은 다음 형태 중 하나입니다:

* **문자열**: 컬럼명만 전달 (기본 ASC)
* **SqlExpression**: SQL 표현식
* **객체**: `{ column, order?, nulls? }` 형태로 상세 지정

```typescript theme={null}
// 다양한 형태 혼합
.orderBy([
  "products.featured",                                         // 문자열: ASC
  { column: "products.rating", order: "desc" },                // 객체: DESC
  { column: "products.price", order: "asc", nulls: "last" },  // 객체 + nulls
])
```

## 실전 예시

<Tabs>
  <Tab title="최신순" icon="clock">
    ```typescript theme={null}
    // 최신 게시물 먼저
    const posts = await puri.table("posts")
      .select({
        id: "posts.id",
        title: "posts.title",
        created_at: "posts.created_at"
      })
      .orderBy("posts.created_at", "desc")
      .limit(20);

    // 가장 최근에 업데이트된 것 먼저
    const users = await puri.table("users")
      .select({
        id: "users.id",
        name: "users.name",
        updated_at: "users.updated_at"
      })
      .orderBy("users.updated_at", "desc");
    ```
  </Tab>

  <Tab title="알파벳순" icon="arrow-down-a-z">
    ```typescript theme={null}
    // 이름 가나다순
    const users = await puri.table("users")
      .select({
        id: "users.id",
        name: "users.name"
      })
      .orderBy("users.name", "asc");

    // 제목 알파벳 역순
    const products = await puri.table("products")
      .select({
        id: "products.id",
        name: "products.name"
      })
      .orderBy("products.name", "desc");
    ```
  </Tab>

  <Tab title="복합 정렬" icon="layer-group">
    ```typescript theme={null}
    // 우선순위: 상태 → 생성일
    const orders = await puri.table("orders")
      .select({
        id: "orders.id",
        status: "orders.status",
        created_at: "orders.created_at"
      })
      .orderBy("orders.status", "asc")        // pending → processing → completed
      .orderBy("orders.created_at", "desc");  // 최신순

    // 우선순위: featured → 평점 → 판매량
    const products = await puri.table("products")
      .select({
        id: "products.id",
        name: "products.name",
        featured: "products.featured",
        rating: "products.rating",
        sales: "products.sales"
      })
      .orderBy("products.featured", "desc")  // 추천 상품 먼저
      .orderBy("products.rating", "desc")    // 높은 평점
      .orderBy("products.sales", "desc");    // 많이 팔린 것
    ```
  </Tab>

  <Tab title="숫자 정렬" icon="arrow-down-1-9">
    ```typescript theme={null}
    // 가격 낮은 순
    const products = await puri.table("products")
      .select({
        id: "products.id",
        name: "products.name",
        price: "products.price"
      })
      .where("products.published", true)
      .orderBy("products.price", "asc");

    // 조회수 높은 순
    const posts = await puri.table("posts")
      .select({
        id: "posts.id",
        title: "posts.title",
        views: "posts.views"
      })
      .orderBy("posts.views", "desc")
      .limit(10);
    ```
  </Tab>

  <Tab title="NULL 처리" icon="ban">
    ```typescript theme={null}
    // nulls 옵션으로 NULL 위치를 명시적으로 지정
    const users = await puri.table("users")
      .select({
        id: "users.id",
        name: "users.name",
        last_login: "users.last_login"
      })
      .orderBy("users.last_login", "desc", "last");  // NULL을 마지막으로

    // NULL을 먼저
    const posts = await puri.table("posts")
      .select({
        id: "posts.id",
        title: "posts.title",
        deleted_at: "posts.deleted_at"
      })
      .orderBy("posts.deleted_at", "asc", "first");  // NULL을 먼저
    ```
  </Tab>
</Tabs>

## 조인된 테이블 정렬

조인한 테이블의 컬럼으로도 정렬할 수 있습니다.

```typescript theme={null}
const posts = await puri
  .table("posts")
  .join("users", "posts.user_id", "users.id")
  .select({
    post_id: "posts.id",
    title: "posts.title",
    author_name: "users.name",
  })
  .orderBy("users.name", "asc") // 작성자 이름순
  .orderBy("posts.created_at", "desc"); // 같은 작성자는 최신순

// ORDER BY users.name ASC, posts.created_at DESC
```

## SELECT 별칭으로 정렬

SELECT에서 지정한 별칭으로도 정렬할 수 있습니다.

```typescript theme={null}
const posts = await puri
  .table("posts")
  .select({
    id: "posts.id",
    title: "posts.title",
    view_count: "posts.views", // 별칭 지정
  })
  .orderBy("view_count", "desc"); // 별칭 사용

// SELECT posts.views as view_count
// ORDER BY view_count DESC
```

## 집계 함수 정렬

GROUP BY와 함께 집계 함수 결과로 정렬할 수 있습니다.

```typescript theme={null}
const userStats = await puri
  .table("posts")
  .select({
    user_id: "posts.user_id",
    post_count: Puri.count(),
  })
  .groupBy("posts.user_id")
  .orderBy("post_count", "desc") // 게시물 많은 순
  .limit(10);

// SELECT user_id, COUNT(*) as post_count
// FROM posts
// GROUP BY user_id
// ORDER BY post_count DESC
// LIMIT 10
```

## SQL 표현식으로 정렬

`orderBy`는 컬럼명 대신 `SqlExpression<"number">` 또는 `SqlExpression<"string">`을 받을 수 있습니다. 전문 검색 순위, 유사도 점수 등 계산된 값을 기준으로 정렬할 때 사용합니다.

### 검색 순위 기준 정렬

```typescript theme={null}
// ts_rank로 전문 검색 결과를 관련도 순으로 정렬
const posts = await puri
  .table("posts")
  .select({
    id: "posts.id",
    title: "posts.title",
    rank: Puri.tsRank("posts.content_tsv", "typescript"),
  })
  .orderBy(Puri.tsRank("posts.content_tsv", "typescript"), "desc");

// ORDER BY ts_rank(posts.content_tsv, plainto_tsquery('simple', ?)) DESC
```

### 유사도 점수 기준 정렬

```typescript theme={null}
// pg_trgm similarity로 유사도 높은 순 정렬
const users = await puri
  .table("users")
  .select({
    id: "users.id",
    name: "users.name",
    score: Puri.similarity("users.name", "홍길동"),
  })
  .orderBy(Puri.similarity("users.name", "홍길동"), "desc");

// ORDER BY similarity(users.name, ?) DESC
```

### rawNumber로 커스텀 정렬

```typescript theme={null}
// 커스텀 SQL 표현식으로 정렬
const products = await puri
  .table("products")
  .select({
    id: "products.id",
    name: "products.name",
    weighted: Puri.rawNumber("(products.views * 0.3 + products.likes * 0.7)"),
  })
  .orderBy(Puri.rawNumber("(products.views * 0.3 + products.likes * 0.7)"), "desc");

// ORDER BY (products.views * 0.3 + products.likes * 0.7) DESC
```

<Info>
  SQL 표현식을 사용할 때는 내부적으로 `orderByRaw`가 실행되며, 파라미터 바인딩이 자동으로
  적용됩니다.
</Info>

## 정렬 방향

### ASC (오름차순)

* 숫자: 작은 값 → 큰 값
* 문자: A → Z, 가 → 하
* 날짜: 과거 → 미래
* NULL: 마지막

```typescript theme={null}
.orderBy("users.age", "asc")
// 18, 25, 30, 35, 40, NULL
```

### DESC (내림차순)

* 숫자: 큰 값 → 작은 값
* 문자: Z → A, 하 → 가
* 날짜: 미래 → 과거
* NULL: 마지막

```typescript theme={null}
.orderBy("users.age", "desc")
// 40, 35, 30, 25, 18, NULL
```

<Info>**기본값은 ASC**입니다. 방향을 지정하지 않으면 오름차순으로 정렬됩니다.</Info>

## 성능 최적화

### 1. 인덱스 활용

```typescript theme={null}
// ✅ 좋음: 인덱스 컬럼으로 정렬
.orderBy("users.created_at", "desc")  // created_at에 인덱스 필요

// ❌ 나쁨: 인덱스 없는 컬럼으로 정렬 (느림)
.orderBy("users.bio", "asc")
```

**권장 인덱스:**

```sql theme={null}
CREATE INDEX idx_users_created_at ON users(created_at DESC);
CREATE INDEX idx_posts_published_created ON posts(published, created_at DESC);
```

### 2. LIMIT과 함께 사용

```typescript theme={null}
// ✅ 좋음: 상위 N개만 정렬
.orderBy("posts.views", "desc")
.limit(10)  // 전체를 정렬하지 않고 상위 10개만

// ⚠️ 주의: LIMIT 없으면 모든 행 정렬 (느림)
.orderBy("posts.views", "desc")
```

### 3. 복합 인덱스

여러 컬럼으로 정렬할 때는 복합 인덱스가 필요합니다.

```typescript theme={null}
// 이 쿼리에는
.orderBy("products.category", "asc")
.orderBy("products.price", "asc")

// 이런 인덱스가 필요
CREATE INDEX idx_products_category_price ON products(category, price);
```

## 주의사항

### 1. 정렬 순서

```typescript theme={null}
// orderBy 순서가 중요함

// ✅ 올바름: 상태 → 날짜
.orderBy("orders.status", "asc")
.orderBy("orders.created_at", "desc")

// ❌ 의도와 다름: 날짜 → 상태
.orderBy("orders.created_at", "desc")
.orderBy("orders.status", "asc")
```

### 2. NULL 값

```typescript theme={null}
// PostgreSQL과 MySQL의 NULL 정렬이 다를 수 있음

// PostgreSQL: NULL LAST (기본)
.orderBy("users.last_login", "desc")

// nulls 옵션으로 명시적으로 지정 가능
.orderBy("users.last_login", "desc", "first")  // NULLS FIRST
.orderBy("users.last_login", "desc", "last")   // NULLS LAST
```

### 3. 대소문자 구분

```typescript theme={null}
// ⚠️ 주의: 대소문자 구분 정렬
.orderBy("users.name", "asc")
// "Alice", "Bob", "alice", "bob"

// 대소문자 무시하려면
.orderBy(puri.raw("LOWER(users.name)"), "asc")
// "Alice", "alice", "Bob", "bob"
```

### 4. 성능 영향

```typescript theme={null}
// ❌ 나쁨: 대용량 데이터 전체 정렬
const allUsers = await puri.table("users").orderBy("users.created_at", "desc"); // 수백만 행 정렬

// ✅ 좋음: LIMIT으로 제한
const recentUsers = await puri.table("users").orderBy("users.created_at", "desc").limit(100); // 상위 100개만
```

## 타입 안정성

`orderBy`는 타입 안전하게 컬럼을 검증합니다.

```typescript theme={null}
// ✅ 올바른 컬럼
await puri.table("users").orderBy("users.created_at", "desc");

// ❌ 타입 에러: 존재하지 않는 컬럼
await puri.table("users").orderBy("users.unknown_field", "desc");

// ❌ 타입 에러: 잘못된 방향
await puri.table("users").orderBy("users.name", "invalid"); // "asc" 또는 "desc"만 가능
```

## 다음 단계

<CardGroup cols={2}>
  <Card title="limit" icon="hashtag" href="/ko/api-reference/puri-methods/limit">
    결과 개수 제한하기
  </Card>

  <Card title="where" icon="filter" href="/ko/api-reference/puri-methods/where">
    조건 필터링하기
  </Card>

  <Card title="select" icon="list-check" href="/ko/api-reference/puri-methods/select">
    조회할 필드 선택하기
  </Card>

  <Card title="join" icon="link" href="/ko/api-reference/puri-methods/join">
    테이블 조인하기
  </Card>
</CardGroup>
