-
Database - Foreign keys 外键
- 外键就是在一张表里,存放另一张表主键的字段,用来建立表与表之间的约束和联系
- 外键是数据库级别的约束,当前例子中,如果用户注销,也就是users.id被删除,那么该用户下的发布的视频都会被删除
- 外键与 relations 功能类似,但作用级别不同
export const videos = pgTable("videos", {
...
userId: uuid("user_id").references(() => users.id, { // 外键 - 关联到users表的id字段
onDelete: "cascade", // 级联删除,如果用户被删除,则删除该用户的所有视频
}).notNull(),
categoryId: uuid("category_id").references(() => categories.id, {
onDelete: "set null", // 级联删除,如果分类被删除,则将视频的分类设置为null,但不删除视频
}),
...
})
-
Database - Relations 关系
- 关于relations与foreign key之间的解释,请参考 Drizzle soft relations
- 表与表之间的逻辑联系,是业务逻辑层面的概念
- 关系型数据库中常见的关系:One-to-One,One-to-Many,Many-to-Many
- 视频与用户一对一,视频对应视频分类也是一对一
export const videoRelations = relations(videos, ({ one }) => ({
user: one(users, {
fields: [videos.userId],
references: [users.id],
}),
category: one(categories, {
fields: [videos.categoryId],
references: [categories.id],
}),
}))
- 用户与视频一对多,或者用户视频为空(不传fields、references)
export const usersRelations = relations(users, ({ many }) => ({
videos: many(videos),
}));
-
Cursor-based pagination 游标分页
- 我们在获取videos数据的时候,考虑到页面是无限加载滚动的,因此这个tRPC路由使用游标分页
- 用一个锚点(例如上一条记录的id或时间戳)作为起点,往后取数据;需要记住最后一条记录来精准获取下一页数据
- 输入参数有2个:
cursor 和 limit
getAll: protectedProcedure
.input(
z.object({
// 可选游标参数,上一次的最后一条数据,首次请求可不传
cursor: z.object({
id: z.uuid(),
updatedAt: z.date(),
}).nullish(),
// 每页数据条数限制
limit: z.number().min(1).max(100),
})
)
- 使用
prefetchInfinite() 预取数据:预取只加载第一页的数据,因此不用传 cursor
void trpc.studio.getAll.prefetchInfinite({
limit: DEFAULT_LIMIT
});
- 获取缓存数据时使用
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, ...) 且,多个条件同时满足,cond1 and cond2 and ...
or(cond1, cond2, ...) 或,多个条件只要一个满足,cond1 or cond2 or ...
lt(field, value) 小于,Less Than,field < value
desc(field) 倒序排序,DESC,根据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 ),
cursor ? or(
lt(videos.updatedAt, cursor.updatedAt),
and(
eq(videos.updatedAt, cursor.updatedAt),
lt(videos.id, cursor.id)
)
) : undefined
)
)
.orderBy(desc(videos.updatedAt), desc(videos.id))
.limit(limit + 1)
const hasMore = data.length > limit
const videosData = hasMore ? data.slice(0, -1) : data
const lastVideo = videosData[videosData.length - 1]
const nextCursor = hasMore ? {
id: lastVideo.id,
updatedAt: lastVideo.updatedAt,
} : null
return {
videosData,
nextCursor,
}
})