Задать вопрос
@VlLight

Почему с фильтром PostgreSQL выдаёт больше записей?

Добрый день. БД PostgreSQL, есть примерно такая табличка appointments, в которой перечислены посещения клиентов:
═════════╦════════╦══════╦══════╦═════╦════╦═════
appointment_id║ customer_id ║ time_start║ invoice_id║ filial_id ║ status║ in_trash
═════════╩════════╩══════╩══════╩═════╩════╩═════
Есть задачка - найти всех новых (пришедших в этом году) клиентов. Ищу таким запросом:
SELECT apps.customer_id  FROM appointments apps 
	WHERE apps.in_trash is not true and apps.status = 4 and apps.filial_id = 1
GROUP BY apps.customer_id
HAVING MIN(apps.time_start) BETWEEN '2024-01-01 00:00:00' AND '2024-12-31 23:59:59'

Очень смущает, что когда я хочу ограничить выдачу клиентов только теми, по посещениям которых были сформированы счета,
SELECT apps.customer_id  FROM appointments apps 
	WHERE apps.in_trash is not true and apps.status = 4 and apps.filial_id = 1
		and apps.invoice_id is not null
GROUP BY apps.customer_id
HAVING MIN(apps.time_start) BETWEEN '2024-01-01 00:00:00' AND '2024-12-31 23:59:59'


то получаю выборку большую, чем в предыдущем запросе!
Подскажите, пожалуйста, в чём я ошибаюсь?
  • Вопрос задан
  • 164 просмотра
Подписаться 2 Простой Комментировать
Решения вопроса 4
shurshur
@shurshur
Сисадмин, просто сисадмин...
Потому что time_start у некоторых, пришедших не в этом году, имеет invoice_id is null по старым записям. Раньше эти записи попадали в предыдущие года и давали min(time_start) в прошлом, а после дополнительного условия time_start у всех отобранных попадает уже в текущий год и min(time_start) попадает в условие отбора.
Ответ написан
Комментировать
Rsa97
@Rsa97
Для правильного вопроса надо знать половину ответа
До группировки в первом случае в исходную выборку попадают строки, в которых invoice_id == NULL, среди которых есть такие, что time_start < '2024-01-01 00:00:00'. Соответственно, customer_id из них отсеивается в HAVING.
Пример:
|-------------+------------+---------------------|
| customer_id | invoice_id |      time_start     |
|-------------+------------+---------------------|
|         1   |     null   | 2023-12-31 00:00:00 |
|         1   |        1   | 2024-02-01 00:00:00 |
|-------------+------------+---------------------|

В первом случае в предварительную выборку попадают обе строки и, поскольку MIN(time_start) == '2023-12-31 00:00:00' < '2024-01-01 00:00:00', то customer_id == 1 в финальную выборку не попадает.
Во втором случае в предварительную выборку попадает только вторая строка и, поскольку MIN(time_start) == '2024-02-01 00:00:00' > '2024-01-01 00:00:00', то customer_id == 1 попадает в финальную выборку.
Ответ написан
Комментировать
Melkij
@Melkij
PostgreSQL DBA
Смотрите свои данные. Совершенно логично, что если у вас есть клиент №5 с такими данными:
insert into appointments (customer_id, in_trash, status, filial_id, invoice_id, time_start) values 
(5, false, 4, 1, null, '2023-12-23'),
(5, false, 4, 1, 123, '2024-01-13')

то он не попадёт в результат первого запроса, но попадёт в результат второго. И оба ответа верны, а вот какой из них хочет знать бизнес - уточнять надо у бизнеса.
Ответ написан
Комментировать
@Akina
Сетевой и системный админ, SQL-программист.
Если у клиента ВСЕ записи ранее текущего года НЕ соответствуют условию WHERE, то эти записи будут отброшены ещё до группировки. И клиент попадёт в выборку, потому что останутся только записи текущего года. Проверять надо после группировки:
HAVING MAX(   (apps.in_trash is not true 
           and apps.status = 4 
           and apps.filial_id = 1 
           and apps.invoice_id is not null
               ) :: INT
           ) > 0
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы