Я запустил аналогичный запрос со следующей настройкой:
CREATE TABLE person (id1, id2, NAME) AS
SELECT to_char(mod(ROWNUM, 1000), 'fm0000'), ROWNUM,
dbms_random.string('A',10)
FROM dual
CONNECT BY LEVEL <= 1e6;
CREATE TABLE nameindex (id1, id2, phonetic) AS
SELECT id1, id2, to_char(dbms_random.value(1, 200), 'fm000')
FROM person;
Я обнаружил, что ваш первый запрос дает следующий план:
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 291343677
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2040 | 331K (2)| 01:06:2
| 1 | HASH UNIQUE | | 1 | 2040 | 331K (2)| 01:06:2
|* 2 | FILTER | | | | |
|* 3 | HASH JOIN | | 891 | 1775K| 1750 (2)| 00:00:2
|* 4 | TABLE ACCESS FULL | NAMEINDEX | 892 | 18732 | 739 (2)| 00:00:0
|* 5 | TABLE ACCESS FULL | PERSON | 1395 | 2750K| 1010 (2)| 00:00:1
|* 6 | FILTER | | | | |
| 7 | HASH GROUP BY | | 9550 | 76400 | 740 (2)| 00:00:0
|* 8 | TABLE ACCESS FULL| NAMEINDEX | 9550 | 76400 | 739 (2)| 00:00:0
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter( EXISTS (SELECT 0 FROM "NAMEINDEX" "N2" WHERE "N2"."ID1"=:B1
GROUP BY "N2"."PHONETIC" HAVING "N2"."PHONETIC"=:B2 AND COUNT(*)>=
COUNT(*)<=500))
3 - access("PERSON"."ID2"="N"."ID2" AND "PERSON"."ID1"="N"."ID1")
4 - filter("N"."ID1"='0812')
5 - filter("PERSON"."ID1"='0812')
6 - filter("N2"."PHONETIC"=:B1 AND COUNT(*)>=4 AND COUNT(*)<=500)
8 - filter("N2"."ID1"=:B1)
Как видите, полусоединение IN переписано как EXISTS, который создает тот же план, что и этот запрос:
SELECT DISTINCT NAME
FROM person, nameindex n
WHERE person.id1 = '0812'
AND person.id2 = n.id2
AND person.id1 = n.id1
AND EXISTS (SELECT NULL
FROM nameindex n2
WHERE n2.id1 = person.id1
AND n2.phonetic = n.phonetic
GROUP BY n2.phonetic
HAVING COUNT(*) BETWEEN 4 AND 500);
Здесь вы видите, что подзапрос НЕ является константой и поэтому вычисляется для каждой строки основного запроса, что приводит к неоптимальному плану выполнения.
Я предлагаю вам использовать все столбцы соединения в GROUP BY при использовании агрегированного полусоединения. Следующий запрос дает оптимальный план:
SELECT DISTINCT NAME
FROM person, nameindex n
WHERE person.id1 = '0812'
AND person.id2 = n.id2
AND person.id1 = n.id1
AND (n.id1, n.phonetic) IN (SELECT n2.id1, n2.phonetic
FROM nameindex n2
GROUP BY n2.id1, n2.phonetic
HAVING COUNT(*) BETWEEN 4 AND 500);