Database - Relations 關(guān)系
- 關(guān)于relations與foreign key之間的解釋,請(qǐng)參考 Drizzle soft relations
- 表與表之間的邏輯聯(lián)系,是業(yè)務(wù)邏輯層面的概念
- 關(guān)系型數(shù)據(jù)庫(kù)中常見的關(guān)系:One-to-One,One-to-Many,Many-to-Many
- 視頻與用戶一對(duì)一,視頻對(duì)應(yīng)視頻分類也是一對(duì)一
export const videoRelations = relations(videos, ({ one }) => ({
user: one(users, {
fields: [videos.userId],
references: [users.id],
}),
category: one(categories, {
fields: [videos.categoryId],
references: [categories.id],
}),
}))
- 用戶與視頻一對(duì)多,或者用戶視頻為空(不傳fields、references)
export const usersRelations = relations(users, ({ many }) => ({
videos: many(videos), // 一對(duì)多關(guān)系,用戶可以有多個(gè)視頻,或者為空
}));
Cursor-based pagination 游標(biāo)分頁(yè)
- 我們?cè)讷@取videos數(shù)據(jù)的時(shí)候,考慮到頁(yè)面是無(wú)限加載滾動(dòng)的,因此這個(gè)tRPC路由使用游標(biāo)分頁(yè)
- 用一個(gè)錨點(diǎn)(例如上一條記錄的id或時(shí)間戳)作為起點(diǎn),往后取數(shù)據(jù);需要記住最后一條記錄來(lái)精準(zhǔn)獲取下一頁(yè)數(shù)據(jù)
- 輸入?yún)?shù)有2個(gè):
cursor 和 limit
getAll: protectedProcedure
.input(
z.object({
// 可選游標(biāo)參數(shù),上一次的最后一條數(shù)據(jù),首次請(qǐng)求可不傳
cursor: z.object({
id: z.uuid(),
updatedAt: z.date(),
}).nullish(),
// 每頁(yè)數(shù)據(jù)條數(shù)限制
limit: z.number().min(1).max(100),
})
)
- 使用
prefetchInfinite() 預(yù)取數(shù)據(jù):預(yù)取只加載第一頁(yè)的數(shù)據(jù),因此不用傳 cursor
void trpc.studio.getAll.prefetchInfinite({
limit: DEFAULT_LIMIT
});
- 獲取緩存數(shù)據(jù)時(shí)使用
useSuspenseInfiniteQuery()
const [videos, query] = trpc.studio.getAll.useSuspenseInfiniteQuery(
{ limit: DEFAULT_LIMIT },
{ getNextPageParam: lastPage => lastPage.nextCursor }
)
- tRPC路由中的Sql查詢條件:
-
eq(field, value) 等于,field = value
-
and(cond1, cond2, ...) 且,多個(gè)條件同時(shí)滿足,cond1 and cond2 and ...
-
or(cond1, cond2, ...) 或,多個(gè)條件只要一個(gè)滿足,cond1 or cond2 or ...
-
lt(field, value) 小于,Less Than,field < value
-
desc(field) 倒序排序,DESC,根據(jù)field字段倒序排序
import { eq, and, or, lt, desc } from 'drizzle-orm'
getAll: protectedProcedure
.input(...)
.query(async ({ ctx, input }) => {
const { cursor, limit } = input
const { id: userId } = ctx.user
const data = await db
.select()
.from(videos)
.where(
and(
eq( videos.userId, userId ), // 只查詢當(dāng)前用戶的視頻
cursor ? or(
lt(videos.updatedAt, cursor.updatedAt), // 查詢更新時(shí)間小于游標(biāo)參數(shù)的記錄
and(
eq(videos.updatedAt, cursor.updatedAt), // 如果更新時(shí)間相同,則查詢id小于游標(biāo)參數(shù)的記錄
lt(videos.id, cursor.id)
)
) : undefined
)
)
.orderBy(desc(videos.updatedAt), desc(videos.id)) // 按更新時(shí)間和ID降序排列,也就是最新的視頻在前面
.limit(limit + 1) // 多查詢一條數(shù)據(jù)用于判斷是否還有下一頁(yè)
const hasMore = data.length > limit // 判斷是否還有下一頁(yè)
const videosData = hasMore ? data.slice(0, -1) : data // 如果還有下一頁(yè),則去掉最后一條數(shù)據(jù)
const lastVideo = videosData[videosData.length - 1] // 獲取最后一條數(shù)據(jù),用于生成下一頁(yè)的游標(biāo)
// 生成下一頁(yè)的cursor
const nextCursor = hasMore ? {
id: lastVideo.id,
updatedAt: lastVideo.updatedAt,
} : null
return {
videosData,
nextCursor, // 是一個(gè)對(duì)象,包含了id和updatedAt兩個(gè)字段
}
})