๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
where๋Š” ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ํ•„ํ„ฐ๋งํ•˜๋Š” ์กฐ๊ฑด์„ ์ง€์ •ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. ๋‹ค์–‘ํ•œ ์—ฐ์‚ฐ์ž์™€ ์กฐ๊ฑด ์กฐํ•ฉ์„ ์ง€์›ํ•˜๋ฉฐ, ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ํ•„ํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

๋‹จ์ˆœ ๋“ฑํ˜ธ ์กฐ๊ฑด

const users = await puri.table("users")
  .where("users.status", "active")
  .select({ id: "users.id", name: "users.name" });

// WHERE users.status = 'active'

๋น„๊ต ์—ฐ์‚ฐ์ž

// ๋ณด๋‹ค ํฐ
await puri.table("users")
  .where("users.age", ">", 18);

// ๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์Œ
await puri.table("orders")
  .where("orders.amount", "<=", 1000);

// ๊ฐ™์ง€ ์•Š์Œ
await puri.table("posts")
  .where("posts.status", "!=", "deleted");
์ง€์› ์—ฐ์‚ฐ์ž:
  • = - ๊ฐ™์Œ (๊ธฐ๋ณธ๊ฐ’)
  • != - ๊ฐ™์ง€ ์•Š์Œ
  • > - ๋ณด๋‹ค ํผ
  • >= - ๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™์Œ
  • < - ๋ณด๋‹ค ์ž‘์Œ
  • <= - ๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์Œ
  • like - ํŒจํ„ด ๋งค์นญ
  • not like - ํŒจํ„ด ๋ถˆ์ผ์น˜

๊ฐ์ฒด ํ˜•ํƒœ ์กฐ๊ฑด

์—ฌ๋Ÿฌ AND ์กฐ๊ฑด์„ ๊ฐ์ฒด๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
const users = await puri.table("users")
  .where({
    "users.status": "active",
    "users.role": "admin",
    "users.verified": true
  })
  .select({ id: "users.id", name: "users.name" });

// WHERE users.status = 'active' 
//   AND users.role = 'admin' 
//   AND users.verified = true

NULL ์ฒ˜๋ฆฌ

IS NULL

// ๋ช…์‹œ์  null ์ฒดํฌ
await puri.table("users")
  .where("users.deleted_at", null);

// WHERE users.deleted_at IS NULL

// ์—ฐ์‚ฐ์ž ์‚ฌ์šฉ
await puri.table("users")
  .where("users.deleted_at", "=", null);

// WHERE users.deleted_at IS NULL

IS NOT NULL

await puri.table("users")
  .where("users.email", "!=", null);

// WHERE users.email IS NOT NULL

ํŒจํ„ด ๋งค์นญ (LIKE)

๋ถ€๋ถ„ ์ผ์น˜

// ํฌํ•จ
await puri.table("users")
  .where("users.email", "like", "%@gmail.com");

// ์‹œ์ž‘
await puri.table("users")
  .where("users.name", "like", "John%");

// ๋
await puri.table("users")
  .where("users.phone", "like", "%1234");

// ์–ด๋””๋“  ํฌํ•จ
await puri.table("posts")
  .where("posts.title", "like", "%typescript%");

NOT LIKE

await puri.table("users")
  .where("users.email", "not like", "%spam%");

// WHERE users.email NOT LIKE '%spam%'

whereIn / whereNotIn

๊ฐ’ ๋ชฉ๋ก ์ค‘ ํ•˜๋‚˜

// IN
const userIds = [1, 2, 3, 4, 5];
await puri.table("users")
  .whereIn("users.id", userIds);

// WHERE users.id IN (1, 2, 3, 4, 5)

// NOT IN
await puri.table("users")
  .whereNotIn("users.status", ["banned", "suspended"]);

// WHERE users.status NOT IN ('banned', 'suspended')

๋นˆ ๋ฐฐ์—ด ์ฒ˜๋ฆฌ

// ๋นˆ ๋ฐฐ์—ด์€ ํ•ญ์ƒ false
await puri.table("users")
  .whereIn("users.id", []);

// WHERE 1 = 0 (์•„๋ฌด๊ฒƒ๋„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Œ)

๋ณต์žกํ•œ ์กฐ๊ฑด ๊ทธ๋ฃน

whereGroup (AND ๊ทธ๋ฃน)

๊ด„ํ˜ธ๋กœ ๋ฌถ์ธ ์กฐ๊ฑด๋“ค์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
await puri.table("users")
  .where("users.role", "admin")
  .whereGroup((group) => {
    group.where("users.status", "active")
         .orWhere("users.status", "pending");
  });

// WHERE users.role = 'admin' 
//   AND (users.status = 'active' OR users.status = 'pending')

orWhereGroup (OR ๊ทธ๋ฃน)

await puri.table("products")
  .where("products.published", true)
  .orWhereGroup((group) => {
    group.where("products.featured", true)
         .where("products.stock", ">", 0);
  });

// WHERE products.published = true 
//   OR (products.featured = true AND products.stock > 0)

์ค‘์ฒฉ ๊ทธ๋ฃน

await puri.table("orders")
  .whereGroup((outer) => {
    outer.where("orders.status", "completed")
         .whereGroup((inner) => {
           inner.where("orders.amount", ">", 1000)
                .orWhere("orders.vip", true);
         });
  });

// WHERE (
//   orders.status = 'completed' 
//   AND (orders.amount > 1000 OR orders.vip = true)
// )

whereMatch (MySQL)

MySQL์˜ FULLTEXT ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
await puri.table("posts")
  .whereMatch("posts.content", "typescript programming");

// WHERE MATCH (posts.content) AGAINST ('typescript programming')
whereMatch๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•ด๋‹น ์ปฌ๋Ÿผ์— FULLTEXT ์ธ๋ฑ์Šค๊ฐ€ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

whereTsSearch (PostgreSQL)

PostgreSQL์˜ tsvector๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
await puri.table("posts")
  .whereTsSearch("posts.content_tsv", "typescript programming");

// WHERE posts.content_tsv @@ websearch_to_tsquery('simple', 'typescript programming')
์˜ต์…˜:
// ํŒŒ์„œ ์ง€์ •
await puri.table("posts")
  .whereTsSearch("posts.content_tsv", "typescript", {
    parser: "plainto_tsquery",  // ๋˜๋Š” "to_tsquery", "phraseto_tsquery"
    config: "english"  // ์–ธ์–ด ์„ค์ •
  });

// ๊ฐ„๋‹จํ•œ config๋งŒ ์ง€์ •
await puri.table("posts")
  .whereTsSearch("posts.content_tsv", "typescript", "english");

whereSearch (PGroonga)

PGroonga ํ™•์žฅ์„ ์‚ฌ์šฉํ•œ ์ „๋ฌธ ๊ฒ€์ƒ‰์ž…๋‹ˆ๋‹ค.
// ๋‹จ์ผ ์ปฌ๋Ÿผ ๊ฒ€์ƒ‰
await puri.table("posts")
  .whereSearch("posts.title", "ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ");

// WHERE posts.title &@~ pgroonga_condition('ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ')

// ์—ฌ๋Ÿฌ ์ปฌ๋Ÿผ ๊ฒ€์ƒ‰ (๋ฐฐ์—ด)
await puri.table("posts")
  .whereSearch(
    ["posts.title", "posts.content"],
    "ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ"
  );

// WHERE ARRAY[posts.title::text, posts.content::text] 
//   &@~ pgroonga_condition('ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ')

// ๊ฐ€์ค‘์น˜ ์ง€์ •
await puri.table("posts")
  .whereSearch(
    ["posts.title", "posts.content"],
    "ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ",
    { weights: [10, 1] }  // title์ด content๋ณด๋‹ค 10๋ฐฐ ์ค‘์š”
  );
PGroonga ๊ฒ€์ƒ‰์€ ์‚ฌ์šฉํ•  ์ธ๋ฑ์Šค์™€ ๋™์ผํ•œ ์ปฌ๋Ÿผ ๊ตฌ์„ฑ์œผ๋กœ ๊ฒ€์ƒ‰ํ•ด์•ผ ์ธ๋ฑ์Šค๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

Raw SQL ์กฐ๊ฑด

whereRaw

๋ณต์žกํ•œ SQL ์กฐ๊ฑด์„ ์ง์ ‘ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// ํŒŒ๋ผ๋ฏธํ„ฐ ์—†์Œ
await puri.table("users")
  .whereRaw("YEAR(users.created_at) = YEAR(CURRENT_DATE)");

// ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ
await puri.table("users")
  .whereRaw("users.age BETWEEN ? AND ?", [18, 65]);

// ์—ฌ๋Ÿฌ ์กฐ๊ฑด
await puri.table("orders")
  .whereRaw("DATE(orders.created_at) = ?", ["2024-01-01"])
  .whereRaw("orders.amount > ?", [1000]);
SQL ์ธ์ ์…˜์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํ•ญ์ƒ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

์‹ค์ „ ์˜ˆ์‹œ

async function getActiveAdmins() {
  return puri.table("users")
    .where({
      "users.role": "admin",
      "users.status": "active",
      "users.email_verified": true
    })
    .where("users.last_login", ">", new Date(Date.now() - 30 * 24 * 60 * 60 * 1000))
    .select({
      id: "users.id",
      email: "users.email",
      name: "users.name",
      last_login: "users.last_login"
    })
    .orderBy("users.last_login", "desc");
}

์กฐ๊ฑด ์ฒด์ด๋‹

๋ชจ๋“  where ๋ฉ”์„œ๋“œ๋Š” ์ฒด์ด๋‹ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๊ธฐ๋ณธ์ ์œผ๋กœ AND๋กœ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค.
const result = await puri.table("users")
  .where("users.role", "admin")         // AND
  .where("users.status", "active")       // AND
  .where("users.age", ">", 18)          // AND
  .whereIn("users.country", ["KR", "US"]) // AND
  .where("users.verified", true);        // AND

OR ์กฐ๊ฑด

WhereGroup ๋‚ด์—์„œ orWhere

await puri.table("users")
  .whereGroup((group) => {
    group.where("users.role", "admin")
         .orWhere("users.role", "moderator")
         .orWhere("users.role", "editor");
  });

// WHERE (
//   users.role = 'admin' 
//   OR users.role = 'moderator' 
//   OR users.role = 'editor'
// )
์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—๋Š” orWhere๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. OR ์กฐ๊ฑด์€ whereGroup ๋˜๋Š” orWhereGroup ๋‚ด์—์„œ ์‚ฌ์šฉํ•˜์„ธ์š”.

ํƒ€์ž… ์•ˆ์ •์„ฑ

WHERE ์กฐ๊ฑด์€ ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ๊ฒ€์ฆ๋ฉ๋‹ˆ๋‹ค.
// โœ… ์˜ฌ๋ฐ”๋ฅธ ์ปฌ๋Ÿผ
await puri.table("users")
  .where("users.email", "like", "%@gmail.com");

// โŒ ํƒ€์ž… ์—๋Ÿฌ: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ปฌ๋Ÿผ
await puri.table("users")
  .where("users.unknown_field", "value");

// โœ… ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…
await puri.table("users")
  .where("users.age", 25);  // number

// โŒ ํƒ€์ž… ์—๋Ÿฌ: ์ž˜๋ชป๋œ ํƒ€์ž…
await puri.table("users")
  .where("users.age", "25");  // string (age๋Š” number)

์„ฑ๋Šฅ ์ตœ์ ํ™”

1. ์ธ๋ฑ์Šค ํ™œ์šฉ

// โœ… ์ข‹์Œ: ์ธ๋ฑ์Šค ์ปฌ๋Ÿผ ์‚ฌ์šฉ
await puri.table("users")
  .where("users.email", "[email protected]");  // email์— ์ธ๋ฑ์Šค ํ•„์š”

// โŒ ๋‚˜์จ: ํ•จ์ˆ˜ ์‚ฌ์šฉ (์ธ๋ฑ์Šค ๋ฌดํšจํ™”)
await puri.table("users")
  .whereRaw("LOWER(users.email) = ?", ["[email protected]"]);

2. LIKE ํŒจํ„ด

// โœ… ์ข‹์Œ: ์•ž ๊ณ ์ • (์ธ๋ฑ์Šค ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
.where("users.name", "like", "John%")

// โŒ ๋‚˜์จ: ์•ž ์™€์ผ๋“œ์นด๋“œ (์ธ๋ฑ์Šค ์‚ฌ์šฉ ๋ถˆ๊ฐ€)
.where("users.name", "like", "%John")

3. IN vs ์—ฌ๋Ÿฌ OR

// โœ… ์ข‹์Œ: IN ์‚ฌ์šฉ
.whereIn("users.id", [1, 2, 3, 4, 5])

// โŒ ๋‚˜์จ: ์—ฌ๋Ÿฌ OR (๋น„ํšจ์œจ์ )
.whereGroup((g) => {
  g.where("users.id", 1)
   .orWhere("users.id", 2)
   .orWhere("users.id", 3)
   .orWhere("users.id", 4)
   .orWhere("users.id", 5)
})

์ฃผ์˜์‚ฌํ•ญ

1. NULL ์ฒ˜๋ฆฌ

// โœ… ์˜ฌ๋ฐ”๋ฆ„: NULL ์ฒดํฌ
.where("users.deleted_at", null)           // IS NULL
.where("users.deleted_at", "=", null)      // IS NULL
.where("users.deleted_at", "!=", null)     // IS NOT NULL

// โŒ ์ž˜๋ชป๋จ: ์ผ๋ฐ˜ ๋น„๊ต (๋™์ž‘ํ•˜์ง€ ์•Š์Œ)
.where("users.deleted_at", "=", undefined)

2. ๋นˆ ๋ฐฐ์—ด

// whereIn์— ๋นˆ ๋ฐฐ์—ด์„ ์ „๋‹ฌํ•˜๋ฉด ๊ฒฐ๊ณผ ์—†์Œ
const ids = [];
await puri.table("users")
  .whereIn("users.id", ids);  // ์•„๋ฌด๊ฒƒ๋„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Œ

// ์กฐ๊ฑด๋ถ€๋กœ ์ฒ˜๋ฆฌ
if (ids.length > 0) {
  query = query.whereIn("users.id", ids);
}

3. Raw SQL ์ฃผ์ž…

// โŒ ์œ„ํ—˜: SQL ์ธ์ ์…˜ ๊ฐ€๋Šฅ
const userInput = "'; DROP TABLE users; --";
.whereRaw(`users.name = '${userInput}'`)

// โœ… ์•ˆ์ „: ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ
.whereRaw("users.name = ?", [userInput])

4. ์กฐ๊ฑด ์ˆœ์„œ

// WHERE ์ˆœ์„œ๋Š” ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Œ

// โœ… ์ข‹์Œ: ์„ ํƒ๋„ ๋†’์€ ์กฐ๊ฑด ๋จผ์ €
.where("users.id", 123)        // ๋งค์šฐ ์„ ํƒ์ 
.where("users.status", "active") // ๋œ ์„ ํƒ์ 

// โŒ ๋น„ํšจ์œจ: ์„ ํƒ๋„ ๋‚ฎ์€ ์กฐ๊ฑด ๋จผ์ €
.where("users.status", "active") // ๋งŽ์€ ํ–‰
.where("users.id", 123)        // ์†Œ์ˆ˜์˜ ํ–‰

๋‹ค์Œ ๋‹จ๊ณ„