Анализ проблемы с группировкой по полю DATE в Oracle

Проблемная ситуация

При работе с полем типа DATE и функцией SYSDATE возникает неочевидное поведение:

-- Создаем тестовую таблицу
CREATE TABLE xxt_random_data (
    random_number NUMBER,
    random_string VARCHAR2(100),
    creation_date DATE DEFAULT SYSDATE
);

-- Вставка 100 записей (10 итераций × 10 строк)
BEGIN
  FOR i IN 1..10 LOOP
    INSERT INTO xxt_random_data (random_number, random_string)
    SELECT 
        ROUND(DBMS_RANDOM.VALUE(1, 1000)),
        DBMS_RANDOM.STRING('A', 20)
    FROM dual
    CONNECT BY LEVEL <= 10;
    COMMIT;
  END LOOP;
END;
/

-- Группировка возвращает 1 запись

select CREATION_DATE, count(*) from xxt_random_data group by CREATION_DATE;

-- Результат:
-- CREATION   COUNT(*)
-- -------- ----------
-- 26.06.25        100

Но при увеличении итераций:

-- Вставка 10,000 записей (1000 итераций × 10 строк)
BEGIN
  FOR i IN 1..1000 LOOP
    INSERT INTO xxt_random_data (random_number, random_string)
    SELECT 
        ROUND(DBMS_RANDOM.VALUE(1, 1000)),
        DBMS_RANDOM.STRING('A', 20)
    FROM dual
    CONNECT BY LEVEL <= 10;
    COMMIT;
  END LOOP;
END;
/

-- Группировка показывает несколько записей
select CREATION_DATE, count(*) from xxt_random_data group by CREATION_DATE;

-- Результат:
-- CREATION   COUNT(*)
-- -------- ----------
-- 26.06.25       2670
-- 26.06.25       1500
-- 26.06.25       2700
-- 26.06.25        310
-- 26.06.25       2820

Причина проблемы

  1. Тип DATE в Oracle всегда содержит:
  2. Дату (день, месяц, год)
  3. Время (часы, минуты, секунды)

  4. SYSDATE возвращает текущие дату и время выполнения операции

  5. Отображение в клиенте часто скрывает время, показывая только дату

  6. При группировке учитывается полное значение (дата + время)

Решения

1. Использование TRUNC при выборке

SELECT TRUNC(CREATION_DATE), COUNT(*) 
FROM xxt_random_data 
GROUP BY TRUNC(CREATION_DATE);

2. Использование TRUNC при вставке

INSERT INTO xxt_random_data (random_number, random_string, creation_date)
SELECT 
    ROUND(DBMS_RANDOM.VALUE(1, 1000)),
    DBMS_RANDOM.STRING('A', 20),
    TRUNC(SYSDATE) -- Сохраняем только дату
FROM dual;

3. Вынос COMMIT за пределы цикла

BEGIN
  FOR i IN 1..1000 LOOP
    INSERT INTO xxt_random_data (random_number, random_string)
    SELECT ... FROM dual;
  END LOOP;
  COMMIT; -- Один коммит в конце
END;
/

Вывод

Всегда учитывайте, что тип DATE в Oracle содержит время, даже если оно не отображается. Для работы только с датой используйте TRUNC().