powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / LEFT JOIN и IS NULL
10 сообщений из 10, страница 1 из 1
LEFT JOIN и IS NULL
    #39018386
Nitro_Junkie
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Работаю с таким запросом (как обычно кусок, то есть смысла в нем не много):

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
EXPLAIN (ANALYZE, VERBOSE, BUFFERS, COSTS)
  (SELECT t0.key0 AS jkey0,
          t0.ZReport_dateReceiptDetail_ReceiptDetail AS jprop0,
          t0.key0 AS jprop1,
          CASE
              WHEN t1.System__CLASS_ZReport_receiptReturnDetail=40 THEN t1.System__CLASS_ZReport_receiptReturnDetail
              ELSE NULL
          END AS jprop2
   FROM ZReport_receiptDetail t0
   LEFT JOIN ZReport_receiptReturnDetail t1 ON t1.key0=t0.key0
   LEFT JOIN t_52 t6 ON t6.k0=t0.key0
   WHERE (t0.ZReport_departmentStoreReceiptDetail_ReceiptDetail=287879
          AND (CAST('2015-07-04 +03:00:00' AS date)>=t0.ZReport_dateReceiptDetail_ReceiptDetail)
          AND (t0.ZReport_dateReceiptDetail_ReceiptDetail>=CAST('2015-07-01 +03:00:00' AS date))
          AND (t6.p0 IS NULL)
          )
   )



Этот запрос выполняется аж 35 секунд. План следующий:

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
"Nested Loop Left Join  (cost=0.74..138648.25 rows=1 width=12) (actual time=204.376..34842.256 rows=95011 loops=1)"
"  Output: t0.key0, t0.zreport_datereceiptdetail_receiptdetail, t0.key0, CASE WHEN (t1.system__class_zreport_receiptreturndetail = 40) THEN t1.system__class_zreport_receiptreturndetail ELSE NULL::integer END"
"  Join Filter: (t1.key0 = t0.key0)"
"  Rows Removed by Join Filter: 408167233"
"  Buffers: shared hit=6321534 read=51736"
"  ->  Hash Left Join  (cost=0.74..138202.31 rows=1 (!) width=8) (actual time=203.486..882.099 rows=95011 loops=1)"
"        Output: t0.key0, t0.zreport_datereceiptdetail_receiptdetail"
"        Hash Cond: (t0.key0 = t6.k0)"
"        Filter: (t6.p0 IS NULL)"
"        Rows Removed by Filter: 2"
"        Buffers: shared hit=1000974 read=51680"
"        ->  Index Scan using zreport_datereceiptdetail_receiptdetail_key0_idx_zreport_receip on public.zreport_receiptdetail t0  (cost=0.44..138111.38 rows=24153 width=8) (actual time=203.432..858.052 rows=95013 loops=1)"
"              Output: t0.key0, t0.zreport_idreceiptdetail_receiptdetail, t0.zreport_receiptreceiptdetail_receiptdetail, t0.zreport_signedsumreceiptdetail_receiptdetail, t0.zreport_discountsumreceiptdetail_receiptdetail, t0.zreport_vatreceiptdetail_receiptd (...)"
"              Index Cond: (('2015-07-04'::date >= t0.zreport_datereceiptdetail_receiptdetail) AND (t0.zreport_datereceiptdetail_receiptdetail >= '2015-07-01'::date))"
"              Filter: (t0.zreport_departmentstorereceiptdetail_receiptdetail = 287879)"
"              Rows Removed by Filter: 1271341"
"              Buffers: shared hit=1000974 read=51679"
"        ->  Hash  (cost=0.20..0.20 rows=2 width=8) (actual time=0.022..0.022 rows=2 loops=1)"
"              Output: t6.k0, t6.p0"
"              Buckets: 1024  Batches: 1  Memory Usage: 1kB"
"              Buffers: shared read=1"
"              ->  Seq Scan on public.t_52 t6  (cost=0.00..0.20 rows=2 width=8) (actual time=0.017..0.018 rows=2 loops=1)"
"                    Output: t6.k0, t6.p0"
"                    Buffers: shared read=1"
"  ->  Seq Scan on public.zreport_receiptreturndetail t1  (cost=0.00..220.40 rows=4296 width=8) (actual time=0.001..0.158 rows=4296 loops=95011)"
"        Output: t1.key0, t1.zreport_receiptreceiptreturndetail_receiptreturndetail, t1.zreport_quantityreceiptreturndetail_receiptreturndetail, t1.zreport_batchreceiptreturndetail_receiptreturndetail, t1.zreport_skureceiptreturndetail_receiptreturndetail,  (...)"
"        Buffers: shared hit=5320560 read=56"
"Planning time: 0.329 ms"
"Execution time: 34846.358 ms"



Как видно из плана проблема вот в чем, когда Postgres видит A LEFT JOIN B WHERE B.field IS NULL, он при определении selectivity предиката B.field IS NULL, читает его из просто B.field, и не учитывает тот факт, что в A может быть очень много строк, в B очень мало строк, поэтому в результате LEFT JOIN'а почти все записи будут NULL и selectivity на самом деле будет гораздо выше. Соответственно единственный способ с этим бороться, который я пока вижу, это смотреть что если у нас не INNER JOIN + статистика JOIN'а поля с проверкой на NULL значительно меньше общей статистики (без этой проверки можно получить обратный эффект если B большое - в этом случае postgres правильно считает что selectivity низкое), добавлять к предикату B.field IS NULL что-то типа current_timestamp<>current_timestamp.

Тогда план будет нормальным:

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
"Hash Left Join  (cost=446.68..138888.83 rows=24032 width=12) (actual time=206.607..718.234 rows=95011 loops=1)"
"  Output: t0.key0, t0.zreport_datereceiptdetail_receiptdetail, t0.key0, CASE WHEN (t1.system__class_zreport_receiptreturndetail = 40) THEN t1.system__class_zreport_receiptreturndetail ELSE NULL::integer END"
"  Hash Cond: (t0.key0 = t1.key0)"
"  Buffers: shared hit=1001057 read=51653 written=1"
"  ->  Hash Left Join  (cost=0.74..138202.32 rows=24032 width=8) (actual time=205.697..698.073 rows=95011 loops=1)"
"        Output: t0.key0, t0.zreport_datereceiptdetail_receiptdetail"
"        Hash Cond: (t0.key0 = t6.k0)"
"        Filter: ((t6.p0 IS NULL) OR (now() <> now()))"
"        Rows Removed by Filter: 2"
"        Buffers: shared hit=1001001 read=51653 written=1"
"        ->  Index Scan using zreport_datereceiptdetail_receiptdetail_key0_idx_zreport_receip on public.zreport_receiptdetail t0  (cost=0.44..138111.38 rows=24153 width=8) (actual time=205.673..688.592 rows=95013 loops=1)"
"              Output: t0.key0, t0.zreport_idreceiptdetail_receiptdetail, t0.zreport_receiptreceiptdetail_receiptdetail, t0.zreport_signedsumreceiptdetail_receiptdetail, t0.zreport_discountsumreceiptdetail_receiptdetail, t0.zreport_vatreceiptdetail_receiptd (...)"
"              Index Cond: (('2015-07-04'::date >= t0.zreport_datereceiptdetail_receiptdetail) AND (t0.zreport_datereceiptdetail_receiptdetail >= '2015-07-01'::date))"
"              Filter: (t0.zreport_departmentstorereceiptdetail_receiptdetail = 287879)"
"              Rows Removed by Filter: 1271341"
"              Buffers: shared hit=1001001 read=51652 written=1"
"        ->  Hash  (cost=0.20..0.20 rows=2 width=8) (actual time=0.010..0.010 rows=2 loops=1)"
"              Output: t6.k0, t6.p0"
"              Buckets: 1024  Batches: 1  Memory Usage: 1kB"
"              Buffers: shared read=1"
"              ->  Seq Scan on public.t_52 t6  (cost=0.00..0.20 rows=2 width=8) (actual time=0.008..0.009 rows=2 loops=1)"
"                    Output: t6.k0, t6.p0"
"                    Buffers: shared read=1"
"  ->  Hash  (cost=220.40..220.40 rows=4296 width=8) (actual time=0.904..0.904 rows=4296 loops=1)"
"        Output: t1.system__class_zreport_receiptreturndetail, t1.key0"
"        Buckets: 1024  Batches: 1  Memory Usage: 168kB"
"        Buffers: shared hit=56"
"        ->  Seq Scan on public.zreport_receiptreturndetail t1  (cost=0.00..220.40 rows=4296 width=8) (actual time=0.004..0.534 rows=4296 loops=1)"
"              Output: t1.system__class_zreport_receiptreturndetail, t1.key0"
"              Buffers: shared hit=56"
"Planning time: 0.358 ms"
"Execution time: 720.657 ms"



Но тут соответственно 2 вопроса :
a) Может есть более простой способ забороть проблему
б) Какой еще проверкой можно значительно повысить selectivity кроме как OR current_timestamp<>current_timestamp? OR 1<>1 и аналогичные уберет оптимизатор. А current_timestamp как мне кажется может overhead в крайних случаях давать.
...
Рейтинг: 0 / 0
LEFT JOIN и IS NULL
    #39018398
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Nitro_Junkie,

a)для начала всесто left join + IS NULL попробуйте использовать
Код: plaintext
NOT exists (select * from  t_52 t6 ON t6.k0=t0.key0)

б)ну можно например
Код: plaintext
OR abs(t0.key0)<0
сделать

--
Maxim Boguk
www.postgresql-consulting.ru
...
Рейтинг: 0 / 0
LEFT JOIN и IS NULL
    #39018411
Nitro_Junkie
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Maxim Boguk,

Не, так, если t6.p0 IS NULL на t6.k0 IS NULL поменять, то она на ANTI JOIN переходит:

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
"Limit  (cost=0.72..622.82 rows=100 width=12) (actual time=132.309..132.483 rows=100 loops=1)"
"  Output: t0.key0, t0.zreport_datereceiptdetail_receiptdetail, t0.key0, (CASE WHEN (t1.system__class_zreport_receiptreturndetail = 40) THEN t1.system__class_zreport_receiptreturndetail ELSE NULL::integer END)"
"  Buffers: shared hit=279054 read=2957"
"  ->  Nested Loop Anti Join  (cost=0.72..150255.47 rows=24153 width=12) (actual time=132.308..132.477 rows=100 loops=1)"
"        Output: t0.key0, t0.zreport_datereceiptdetail_receiptdetail, t0.key0, CASE WHEN (t1.system__class_zreport_receiptreturndetail = 40) THEN t1.system__class_zreport_receiptreturndetail ELSE NULL::integer END"
"        Join Filter: (t6.k0 = t0.key0)"
"        Rows Removed by Join Filter: 200"
"        Buffers: shared hit=279054 read=2957"
"        ->  Nested Loop Left Join  (cost=0.72..147538.05 rows=24153 width=12) (actual time=132.298..132.429 rows=100 loops=1)"
"              Output: t0.key0, t0.zreport_datereceiptdetail_receiptdetail, t1.system__class_zreport_receiptreturndetail"
"              Buffers: shared hit=279053 read=2957"
"              ->  Index Scan using zreport_datereceiptdetail_receiptdetail_key0_idx_zreport_receip on public.zreport_receiptdetail t0  (cost=0.44..138111.38 rows=24153 width=8) (actual time=132.288..132.343 rows=100 loops=1)"
"                    Output: t0.key0, t0.zreport_idreceiptdetail_receiptdetail, t0.zreport_receiptreceiptdetail_receiptdetail, t0.zreport_signedsumreceiptdetail_receiptdetail, t0.zreport_discountsumreceiptdetail_receiptdetail, t0.zreport_vatreceiptdetail_re (...)"
"                    Index Cond: (('2015-07-04'::date >= t0.zreport_datereceiptdetail_receiptdetail) AND (t0.zreport_datereceiptdetail_receiptdetail >= '2015-07-01'::date))"
"                    Filter: (t0.zreport_departmentstorereceiptdetail_receiptdetail = 287879)"
"                    Rows Removed by Filter: 303980"
"                    Buffers: shared hit=278853 read=2957"
"              ->  Index Scan using zreport_receiptreturndetail_pkey on public.zreport_receiptreturndetail t1  (cost=0.28..0.34 rows=1 width=8) (actual time=0.001..0.001 rows=0 loops=100)"
"                    Output: t1.key0, t1.zreport_receiptreceiptreturndetail_receiptreturndetail, t1.zreport_quantityreceiptreturndetail_receiptreturndetail, t1.zreport_batchreceiptreturndetail_receiptreturndetail, t1.zreport_skureceiptreturndetail_receiptre (...)"
"                    Index Cond: (t1.key0 = t0.key0)"
"                    Buffers: shared hit=200"
"        ->  Materialize  (cost=0.00..0.21 rows=2 width=4) (actual time=0.000..0.000 rows=2 loops=100)"
"              Output: t6.k0"
"              Buffers: shared hit=1"
"              ->  Seq Scan on public.t_52 t6  (cost=0.00..0.20 rows=2 width=4) (actual time=0.002..0.002 rows=2 loops=1)"
"                    Output: t6.k0"
"                    Buffers: shared hit=1"
"Planning time: 0.369 ms"
"Execution time: 132.530 ms"



Но мне то надо и те, которые есть в t_52, но у которых p0 IS NULL.

Понятно что можно разбить на 2 подзапроса (один с k0 IS NULL, а второй с INNER JOIN t_52 и p0 IS NULL) и их UNION'ить. Но это может быть еще больший оверхед.

Что касается abs() < 0, хотелось бы что-то не использующее данные таблиц. Но если что можно и так сделать.
...
Рейтинг: 0 / 0
LEFT JOIN и IS NULL
    #39018414
Nitro_Junkie
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Собственно насколько я читал, not exists и left join с IS NULL начиная с 8.4 выполняются практически одинаково.
...
Рейтинг: 0 / 0
LEFT JOIN и IS NULL
    #39018478
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Nitro_JunkieMaxim Boguk,
Не, так, если t6.p0 IS NULL на t6.k0 IS NULL поменять, то она на ANTI JOIN переходит:
Но мне то надо и те, которые есть в t_52, но у которых p0 IS NULL.


пардон не внимательно прочел запрос... вот так вот тогда можно попробовать
Код: plaintext
NOT exists (select * from  t_52 t6 ON t6.k0=t0.key0 and t6.p0 IS NOT NULL)
надеюсь что я с множественными отрицаниями ничего не напутал.

На всякий случай вопрос - t6.k0 оно вообще уникальное?
Так как если нет то при наличии нескольких одинаковых строк с одинаковыми t6.k0 + t6.p0 IS NULL
вы получите дубли в результате вашего запроса.

--
Maxim Boguk
www.postgresql-consulting.ru
...
Рейтинг: 0 / 0
LEFT JOIN и IS NULL
    #39018558
Nitro_Junkie
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Maxim BogukNitro_JunkieMaxim Boguk,
Не, так, если t6.p0 IS NULL на t6.k0 IS NULL поменять, то она на ANTI JOIN переходит:
Но мне то надо и те, которые есть в t_52, но у которых p0 IS NULL.


пардон не внимательно прочел запрос... вот так вот тогда можно попробовать
Код: plaintext
NOT exists (select * from  t_52 t6 ON t6.k0=t0.key0 and t6.p0 IS NOT NULL)
надеюсь что я с множественными отрицаниями ничего не напутал.

На всякий случай вопрос - t6.k0 оно вообще уникальное?
Так как если нет то при наличии нескольких одинаковых строк с одинаковыми t6.k0 + t6.p0 IS NULL
вы получите дубли в результате вашего запроса.

--
Maxim Boguk
www.postgresql-consulting.ru

Так естественно тоже помогает.

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
"Hash Anti Join  (cost=446.68..140070.46 rows=24153 width=12) (actual time=169.563..645.825 rows=95011 loops=1)"
"  Output: t0.key0, t0.zreport_datereceiptdetail_receiptdetail, t0.key0, CASE WHEN (t1.system__class_zreport_receiptreturndetail = 40) THEN t1.system__class_zreport_receiptreturndetail ELSE NULL::integer END"
"  Hash Cond: (t0.key0 = t6.k0)"
"  Buffers: shared hit=1001015 read=51695 written=1"
"  ->  Hash Left Join  (cost=446.38..138738.72 rows=24153 width=12) (actual time=169.522..635.621 rows=95013 loops=1)"
"        Output: t0.key0, t0.zreport_datereceiptdetail_receiptdetail, t1.system__class_zreport_receiptreturndetail"
"        Hash Cond: (t0.key0 = t1.key0)"
"        Buffers: shared hit=1001015 read=51694 written=1"
"        ->  Index Scan using zreport_datereceiptdetail_receiptdetail_key0_idx_zreport_receip on public.zreport_receiptdetail t0  (cost=0.44..138111.38 rows=24153 width=8) (actual time=168.401..619.469 rows=95013 loops=1)"
"              Output: t0.key0, t0.zreport_idreceiptdetail_receiptdetail, t0.zreport_receiptreceiptdetail_receiptdetail, t0.zreport_signedsumreceiptdetail_receiptdetail, t0.zreport_discountsumreceiptdetail_receiptdetail, t0.zreport_vatreceiptdetail_receiptd (...)"
"              Index Cond: (('2015-07-04'::date >= t0.zreport_datereceiptdetail_receiptdetail) AND (t0.zreport_datereceiptdetail_receiptdetail >= '2015-07-01'::date))"
"              Filter: (t0.zreport_departmentstorereceiptdetail_receiptdetail = 287879)"
"              Rows Removed by Filter: 1271341"
"              Buffers: shared hit=1001015 read=51638 written=1"
"        ->  Hash  (cost=220.40..220.40 rows=4296 width=8) (actual time=1.100..1.100 rows=4296 loops=1)"
"              Output: t1.system__class_zreport_receiptreturndetail, t1.key0"
"              Buckets: 1024  Batches: 1  Memory Usage: 168kB"
"              Buffers: shared read=56"
"              ->  Seq Scan on public.zreport_receiptreturndetail t1  (cost=0.00..220.40 rows=4296 width=8) (actual time=0.009..0.650 rows=4296 loops=1)"
"                    Output: t1.system__class_zreport_receiptreturndetail, t1.key0"
"                    Buffers: shared read=56"
"  ->  Hash  (cost=0.20..0.20 rows=2 width=4) (actual time=0.008..0.008 rows=2 loops=1)"
"        Output: t6.k0"
"        Buckets: 1024  Batches: 1  Memory Usage: 1kB"
"        Buffers: shared read=1"
"        ->  Seq Scan on public.t_52 t6  (cost=0.00..0.20 rows=2 width=4) (actual time=0.007..0.008 rows=2 loops=1)"
"              Output: t6.k0"
"              Filter: (t6.p0 IS NOT NULL)"
"              Buffers: shared read=1"
"Planning time: 0.338 ms"
"Execution time: 648.221 ms"



Но это уже ручное управление планом запроса. Хотелось бы точечно на предикат воздействовать, потому как если из t_52 нужно еще что-то считать, нужно получается еще раз join'ить, а если при этом там "каскадный" join через 3 таблицы....
...
Рейтинг: 0 / 0
LEFT JOIN и IS NULL
    #39018642
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Nitro_Junkie,

LEFT JOIN WHERE IS NULL эквивалентен NOT EXISTS в одном и только в одном случае...
если это
LEFT JOIN t_52 t6 ON t6.k0=t0.key0
...
WHERE t6.k0 IS NULL

У вас же WHERE IS NULL условие совсем другое (и как я уже написал not exists и LEFT JOIN могут давать разное количество строк результата если есть дубли по полю t6.k0).
Поэтому и планы разные так как запросы разные по сути.

Для поднятия селективности LEFT JOIN условия с т.з. оптимизатора можно например прикрутить
LEFT JOIN t_52 t6 ON t6.k0=t0.key0 AND random()>=0
например... ну или еще какую функцию подобную (селективность у всех одинакова), главное чтобы она в константу не фолдилась.


--
Maxim Boguk
www.postgresql-consulting.ru
...
Рейтинг: 0 / 0
LEFT JOIN и IS NULL
    #39019037
Alexius
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
касательно selectivity:

зная вот эти значения

Код: plaintext
1.
2.
3.
4.
5.
/src/include/utils/selfuncs.h

/* default selectivity estimate for equalities such as "A = b" */
#define DEFAULT_EQ_SEL 0.005
/* default selectivity estimate for inequalities such as "A < b" */
#define DEFAULT_INEQ_SEL 0.3333333333333333

набор каких-то заведомо истинных/ложных условий типа abs(t0.key0)<0, random() = -1

и формулы, по которым вычисляется selectivity and/or двух условий (для and - как произведение selectivity отдельных частей, для or - как сумма минус произведение)

можно в принципе извратиться и захардкодить нужное значение если очень надо.
...
Рейтинг: 0 / 0
LEFT JOIN и IS NULL
    #39021106
Ivan Durak
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
авторSeq Scan on public.t_52 t6 (cost=0.00..0.20 rows=2 width=8)
статистика по количеству записей - того. Тухлая. Там же не 2 ровса?????
...
Рейтинг: 0 / 0
LEFT JOIN и IS NULL
    #39021111
Фотография Maxim Boguk
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ivan DurakавторSeq Scan on public.t_52 t6 (cost=0.00..0.20 rows=2 width=8)
статистика по количеству записей - того. Тухлая. Там же не 2 ровса?????

Судя по
Код: plaintext
1.
2.
3.
4.
        ->  Seq Scan on public.t_52 t6  (cost=0.00..0.20 rows=2 width=4) (actual time=0.007..0.008 rows=2 loops=1)
              Output: t6.k0
              Filter: (t6.p0 IS NOT NULL)
              Buffers: shared read=1
- именно два. Так что с статистикой все нормально.

PS: При пустой статистике по умолчанию считается 1000 в таблице.

--
Maxim Boguk
www.postgresql-consulting.ru
...
Рейтинг: 0 / 0
10 сообщений из 10, страница 1 из 1
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / LEFT JOIN и IS NULL
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]