Как можно создать триггер, который будет изменять значение поля в одной таблице в зависимости от условий, заданных в другой таблице?

Я никак не могу понять, как правильно реализовать функцию и триггер для обновления значений в базе данных. Вот детали:

**Сущность, которая должна автоматически обновлять значение одного поля в зависимости от условия в другой сущности:**

**Получатель условия**
```java
@Entity
class Employee {
   ...
   @Column(name = "employee_status")
   private EmployeeStatusENUM employeeStatusENUM; 
   // 0="действующий", 1="в отпуске", 2="закрыт" — 0 и 2 устанавливаются через поле в HTML, 
   // а 1 автоматически по условию из сущности Vocation
   
   ...
   @OneToMany(mappedBy = "employee")
   private List<Vocation> vocationList = new ArrayList<>(); 
   // график отпусков, коллекция, т.к. годовой отпуск делится на несколько частей
   ...
   // getters, setters...
}
```

**Поставщик условия**
```java
@Entity
class Vocation {
   ...
   @Column(name = "date_start")
   private LocalDate startOfVocation; 
   // дата начала отпуска, задается через поле в HTML

   @Column(name = "days")
   private Integer daysOfVocation; 
   // количество дней отпуска, задается через поле в HTML

   @Column(name = "date_end")
   private LocalDate endOfVocation; 
   // дата окончания отпуска, устанавливается через setter в контроллере перед сохранением 
   // (созданием) новой сущности.
   // vocation.setEndOfVocation(vocation.getStartOfVocation().plusDays(vocation.getDaysOfVocation()));
   // это поле также можно было бы заполнить через триггер, но не через HTML, таково указание. 
   // Данная сущность Vocation создается для Employee, её нельзя обновлять, только создавать и удалять.
   
   @ManyToOne
   @JoinColumn(name = "employee_id")
   private Employee employee;
}
```

Как мне правильно организовать триггер и функцию для этой логики?
  • 5 января 2025 г. 16:43
Ответы на вопрос 2
Для реализации триггера в базе данных, который будет обновлять статус сотрудника (`employee_status`) в зависимости от условий, связанных с его отпусками (`Vocation`), необходимо учитывать, что триггер будет срабатывать при вставке новой записи в таблицу `Vocation`. В зависимости от даты отпуска, нужно изменить статус сотрудника. Ваша рабочая база данных должна поддерживать SQL-триггеры, такие как PostgreSQL, MySQL и другие реляционные СУБД.

Вот пример SQL-триггера и функции, которые могут реализовать эту логику. Предположим, что ваша база данных — PostgreSQL.

### Шаг 1: Создание функции для триггера

Эта функция будет проверять, есть ли активный отпуск на текущую дату, и обновлять статус сотрудника.

```sql
CREATE OR REPLACE FUNCTION update_employee_status()
RETURNS TRIGGER AS $$
DECLARE
    count_vocations INT;
BEGIN
    -- Считаем количество отпусков с учетом новых данных
    SELECT COUNT(*)
    INTO count_vocations
    FROM Vocation
    WHERE employee_id = NEW.employee_id
      AND (NEW.start_of_vocation < date_trunc('day', NOW()) + INTERVAL '1 day'
      AND NEW.end_of_vocation > date_trunc('day', NOW()));

    -- Если есть активные отпуска, устанавливаем статус "в отпуске"
    IF count_vocations > 0 THEN
        UPDATE Employee
        SET employee_status = 1  -- 1 = "в отпуске"
        WHERE id = NEW.employee_id;
    ELSE
        UPDATE Employee
        SET employee_status = 0  -- 0 = "действующий"
        WHERE id = NEW.employee_id;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
```

### Шаг 2: Создание триггера

Теперь необходимо создать триггер, который будет вызывать ранее созданную функцию при вставке новых записей в таблицу `Vocation`.

```sql
CREATE TRIGGER on_vocation_insert
AFTER INSERT ON Vocation
FOR EACH ROW
EXECUTE FUNCTION update_employee_status();
```

### Объяснение

1. **Функция `update_employee_status`**:
   - Когда вставляется новая запись в `Vocation`, функция считает количество отпусков для определенного сотрудника на основе его идентификатора (поле `NEW.employee_id`).
   - Если количество активных отпусков больше нуля, статус сотрудника обновляется на "в отпуске" (значение 1).
   - Если активных отпусков нет, статус обновляется на "действующий" (значение 0).

2. **Триггер `on_vocation_insert`**:
   - Настраивается для выполнения функции `update_employee_status` после каждой вставки новой записи в таблицу `Vocation`.

### Рекомендации

- Убедитесь, что вы правильно настроили пути для обновления статуса, чтобы избежать расхождений между базой данных и логикой приложения.
- Протестируйте триггер с различными сценариями, чтобы удостовериться, что статус обновляется корректно.
- Если вы планируете обновление статуса при удалении записи (например, об окончании отпуска), вы можете создать аналогичный триггер для события `AFTER DELETE`.

### Поддержка на уровне Java

Учтите, что триггер будет работать независимо от Java-кода, который взаимодействует с базой данных. Это значит, что если вы используете ORM (например, Hibernate), настройка триггера всё равно будет актуальна и сохранит согласованность данных.
Создаете функцию для триггера: 
CREATE OR REPLACE FUNCTION set_empoyee_status()
  RETURNS TRIGGER
  LANGUAGE PLPGSQL
  AS
$$
BEGIN
	IF NEW.date_start <= CURRENT_DATE and NEW.date_end >= CURRENT_DATE  THEN
	   update employee -- тут изменяется таблица работников!
           set employee.empoyee_status = 1
          where employee.emp_id = NEW.employee_id;
          ELSE
          update employee -- тут изменяется таблица работников!
           set employee.empoyee_status = 0
          where employee.emp_id = NEW.employee_id;
	END IF;
	RETURN NEW;
END;
$$

Потом нужно присоединить функцию к триггеру:
CREATE TRIGGER tr_set_employee_status
  BEFORE UPDATE
  ON vocation -- тут на таблицу отпусков вешается триггер!
  FOR EACH ROW
  EXECUTE PROCEDURE set_empoyee_status();

PS: Если функция будет вешаться на разные события (в особенности, на delete), то нужно выбирать NEW/OLD источник записи для определения статуса.
Похожие вопросы