【发布时间】:2022-08-15 03:05:59
【问题描述】:
更新 - 此问题的最终解决方案
我们的动态系统允许对姓名、职位、电话号码等内容进行布尔插值匹配。所以我们可以说:
姓名(\"ted\" OR \"mike\" OR \"david\" AND \"martin\") AND Title(\"developer\" AND \"senior\" NOT \"CTO) AND City(\"圣地亚哥\”)
完成此操作的方法是遵循以下动态创建的分组示例。它非常简单,但是必须使用 HAVING COUNT 才能正确定义 AND 索引。
在此示例中,access_indexes 也不是帐户有权访问的 ID 索引列表,因此如果“搜索”返回帐户无法访问的人,则不会显示。
感谢大家的帮助,尤其是@BillKarwin!
WITH filter0 AS
(
SELECT pm.ID FROM person_main pm
WHERE MATCH(pm.name_full) AGAINST (\'(ted)\' IN BOOLEAN MODE)
),
filter1 AS
(
SELECT ram.object_ref_id AS ID
FROM ras_assignment_main ram
WHERE ram.object_type_c = 1
AND ram.assignment_type_c = 1
AND ram.assignment_ref_id IN (2)
),
persongroup0_and AS
(
SELECT pg0_a.ID FROM
(
SELECT ID FROM filter0
) pg0_a
GROUP BY pg0_a.ID
HAVING COUNT(pg0_a.ID) = 1
),
persongroup0 AS
(
SELECT pm.ID
FROM person_main pm
JOIN persongroup0_and pg0_and ON pm.ID = pg0_and.ID
),
persongroup1_and AS
(
SELECT pg1_a.ID FROM
(
SELECT ID FROM filter1
) pg1_a
GROUP BY pg1_a.ID
HAVING COUNT(pg1_a.ID) = 1
),
persongroup1 AS
(
SELECT pm.ID
FROM person_main pm
JOIN persongroup1_and pg1_and ON pm.ID = pg1_and.ID
),
person_all_and AS
(
SELECT paa.ID FROM
(
SELECT ID FROM persongroup0
UNION ALL (SELECT ID FROM persongroup1)
) paa
GROUP BY paa.ID
HAVING COUNT(paa.ID) = 2
),
person_all AS
(
SELECT pm.ID
FROM person_main pm
JOIN person_all_and pa_and ON pm.ID = pa_and.ID
),
person_access AS
(
SELECT pa.ID
FROM person_all pa
LEFT JOIN access_indexes ai ON pa.ID = ai.ID
)
SELECT (JSON_ARRAYAGG(pm.ID))
FROM
(
SELECT person_sort.ID
FROM
(
SELECT pa.ID
FROM person_access pa
GROUP BY pa.ID
) person_sort
) pm;
我们的前端系统能够使用 AND/OR/NOT 从多个表中定义动态 SQL 查询,并且核心系统工作正常 - 但由于对 IN 的复合扫描,它变得不可用。对于我的生活,我无法弄清楚如何在不使用 IN 的情况下拥有这种级别的动态功能。下面是运行良好的代码(过滤器匹配速度超快),但 IN 扫描的复合需要 > 60 秒,因为它有 50,000 多条记录用于某些过滤器返回。
WITH filter0 AS
(
SELECT pm.ID FROM person_main pm
WHERE MATCH(pm.name_full) AGAINST (\'mike meyers\' IN BOOLEAN MODE)
),
filter1 AS
(
SELECT phw.person_main_ref_id AS ID
FROM person_history_work phw
WHERE MATCH(phw.work_title) AGAINST(\'developer\' IN BOOLEAN MODE)
),
filter2 AS
(
SELECT pa.person_main_ref_id AS ID
FROM person_address pa
WHERE pa.global_address_details_ref_id IN
(
SELECT gad.ID
FROM global_address_details gad
WHERE gad.address_city LIKE \'%seattle%\'
)
),
all_indexes AS
(
SELECT ID FROM filter0
UNION (SELECT ID FROM filter1)
UNION (SELECT ID FROM filter2)
),
person_filter AS
(
SELECT ai.ID
FROM all_indexes ai
WHERE
(
ai.ID IN (SELECT ID FROM filter0)
AND ai.ID NOT IN (SELECT ID FROM filter1)
OR ai.ID IN (SELECT ID FROM filter2)
)
)
SELECT (JSON_ARRAYAGG(pf.ID)) FROM person_filter pf;
过滤器 0 有 461 条记录,过滤器 1 有 48480 条,过滤器 2 有 750 条。
关键问题在于 WHERE 语句;因为前端可以在任何“加入”查询中说 AND/OR 而不是。
因此,如果我将其更改为:
ai.ID IN (SELECT ID FROM filter0)
AND ai.ID IN (SELECT ID FROM filter1)
AND ai.ID IN (SELECT ID FROM filter2)
查询时间超过 60 秒。因为它正在扫描 461 * 48480 * 750 = 16,761,960,00。啊。
当然,如果它是静态存储过程或调用,我可以对此进行硬编码,但它是一个动态插值系统,采用用户定义的设置,因此用户可以定义上述内容。
如您所见,我所做的是创建所有涉及的索引的列表,然后根据前端 Web 工具定义的 AND/OR/NOT 值选择它们。
显然 IN 不会为此工作;问题是我可以使用哪些其他不涉及使用 IN 的技术来允许与 AND/OR/NOT 具有相同级别的灵活性?
评论中@BillKarwin 的更新
所以下面的代码非常适合执行 AND、NOT 和 OR:
SELECT pm.ID
FROM person_main pm
JOIN filter0 f0 ON f0.ID = pm.ID -- AND
LEFT JOIN filter1 f1 ON pm.ID = f1.ID WHERE f1.ID IS NULL -- NOT
UNION (SELECT ID FROM filter2) -- OR
我相信我可以使用我们的系统完成这项工作;我只需要存储不同的类型(AND/NOT/OR)并在进程中执行它们;让我做一些更新,我会回复你。
-
很难说,因为我们不知道您的用户定义条件的可能性范围是多少。但总的来说,我建议使用
JOIN作为实现AND条件的一种方式。然后您可以将 UNION 用于OR条件。 -
但归根结底,优化用户提出的所有可能的动态查询实际上是不可能的。
-
@BillKarwin 是的,但是不呢?那是核心问题;我希望我们有办法从单个 ID 的 UNION 中取消值(我认为 MSSQL 有 EXCEPT 或其他东西),但我看不到实现它的动态方法。
-
正确,MySQL 支持 UNION,但 does not yet support the other operations EXCEPT or INTERSECT。除了使用外连接外,还有其他方法可以模拟。该技术称为排除连接。示例:urgenthomework.com/sql-left-excluding-join-homework-help
-
@BillKarwin 明白了,谢谢 - 确实有效 - 但正如你上面提到的,将所有动态组合与 AND/OR/NOT 匹配似乎是不可能的 - 特别是对于嵌套组并且没有非常具体的限制。例如,如果他们执行 AND/NOT/OR 单独处理,则无法按 UNION、JOIN 或 LEFT JOIN(除外)按接收顺序进行处理;必须编写一种解释器才能正确排序查询,并且分组会造成管理混乱。好奇您建议对前端查询引擎施加哪些限制?
标签: mysql query-optimization dynamic-sql