如何使用 TSVECTOR 和 TSQUERY 搜索单个或多个列
How to search on a single OR multiple columns with TSVECTOR and TSQUERY
我使用了一些样板代码(如下)创建了我指定的所有列(在 searchObjects
中)的规范化 tsvector _search
列,我希望 full-text 对其进行搜索。
在大多数情况下,这很好。我将其与 Sequelize 结合使用,因此我的查询如下所示:
const articles = await Article.findAndCountAll({
where: {
[Sequelize.Op.and]: Sequelize.fn(
'article._search @@ plainto_tsquery',
'english',
Sequelize.literal(':query')
),
[Sequelize.Op.and]: { status: STATUS_TYPE_ACTIVE }
},
replacements: { query: q }
});
搜索索引设置:
const vectorName = '_search';
const searchObjects = {
articles: ['headline', 'cleaned_body', 'summary'],
brands: ['name', 'cleaned_about'],
products: ['name', 'cleaned_description']
};
module.exports = {
up: async queryInterface =>
await queryInterface.sequelize.transaction(t =>
Promise.all(
Object.keys(searchObjects).map(table =>
queryInterface.sequelize
.query(
`
ALTER TABLE ${table} ADD COLUMN ${vectorName} TSVECTOR;
`,
{ transaction: t }
)
.then(() =>
queryInterface.sequelize.query(
`
UPDATE ${table} SET ${vectorName} = to_tsvector('english', ${searchObjects[
table
].join(" || ' ' || ")});
`,
{ transaction: t }
)
)
.then(() =>
queryInterface.sequelize.query(
`
CREATE INDEX ${table}_search ON ${table} USING gin(${vectorName});
`,
{ transaction: t }
)
)
.then(() =>
queryInterface.sequelize.query(
`
CREATE TRIGGER ${table}_vector_update
BEFORE INSERT OR UPDATE ON ${table}
FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger(${vectorName}, 'pg_catalog.english', ${searchObjects[
table
].join(', ')});
`,
{ transaction: t }
)
)
.error(console.log)
)
)
),
down: async queryInterface =>
await queryInterface.sequelize.transaction(t =>
Promise.all(
Object.keys(searchObjects).map(table =>
queryInterface.sequelize
.query(
`
DROP TRIGGER ${table}_vector_update ON ${table};
`,
{ transaction: t }
)
.then(() =>
queryInterface.sequelize.query(
`
DROP INDEX ${table}_search;
`,
{ transaction: t }
)
)
.then(() =>
queryInterface.sequelize.query(
`
ALTER TABLE ${table} DROP COLUMN ${vectorName};
`,
{ transaction: t }
)
)
)
)
)
};
问题在于,由于代码在 searchObjects
的每个数组中连接了两列,因此存储的是每个数组中所有列的组合索引。
例如在 articles
table 上:'headline', 'cleaned_body', 'summary'
都是单个生成的 _search
向量的一部分。
因此,我无法真正仅按 headline
或 cleaned_body
等进行搜索。我希望能够分别搜索每一列,也可以一起搜索。
用例在我的搜索输入中,我只想搜索标题。但是在我的搜索结果页面上,我想搜索 searchObjects
.
中指定的所有列
有人可以提示我需要更改什么吗?我应该为每一列创建一个新的 tsvector 吗?
如果有人好奇,下面是为每一列创建 tsvector 的方法:
try {
for (const table in searchObjects) {
for (const col of searchObjects[table]) {
await queryInterface.sequelize.query(
`ALTER TABLE ${table} ADD COLUMN ${col + vectorName} TSVECTOR;`,
{ transaction }
);
await queryInterface.sequelize.query(
`UPDATE ${table} SET ${col + vectorName} = to_tsvector('english', ${col});`,
{ transaction }
);
await queryInterface.sequelize.query(
`CREATE INDEX ${table}_${col}_search ON ${table} USING gin(${col +
vectorName});`,
{ transaction }
);
await queryInterface.sequelize.query(
`CREATE TRIGGER ${table}_${col}_vector_update
BEFORE INSERT OR UPDATE ON ${table}
FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger(${col +
vectorName}, 'pg_catalog.english', ${col});`,
{ transaction }
);
}
}
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
我使用了一些样板代码(如下)创建了我指定的所有列(在 searchObjects
中)的规范化 tsvector _search
列,我希望 full-text 对其进行搜索。
在大多数情况下,这很好。我将其与 Sequelize 结合使用,因此我的查询如下所示:
const articles = await Article.findAndCountAll({
where: {
[Sequelize.Op.and]: Sequelize.fn(
'article._search @@ plainto_tsquery',
'english',
Sequelize.literal(':query')
),
[Sequelize.Op.and]: { status: STATUS_TYPE_ACTIVE }
},
replacements: { query: q }
});
搜索索引设置:
const vectorName = '_search';
const searchObjects = {
articles: ['headline', 'cleaned_body', 'summary'],
brands: ['name', 'cleaned_about'],
products: ['name', 'cleaned_description']
};
module.exports = {
up: async queryInterface =>
await queryInterface.sequelize.transaction(t =>
Promise.all(
Object.keys(searchObjects).map(table =>
queryInterface.sequelize
.query(
`
ALTER TABLE ${table} ADD COLUMN ${vectorName} TSVECTOR;
`,
{ transaction: t }
)
.then(() =>
queryInterface.sequelize.query(
`
UPDATE ${table} SET ${vectorName} = to_tsvector('english', ${searchObjects[
table
].join(" || ' ' || ")});
`,
{ transaction: t }
)
)
.then(() =>
queryInterface.sequelize.query(
`
CREATE INDEX ${table}_search ON ${table} USING gin(${vectorName});
`,
{ transaction: t }
)
)
.then(() =>
queryInterface.sequelize.query(
`
CREATE TRIGGER ${table}_vector_update
BEFORE INSERT OR UPDATE ON ${table}
FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger(${vectorName}, 'pg_catalog.english', ${searchObjects[
table
].join(', ')});
`,
{ transaction: t }
)
)
.error(console.log)
)
)
),
down: async queryInterface =>
await queryInterface.sequelize.transaction(t =>
Promise.all(
Object.keys(searchObjects).map(table =>
queryInterface.sequelize
.query(
`
DROP TRIGGER ${table}_vector_update ON ${table};
`,
{ transaction: t }
)
.then(() =>
queryInterface.sequelize.query(
`
DROP INDEX ${table}_search;
`,
{ transaction: t }
)
)
.then(() =>
queryInterface.sequelize.query(
`
ALTER TABLE ${table} DROP COLUMN ${vectorName};
`,
{ transaction: t }
)
)
)
)
)
};
问题在于,由于代码在 searchObjects
的每个数组中连接了两列,因此存储的是每个数组中所有列的组合索引。
例如在 articles
table 上:'headline', 'cleaned_body', 'summary'
都是单个生成的 _search
向量的一部分。
因此,我无法真正仅按 headline
或 cleaned_body
等进行搜索。我希望能够分别搜索每一列,也可以一起搜索。
用例在我的搜索输入中,我只想搜索标题。但是在我的搜索结果页面上,我想搜索 searchObjects
.
有人可以提示我需要更改什么吗?我应该为每一列创建一个新的 tsvector 吗?
如果有人好奇,下面是为每一列创建 tsvector 的方法:
try {
for (const table in searchObjects) {
for (const col of searchObjects[table]) {
await queryInterface.sequelize.query(
`ALTER TABLE ${table} ADD COLUMN ${col + vectorName} TSVECTOR;`,
{ transaction }
);
await queryInterface.sequelize.query(
`UPDATE ${table} SET ${col + vectorName} = to_tsvector('english', ${col});`,
{ transaction }
);
await queryInterface.sequelize.query(
`CREATE INDEX ${table}_${col}_search ON ${table} USING gin(${col +
vectorName});`,
{ transaction }
);
await queryInterface.sequelize.query(
`CREATE TRIGGER ${table}_${col}_vector_update
BEFORE INSERT OR UPDATE ON ${table}
FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger(${col +
vectorName}, 'pg_catalog.english', ${col});`,
{ transaction }
);
}
}
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}