select๋ ์กฐํํ ํ๋๋ฅผ ์ง์ ํ๋ ๋ฉ์๋์
๋๋ค. ํ์
์์ ํ๊ฒ ํ๋๋ฅผ ์ ํํ๊ณ , SQL ์ง๊ณ ํจ์ ๋ฐ ํํ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
์ ์ฒด ํ๋ ์กฐํ
const users = await puri.table("users").selectAll();
// SELECT * FROM users
ํน์ ํ๋ ์ ํ
const users = await puri.table("users").select({
id: "users.id",
email: "users.email",
name: "users.name"
});
// SELECT users.id, users.email, users.name FROM users
ํ๋ ๋ณ์นญ (Alias)
const users = await puri.table("users").select({
userId: "users.id",
userName: "users.name"
});
// SELECT users.id as userId, users.name as userName FROM users
SQL ์ง๊ณ ํจ์
Puri๋ ํ์
์์ ํ SQL ํจ์๋ค์ ์ ๊ณตํฉ๋๋ค.
const result = await puri.table("users")
.select({
total: Puri.count() // COUNT(*)
})
.first();
console.log(result.total); // number
// ํน์ ์ปฌ๋ผ ์นด์ดํธ
const result = await puri.table("posts")
.select({
published: Puri.count("posts.published")
})
.first();
SUM, AVG, MAX, MIN
const stats = await puri.table("orders")
.select({
total: Puri.sum("orders.amount"),
average: Puri.avg("orders.amount"),
max: Puri.max("orders.amount"),
min: Puri.min("orders.amount")
})
.first();
console.log(stats.total); // number
console.log(stats.average); // number
๋ฌธ์์ด ํจ์
const users = await puri.table("users").select({
id: "users.id",
full_name: Puri.concat("users.first_name", "' '", "users.last_name"),
email_upper: Puri.upper("users.email"),
email_lower: Puri.lower("users.email")
});
Raw SQL ํํ์
๋ณต์กํ SQL ํํ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ์
๋ณ Raw ํจ์
const result = await puri.table("users").select({
id: "users.id",
// ๋ฌธ์์ด ๋ฐํ
display_name: Puri.rawString("CONCAT(users.first_name, ' ', users.last_name)"),
// ์ซ์ ๋ฐํ
age: Puri.rawNumber("YEAR(CURRENT_DATE) - YEAR(users.birth_date)"),
// ๋ถ๋ฆฌ์ธ ๋ฐํ
is_adult: Puri.rawBoolean("YEAR(CURRENT_DATE) - YEAR(users.birth_date) >= 18"),
// ๋ ์ง ๋ฐํ
signup_date: Puri.rawDate("DATE(users.created_at)"),
// ๋ฌธ์์ด ๋ฐฐ์ด ๋ฐํ
tags: Puri.rawStringArray("ARRAY_AGG(tags.name)")
});
// ํ์
์ด ์๋์ผ๋ก ์ถ๋ก ๋จ
result[0].display_name; // string
result[0].age; // number
result[0].is_adult; // boolean
result[0].signup_date; // Date
result[0].tags; // string[]
์ค์ฒฉ ๊ฐ์ฒด ์ ํ
์ค์ฒฉ ๊ฐ์ฒด ๊ตฌ์กฐ๋ก SELECT๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
const users = await puri.table("users")
.leftJoin("posts", "users.id", "posts.user_id")
.select({
id: "users.id",
email: "users.email",
post: {
id: "posts.id",
title: "posts.title"
}
});
// ์๋์ผ๋ก flatํ๊ฒ ๋ณํ๋จ:
// SELECT
// users.id,
// users.email,
// posts.id as post__id,
// posts.title as post__title
// FROM users LEFT JOIN posts ...
// ๊ฒฐ๊ณผ๋ hydrate๋์ด ์ค์ฒฉ ๊ฐ์ฒด๋ก ๋ฐํ
console.log(users[0].post.title);
appendSelect
๊ธฐ์กด SELECT์ ํ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.
let query = puri.table("users").select({
id: "users.id",
email: "users.email"
});
// ํ๋ ์ถ๊ฐ
query = query.appendSelect({
name: "users.name",
created_at: "users.created_at"
});
// SELECT users.id, users.email, users.name, users.created_at FROM users
select()๋ ๊ธฐ์กด SELECT๋ฅผ ๋ฎ์ด์ฐ๊ณ , appendSelect()๋ ์ถ๊ฐํฉ๋๋ค.
Full-Text Search ํจ์
ts_highlight (PostgreSQL)
PostgreSQL์ tsvector๋ฅผ ์ฌ์ฉํ ํ์ด๋ผ์ดํ
์
๋๋ค.
const posts = await puri.table("posts")
.whereTsSearch("posts.content_tsv", "typescript")
.select({
id: "posts.id",
title: Puri.tsHighlight("posts.title", "typescript"),
content: Puri.tsHighlight("posts.content", "typescript", {
startSel: "<mark>",
stopSel: "</mark>",
maxFragments: 3,
maxWords: 50
})
});
์ต์
:
parser: ์ฟผ๋ฆฌ ํ์ (๊ธฐ๋ณธ: "websearch_to_tsquery")
config: ํ
์คํธ ๊ฒ์ ์ค์ (๊ธฐ๋ณธ: "simple")
startSel: ์์ ํ๊ทธ (๊ธฐ๋ณธ: "<b>")
stopSel: ์ข
๋ฃ ํ๊ทธ (๊ธฐ๋ณธ: "</b>")
maxWords: ์ต๋ ๋จ์ด ์
minWords: ์ต์ ๋จ์ด ์
maxFragments: ์ต๋ ์กฐ๊ฐ ์
ts_rank (PostgreSQL)
๊ฒ์ ๊ด๋ จ๋ ์ ์๋ฅผ ๊ณ์ฐํฉ๋๋ค.
const posts = await puri.table("posts")
.whereTsSearch("posts.content_tsv", "typescript")
.select({
id: "posts.id",
title: "posts.title",
// ๊ธฐ๋ณธ rank
rank: Puri.tsRank("posts.content_tsv", "typescript"),
// ๊ฐ์ค์น ์ ์ฉ
weighted_rank: Puri.tsRank("posts.content_tsv", "typescript", {
weights: [0.1, 0.2, 0.4, 1.0], // D, C, B, A ๊ฐ์ค์น
normalization: 1 // ๊ธธ์ด ์ ๊ทํ
}),
// Cover Density rank
rank_cd: Puri.tsRankCd("posts.content_tsv", "typescript")
})
.orderBy("rank", "desc");
์ต์
:
parser: ์ฟผ๋ฆฌ ํ์ (๊ธฐ๋ณธ: "websearch_to_tsquery")
config: ํ
์คํธ ๊ฒ์ ์ค์ (๊ธฐ๋ณธ: "simple")
weights: [D, C, B, A] ๊ฐ์ค์น ๋ฐฐ์ด
normalization: ์ ๊ทํ ์ต์
(0-32)
PGroonga ํจ์
PGroonga ํ์ฅ์ ์ฌ์ฉํ ์ ๋ฌธ ๊ฒ์์
๋๋ค.
const posts = await puri.table("posts")
.whereSearch("posts.title", "ํ์
์คํฌ๋ฆฝํธ")
.select({
id: "posts.id",
// ๋จ์ผ ์ปฌ๋ผ ํ์ด๋ผ์ดํ
title: Puri.highlight("posts.title", "ํ์
์คํฌ๋ฆฝํธ"),
// ์ฌ๋ฌ ๊ฒ์์ด
content: Puri.highlight("posts.content", ["ํ์
์คํฌ๋ฆฝํธ", "์๋ฐ์คํฌ๋ฆฝํธ"]),
// ์ฌ๋ฌ ์ปฌ๋ผ ํ์ด๋ผ์ดํ
(๋ฐฐ์ด ๋ฐํ)
highlights: Puri.highlight(
["posts.title", "posts.content"],
["ํ์
์คํฌ๋ฆฝํธ", "Node.js"]
),
// ๊ฒ์ ์ ์
score: Puri.score()
})
.orderBy("score", "desc");
// ํ์
์๋ ์ถ๋ก
console.log(posts[0].title); // string
console.log(posts[0].highlights); // string[]
console.log(posts[0].score); // number
์ค์ ์์
์ฌ์ฉ์ ํต๊ณ
์ฃผ๋ฌธ ์์ฝ
์ฌ์ฉ์ + ๊ฒ์๋ฌผ
๊ฒ์ + ํ์ด๋ผ์ดํ
const stats = await puri.table("users")
.select({
total_users: Puri.count(),
active_users: Puri.rawNumber(
"SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END)"
),
avg_age: Puri.avg("users.age"),
oldest: Puri.max("users.age"),
youngest: Puri.min("users.age")
})
.first();
console.log(stats);
// {
// total_users: 1000,
// active_users: 850,
// avg_age: 32.5,
// oldest: 65,
// youngest: 18
// }
const orderSummary = await puri.table("orders")
.where("orders.status", "completed")
.select({
order_count: Puri.count(),
total_amount: Puri.sum("orders.amount"),
avg_amount: Puri.avg("orders.amount"),
max_amount: Puri.max("orders.amount"),
// ๋ ์ง ํ์ ๋ณํ
first_order: Puri.rawDate("MIN(orders.created_at)"),
last_order: Puri.rawDate("MAX(orders.created_at)")
})
.first();
const users = await puri.table("users")
.leftJoin("posts", "users.id", "posts.user_id")
.select({
// ์ฌ์ฉ์ ์ ๋ณด
id: "users.id",
name: "users.name",
email: "users.email",
// ๊ฒ์๋ฌผ ์ ๋ณด (์ค์ฒฉ)
post: {
id: "posts.id",
title: "posts.title",
views: "posts.views"
},
// ๊ณ์ฐ ํ๋
full_info: Puri.concat(
"users.name",
"' ('",
"users.email",
"')'"
)
});
// ์ค์ฒฉ ๊ฐ์ฒด๋ก ๋ฐํ
console.log(users[0].post.title);
const searchResults = await puri.table("posts")
.whereTsSearch("posts.content_tsv", "typescript programming")
.select({
id: "posts.id",
// ์ ๋ชฉ ํ์ด๋ผ์ดํ
title: Puri.tsHighlight("posts.title", "typescript programming", {
startSel: "<mark>",
stopSel: "</mark>"
}),
// ๋ด์ฉ ๋ฐ์ท + ํ์ด๋ผ์ดํ
excerpt: Puri.tsHighlight("posts.content", "typescript programming", {
startSel: "<mark>",
stopSel: "</mark>",
maxFragments: 2,
maxWords: 30
}),
// ๊ด๋ จ๋ ์ ์
rank: Puri.tsRank("posts.content_tsv", "typescript programming", {
weights: [0.1, 0.2, 0.4, 1.0]
})
})
.orderBy("rank", "desc")
.limit(20);
// ํ์ด๋ผ์ดํ
๋ HTML ๋ฐํ
console.log(searchResults[0].title);
// "Introduction to <mark>TypeScript</mark> <mark>Programming</mark>"
ํ์
์์ ์ฑ
SELECT์ ๋ฐํ ํ์
์ ์๋์ผ๋ก ์ถ๋ก ๋ฉ๋๋ค.
const result = await puri.table("users")
.select({
id: "users.id",
email: "users.email",
count: Puri.count(),
upper_name: Puri.upper("users.name")
})
.first();
// ํ์
์๋ ์ถ๋ก
result.id; // number
result.email; // string
result.count; // number
result.upper_name; // string
result.unknown; // โ ํ์
์๋ฌ
์ฃผ์์ฌํญ
1. select vs appendSelect
// โ ๋์ ์: ์ด์ select๊ฐ ๋ฎ์ด์์์ง
query.select({ id: "users.id" })
.select({ name: "users.name" }); // id๊ฐ ์ฌ๋ผ์ง!
// โ
์ข์ ์: appendSelect๋ก ์ถ๊ฐ
query.select({ id: "users.id" })
.appendSelect({ name: "users.name" });
2. ํ
์ด๋ธ ์ ๋์ฌ ํ์
// โ ์๋ชป๋จ
puri.table("users").select({
id: "id" // ์ด๋ค ํ
์ด๋ธ์ id์ธ์ง ๋ถ๋ช
ํ
});
// โ
์ฌ๋ฐ๋ฆ
puri.table("users").select({
id: "users.id" // ๋ช
ํํ ํ
์ด๋ธ ์ง์
});
3. Raw SQL ํ์
์ง์
// Raw SQL ์ฌ์ฉ ์ ๋ฐํ ํ์
๋ช
์
const result = await puri.table("users").select({
// โ
ํ์
๋ช
์
age: Puri.rawNumber("YEAR(CURRENT_DATE) - YEAR(birth_date)"),
// โ ํ์
์์ (any)
unknown: puri.raw("SOME_COMPLEX_SQL")
});
๋ค์ ๋จ๊ณ