优化这个 sql 查询的替代方法是什么?
what are the alternative approaches to optimize this sql query?
我只是 sql 的初学者,我正在尝试优化 sql 查询但还没有任何想法,所以如果有人帮助我,我会与你们分享查询通过这个出来将不胜感激。
表格已经编入索引,但如果您有任何复合索引方法或任何您可以共享的方法。
这是代码:
var viewDealsSQLV2 = function (apiReference, userId, workflowId, dealStatus, startFrom, pageSize, rangeFilter,
dealStage, role, businessOwnerId, dealScore, getFollowersData, userFilters, tags, country, city,getMemberAssignToDeals) {
return new Promise((resolve, reject) => {
var tableName = constants.tableName.TB_DEAL_SUMMARY
var limitFilter = "";
var tagsFilter = "";
var followFilter = ``,
followJoin = ``;
if ((role == constants.userRoles.TEAM_MEMBER) || ((role == constants.userRoles.ADMIN || role == constants.userRoles.BUSINESS_OWNER) && getFollowersData && Array.isArray(userFilters) && userFilters.length)) {
followJoin = ` LEFT JOIN tb_deal_follower ON tds.deal_id = tb_deal_follower.deal_id `;
}
var filters = ''
var organisationJoin = ''
if (tags) {
tagsFilter = ` LEFT JOIN tb_deal_tag_mapping ON tds.deal_id = tb_deal_tag_mapping.deal_id `
}
if (city || country) {
organisationJoin = ` LEFT JOIN tb_organizations ON tb_organizations.organization_id = tds.organization_id `
}
// console.log("+++++++",followJoin)
var sql = ` SELECT
deal_id,
email,
phone,
activity_date,
activity_status,
contact_name,
deal_tag_names,
name,
organization_id,
contact_person_id,
deal_score,
marketing_campaign,
business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
owner_id ,
created_at,
latest_activity_date
FROM
(
SELECT
deal_id,
email,
phone,
activity_date,
activity_status,
contact_name,
deal_tag_names,
name,
organization_id,
contact_person_id,
deal_score,
marketing_campaign,
business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
owner_id,
created_at,
latest_activity_date,
CASE WHEN @pre_stage = deal_stage THEN @previous_rank := @previous_rank + 1 ELSE @previous_rank := 1
END AS rank,
@pre_stage := deal_stage
FROM
(
SELECT
tds.deal_id,
contact_emal AS email,
contact_phone as phone,
latest_activity_date AS activity_date,
latest_activity_status AS activity_status,
contact_name,
deal_tag_names,
tds.name ,
tds.organization_id,
contact_person_id,
deal_score,
marketing_campaign,
tds.business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
tds.owner_id,
tds.created_at,
tds.latest_activity_date
FROM
${tableName} tds ${followJoin} ${organisationJoin} ${tagsFilter}
WHERE
tds.business_owner_id = ? AND workflow_id = ?
`
var queryParams = [businessOwnerId, workflowId];
if (dealStage && dealStage.length) {
sql += ` AND tds.deal_stage_id IN (?) `;
queryParams.push(dealStage);
}
if (userFilters && userFilters.length) {
if(followJoin && role == constants.userRoles.TEAM_MEMBER){
if(getMemberAssignToDeals){
sql += ` AND ( ( tds.assign_to IN (?) AND tb_deal_follower.is_active = 1 AND tb_deal_follower.follower_id = ? ) OR tds.assign_to = ? ) `;
queryParams.push(userFilters);
queryParams.push(userId);
queryParams.push(userId);
}else{
sql += ` AND ( ( tds.assign_to IN (?) AND tb_deal_follower.is_active = 1 AND tb_deal_follower.follower_id = ? ) ) `;
queryParams.push(userFilters);
queryParams.push(userId);
}
}else{
sql += ` AND tds.assign_to IN (?) `;
queryParams.push(userFilters);
}
}
if (dealStatus && Array.isArray(dealStatus) && dealStatus.length) {
sql += ` AND tds.deal_status IN (?) `;
queryParams.push(dealStatus);
}
if (rangeFilter.start && rangeFilter.end) {
sql += ` AND tds.deal_created_at > ? AND tds.deal_created_at < ? `;
queryParams.push(rangeFilter.start, rangeFilter.end);
}
if (dealScore && Array.isArray(dealScore) && dealScore.length) {
sql += `AND tds.deal_score = ?`;
queryParams.push(dealScore)
}
if (followJoin && role != constants.userRoles.TEAM_MEMBER) {
sql += ` AND tb_deal_follower.is_active = 1 `;
}
if (organisationJoin) {
if (city) {
var cityText = ''
for (elements in city) {
cityText += `'${city[elements]}', `
}
cityText = cityText.slice(0, -2)
sql += ` AND tb_organizations.city IN (${cityText}) `
}
if (country) {
var countryText = ''
for (elements in country) {
countryText += `'${country[elements]}', `
}
countryText = countryText.slice(0, -2)
sql += ` AND tb_organizations.country IN (${countryText}) `
}
}
if (tags) {
sql += ` AND tb_deal_tag_mapping.tag_id IN (${tags}) AND tb_deal_tag_mapping.is_deleted = 0 `
}
sql += ` GROUP BY tds.deal_id
ORDER BY
deal_stage ASC,
tds.deal_id
DESC
`
//
sql += `) a
JOIN(
SELECT
@previous_rank := 1,
@pre_stage := 0
) b
) AS t `
if (pageSize) {
pageSize = startFrom + pageSize;
sql += ` WHERE
rank >= ${startFrom} AND rank <= ${pageSize} `
}
sql = sql.replace(/--.*(\n|$)/g, "").replace(/\s+/g, ' ');
对于特定情况,sql 查询是:
SELECT
deal_id,
email,
phone,
activity_date,
activity_status,
contact_name,
deal_tag_names,
name,
organization_id,
contact_person_id,
deal_score,
marketing_campaign,
business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
owner_id ,
created_at,
latest_activity_date
FROM
( SELECT
deal_id,
email,
phone,
activity_date,
activity_status,
contact_name,
deal_tag_names,
name,
organization_id,
contact_person_id,
deal_score,
marketing_campaign,
business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
owner_id,
created_at,
latest_activity_date,
CASE WHEN @pre_stage = deal_stage
THEN @previous_rank := @previous_rank + 1
ELSE @previous_rank := 1 END AS rank,
@pre_stage := deal_stage
FROM
( SELECT
tds.deal_id,
contact_emal AS email,
contact_phone as phone,
latest_activity_date AS activity_date,
latest_activity_status AS activity_status,
contact_name,
deal_tag_names,
tds.name ,
tds.organization_id,
contact_person_id,
deal_score,
marketing_campaign,
tds.business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
tds.owner_id,
tds.created_at,
tds.latest_activity_date
FROM
tb_deal_summary_v2 tds
WHERE
tds.business_owner_id = 'n6lqj9nayewl6osh2b82'
AND workflow_id = 'c110988e-6519-4d3f-a675-de083e59e9fd'
AND tds.deal_stage_id IN ('c110988e-6519-4d3f-a675-de083e59e9fd1','c110988e-6519-4d3f-a675-de083e59e9fd2')
AND tds.assign_to IN ('n6lqj9nayewl6osh2b82')
AND tds.deal_status IN (0)
GROUP BY
tds.deal_id
ORDER BY
deal_stage ASC,
tds.deal_id DESC ) a
JOIN
( SELECT
@previous_rank := 1,
@pre_stage := 0 ) b
) AS t
WHERE
rank >= 0
AND rank <= 20
IN 子句中可能有更多值,但在这种情况下,每个 IN 子句只有一个值。
一些观察及其结果:
- 需要1.5秒以上
- Output of explain statement
任何类型的建议都会很有帮助,
提前致谢。
编辑:此 sql 的目的是明智地对交易分页,例如,如果排名 >= 0 且排名 <= 20,则每个阶段最多有 20 笔交易,其余交易将在下一页中提取。
这是图片:
Rank wise pagination
如果交易阶段更改排名从 1 开始,如果它来自同一阶段,则排名将增加。
您的 Javascript 生成一个所谓的 frankenquery -- 一个具有许多可能的过滤器 (WHERE) 子句排列的查询。您查询的每个变体都有自己理想的复合索引。
您的 EXPLAIN 显示您已经在 business_owner_id
上建立了索引。我猜你的 Javascript 总是生成一个查询说 WHERE business_owner_id = someConstant
。所以这是一个很好的优化起点。如果事情仍然不够快,您可能想找出您的弗兰肯查询的哪些变体最慢或最常见或两者兼而有之。
对于您向我们展示的变体,此复合索引或其某些版本可能会有所帮助。
CREATE INDEX index_name ON tb_deal_summary_v2
(business_owner_id, deal_status, workflow_id, deal_stage_id, assign_to);
请注意,许多单列索引通常对数据库性能有害。除了主键和唯一性约束的索引,您的索引选择应基于常见的查询模式。
顺便说一下,我希望您的 deal_id
列是 table 的主键或在其上有唯一索引。否则,您就是在滥用 MySQL's notorious nonstandard extension to GROUP BY,并且您的查询生成的列值是不可预测的 table。不预测的table结果是没办法处理别人的钱
Marcus Winand 的 https://use-the-index-luke.com/ 在线书籍是一个很好的参考。
我只是 sql 的初学者,我正在尝试优化 sql 查询但还没有任何想法,所以如果有人帮助我,我会与你们分享查询通过这个出来将不胜感激。
表格已经编入索引,但如果您有任何复合索引方法或任何您可以共享的方法。
这是代码:
var viewDealsSQLV2 = function (apiReference, userId, workflowId, dealStatus, startFrom, pageSize, rangeFilter,
dealStage, role, businessOwnerId, dealScore, getFollowersData, userFilters, tags, country, city,getMemberAssignToDeals) {
return new Promise((resolve, reject) => {
var tableName = constants.tableName.TB_DEAL_SUMMARY
var limitFilter = "";
var tagsFilter = "";
var followFilter = ``,
followJoin = ``;
if ((role == constants.userRoles.TEAM_MEMBER) || ((role == constants.userRoles.ADMIN || role == constants.userRoles.BUSINESS_OWNER) && getFollowersData && Array.isArray(userFilters) && userFilters.length)) {
followJoin = ` LEFT JOIN tb_deal_follower ON tds.deal_id = tb_deal_follower.deal_id `;
}
var filters = ''
var organisationJoin = ''
if (tags) {
tagsFilter = ` LEFT JOIN tb_deal_tag_mapping ON tds.deal_id = tb_deal_tag_mapping.deal_id `
}
if (city || country) {
organisationJoin = ` LEFT JOIN tb_organizations ON tb_organizations.organization_id = tds.organization_id `
}
// console.log("+++++++",followJoin)
var sql = ` SELECT
deal_id,
email,
phone,
activity_date,
activity_status,
contact_name,
deal_tag_names,
name,
organization_id,
contact_person_id,
deal_score,
marketing_campaign,
business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
owner_id ,
created_at,
latest_activity_date
FROM
(
SELECT
deal_id,
email,
phone,
activity_date,
activity_status,
contact_name,
deal_tag_names,
name,
organization_id,
contact_person_id,
deal_score,
marketing_campaign,
business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
owner_id,
created_at,
latest_activity_date,
CASE WHEN @pre_stage = deal_stage THEN @previous_rank := @previous_rank + 1 ELSE @previous_rank := 1
END AS rank,
@pre_stage := deal_stage
FROM
(
SELECT
tds.deal_id,
contact_emal AS email,
contact_phone as phone,
latest_activity_date AS activity_date,
latest_activity_status AS activity_status,
contact_name,
deal_tag_names,
tds.name ,
tds.organization_id,
contact_person_id,
deal_score,
marketing_campaign,
tds.business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
tds.owner_id,
tds.created_at,
tds.latest_activity_date
FROM
${tableName} tds ${followJoin} ${organisationJoin} ${tagsFilter}
WHERE
tds.business_owner_id = ? AND workflow_id = ?
`
var queryParams = [businessOwnerId, workflowId];
if (dealStage && dealStage.length) {
sql += ` AND tds.deal_stage_id IN (?) `;
queryParams.push(dealStage);
}
if (userFilters && userFilters.length) {
if(followJoin && role == constants.userRoles.TEAM_MEMBER){
if(getMemberAssignToDeals){
sql += ` AND ( ( tds.assign_to IN (?) AND tb_deal_follower.is_active = 1 AND tb_deal_follower.follower_id = ? ) OR tds.assign_to = ? ) `;
queryParams.push(userFilters);
queryParams.push(userId);
queryParams.push(userId);
}else{
sql += ` AND ( ( tds.assign_to IN (?) AND tb_deal_follower.is_active = 1 AND tb_deal_follower.follower_id = ? ) ) `;
queryParams.push(userFilters);
queryParams.push(userId);
}
}else{
sql += ` AND tds.assign_to IN (?) `;
queryParams.push(userFilters);
}
}
if (dealStatus && Array.isArray(dealStatus) && dealStatus.length) {
sql += ` AND tds.deal_status IN (?) `;
queryParams.push(dealStatus);
}
if (rangeFilter.start && rangeFilter.end) {
sql += ` AND tds.deal_created_at > ? AND tds.deal_created_at < ? `;
queryParams.push(rangeFilter.start, rangeFilter.end);
}
if (dealScore && Array.isArray(dealScore) && dealScore.length) {
sql += `AND tds.deal_score = ?`;
queryParams.push(dealScore)
}
if (followJoin && role != constants.userRoles.TEAM_MEMBER) {
sql += ` AND tb_deal_follower.is_active = 1 `;
}
if (organisationJoin) {
if (city) {
var cityText = ''
for (elements in city) {
cityText += `'${city[elements]}', `
}
cityText = cityText.slice(0, -2)
sql += ` AND tb_organizations.city IN (${cityText}) `
}
if (country) {
var countryText = ''
for (elements in country) {
countryText += `'${country[elements]}', `
}
countryText = countryText.slice(0, -2)
sql += ` AND tb_organizations.country IN (${countryText}) `
}
}
if (tags) {
sql += ` AND tb_deal_tag_mapping.tag_id IN (${tags}) AND tb_deal_tag_mapping.is_deleted = 0 `
}
sql += ` GROUP BY tds.deal_id
ORDER BY
deal_stage ASC,
tds.deal_id
DESC
`
//
sql += `) a
JOIN(
SELECT
@previous_rank := 1,
@pre_stage := 0
) b
) AS t `
if (pageSize) {
pageSize = startFrom + pageSize;
sql += ` WHERE
rank >= ${startFrom} AND rank <= ${pageSize} `
}
sql = sql.replace(/--.*(\n|$)/g, "").replace(/\s+/g, ' ');
对于特定情况,sql 查询是:
SELECT
deal_id,
email,
phone,
activity_date,
activity_status,
contact_name,
deal_tag_names,
name,
organization_id,
contact_person_id,
deal_score,
marketing_campaign,
business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
owner_id ,
created_at,
latest_activity_date
FROM
( SELECT
deal_id,
email,
phone,
activity_date,
activity_status,
contact_name,
deal_tag_names,
name,
organization_id,
contact_person_id,
deal_score,
marketing_campaign,
business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
owner_id,
created_at,
latest_activity_date,
CASE WHEN @pre_stage = deal_stage
THEN @previous_rank := @previous_rank + 1
ELSE @previous_rank := 1 END AS rank,
@pre_stage := deal_stage
FROM
( SELECT
tds.deal_id,
contact_emal AS email,
contact_phone as phone,
latest_activity_date AS activity_date,
latest_activity_status AS activity_status,
contact_name,
deal_tag_names,
tds.name ,
tds.organization_id,
contact_person_id,
deal_score,
marketing_campaign,
tds.business_owner_id,
deal_stage,
deal_stage_id,
estimated_worth,
deal_status,
deal_color,
deal_order,
currency_id,
tds.owner_id,
tds.created_at,
tds.latest_activity_date
FROM
tb_deal_summary_v2 tds
WHERE
tds.business_owner_id = 'n6lqj9nayewl6osh2b82'
AND workflow_id = 'c110988e-6519-4d3f-a675-de083e59e9fd'
AND tds.deal_stage_id IN ('c110988e-6519-4d3f-a675-de083e59e9fd1','c110988e-6519-4d3f-a675-de083e59e9fd2')
AND tds.assign_to IN ('n6lqj9nayewl6osh2b82')
AND tds.deal_status IN (0)
GROUP BY
tds.deal_id
ORDER BY
deal_stage ASC,
tds.deal_id DESC ) a
JOIN
( SELECT
@previous_rank := 1,
@pre_stage := 0 ) b
) AS t
WHERE
rank >= 0
AND rank <= 20
IN 子句中可能有更多值,但在这种情况下,每个 IN 子句只有一个值。
一些观察及其结果:
- 需要1.5秒以上
- Output of explain statement
任何类型的建议都会很有帮助, 提前致谢。
编辑:此 sql 的目的是明智地对交易分页,例如,如果排名 >= 0 且排名 <= 20,则每个阶段最多有 20 笔交易,其余交易将在下一页中提取。
这是图片:
Rank wise pagination
如果交易阶段更改排名从 1 开始,如果它来自同一阶段,则排名将增加。
您的 Javascript 生成一个所谓的 frankenquery -- 一个具有许多可能的过滤器 (WHERE) 子句排列的查询。您查询的每个变体都有自己理想的复合索引。
您的 EXPLAIN 显示您已经在 business_owner_id
上建立了索引。我猜你的 Javascript 总是生成一个查询说 WHERE business_owner_id = someConstant
。所以这是一个很好的优化起点。如果事情仍然不够快,您可能想找出您的弗兰肯查询的哪些变体最慢或最常见或两者兼而有之。
对于您向我们展示的变体,此复合索引或其某些版本可能会有所帮助。
CREATE INDEX index_name ON tb_deal_summary_v2
(business_owner_id, deal_status, workflow_id, deal_stage_id, assign_to);
请注意,许多单列索引通常对数据库性能有害。除了主键和唯一性约束的索引,您的索引选择应基于常见的查询模式。
顺便说一下,我希望您的 deal_id
列是 table 的主键或在其上有唯一索引。否则,您就是在滥用 MySQL's notorious nonstandard extension to GROUP BY,并且您的查询生成的列值是不可预测的 table。不预测的table结果是没办法处理别人的钱
Marcus Winand 的 https://use-the-index-luke.com/ 在线书籍是一个很好的参考。