跳至主要內容
版本:v6 - 穩定版

子查詢

假設您有兩個模型,PostReaction,並設定了一對多關聯,使得一個貼文有多個反應。

const Post = sequelize.define(
'post',
{
content: DataTypes.STRING,
},
{ timestamps: false },
);

const Reaction = sequelize.define(
'reaction',
{
type: DataTypes.STRING,
},
{ timestamps: false },
);

Post.hasMany(Reaction);
Reaction.belongsTo(Post);

注意:我們停用了時間戳記,只是為了讓接下來的範例查詢更簡短。

讓我們在表格中填入一些資料

async function makePostWithReactions(content, reactionTypes) {
const post = await Post.create({ content });
await Reaction.bulkCreate(reactionTypes.map(type => ({ type, postId: post.id })));
return post;
}

await makePostWithReactions('Hello World', [
'Like',
'Angry',
'Laugh',
'Like',
'Like',
'Angry',
'Sad',
'Like',
]);
await makePostWithReactions('My Second Post', ['Laugh', 'Laugh', 'Like', 'Laugh']);

現在,我們準備好開始了解子查詢的強大之處。

假設我們想透過 SQL 計算每個貼文的 laughReactionsCount。我們可以使用子查詢來達成,如下所示

SELECT
*,
(
SELECT COUNT(*)
FROM reactions AS reaction
WHERE
reaction.postId = post.id
AND
reaction.type = "Laugh"
) AS laughReactionsCount
FROM posts AS post

如果我們透過 Sequelize 執行上述原始 SQL 查詢,我們會得到

[
{
"id": 1,
"content": "Hello World",
"laughReactionsCount": 1
},
{
"id": 2,
"content": "My Second Post",
"laughReactionsCount": 3
}
]

那麼,我們如何透過 Sequelize 的更多協助來達成這個目標,而無需手動編寫整個原始查詢呢?

答案是:將尋找器方法(例如 findAll)的 attributes 選項與 sequelize.literal 實用函數結合使用,這個函數允許您直接將任意內容插入查詢中,而無需任何自動跳脫。

這表示 Sequelize 將協助您處理主要的、較大的查詢,但您仍然必須自己編寫該子查詢

Post.findAll({
attributes: {
include: [
[
// Note the wrapping parentheses in the call below!
sequelize.literal(`(
SELECT COUNT(*)
FROM reactions AS reaction
WHERE
reaction.postId = post.id
AND
reaction.type = "Laugh"
)`),
'laughReactionsCount',
],
],
},
});

重要注意事項:由於 sequelize.literal 將任意內容插入查詢中而不進行跳脫,因此值得特別注意,因為它可能是(重大)安全漏洞的來源。不應在使用者產生的內容上使用。 然而,在這裡,我們使用 sequelize.literal 與一個固定的字串,由我們(程式設計師)仔細編寫。這是沒問題的,因為我們知道自己在做什麼。

上述程式碼會產生以下輸出

[
{
"id": 1,
"content": "Hello World",
"laughReactionsCount": 1
},
{
"id": 2,
"content": "My Second Post",
"laughReactionsCount": 3
}
]

成功!

使用子查詢進行複雜排序

這個想法可用於啟用複雜排序,例如按照貼文獲得的「笑」反應次數進行排序

Post.findAll({
attributes: {
include: [
[
sequelize.literal(`(
SELECT COUNT(*)
FROM reactions AS reaction
WHERE
reaction.postId = post.id
AND
reaction.type = "Laugh"
)`),
'laughReactionsCount',
],
],
},
order: [[sequelize.literal('laughReactionsCount'), 'DESC']],
});

結果

[
{
"id": 2,
"content": "My Second Post",
"laughReactionsCount": 3
},
{
"id": 1,
"content": "Hello World",
"laughReactionsCount": 1
}
]