Триггеры MySQL используют разные TIMEZONE - PullRequest
0 голосов
/ 25 января 2019

Я создал триггер для установки уникального имени (ref) для покупки, используя часовой пояс для поля create_date.Это работало нормально, пока я не заметил дубликаты ссылок, одна из ссылок имеет дату в ссылке, не совпадающую с датой в поле create_date (1 день различий)? !!

BEGIN
SET NEW.reference := concat(
(SELECT name FROM provider WHERE id = NEW.provider_id),
 date_format(NEW.create_date, '%Y%m%d'), '/',
  (SELECT LPAD(IFNULL(MAX(SUBSTRING_INDEX(reference, '/', -1)) + 1, 0), 3, '0') 
   FROM purchase
   WHERE date_format(NEW.create_date, '%Y%m%d') = date_format(create_date, '%Y%m%d')
      AND NEW.provider_id = provider_id
  )
);
END

У кого-нибудь есть идеякакой хэппинг или лучший подход?

PS: дата в поле create_date является правильной, дата в NEW.create_date, которая используется в ссылке, неверна (возможно, б / с клиентачасовой пояс, поскольку мы отправляем его в виде строки)


Обновление

Структура таблиц:

Покупка:

CREATE TABLE purchase
(
  id             int          auto_increment primary key,
  provider_id    int          not null,
  create_date    timestamp    null,
  create_user    int          null,
  change_date    timestamp    null,
  change_user    int          null,
  group_id       int          null,
  reference      varchar(45)  null
);

CREATE INDEX purchase_reference_index ON purchase (reference);
CREATE INDEX purchase_provider_index ON purchase (provider_id);

Поставщик:

CREATE TABLE provider
(
  id           int auto_increment primary key,
  name         varchar(45) null,
  constraint name_uniq unique (name)
);

Пример запроса:

INSERT INTO purchase (provider_id, create_date, create_user, group_id)
VALUE (4, '2019-01-30 02:36:58', 1, 3);

2019-01-30 02:36:58 сохраняется в базе данных как 2019-01-29 23:36:58, когда я выбираю его из сеанса с часовым поясом сервера использования.

Функциячто я использую для установки часового пояса:

function update_timezone($timezone = null)
{
    if (is_null($timezone)) $timezone = __SERVER_TIMEZONE;

    if (in_array($timezone, timezone_identifiers_list())) {
        date_default_timezone_set($timezone);

        $tz = (new DateTime('now', new DateTimeZone(date_default_timezone_get())))->format('P');
        $conn = Database::Connect();
        Database::NonQuery("SET time_zone = '$tz';", $conn);
    }
}

Что я ожидаю:

reference === 'provider_name20190129/00X'

Что я получаю:

reference === 'provider_name20190130/00Y'

Как воспроизвестивыпуск:

CREATE DATABASE test;

CREATE TABLE purchase
(
  id             int          auto_increment primary key,
  provider_id    int          not null,
  create_date    timestamp    null,
  create_user    int          null,
  change_date    timestamp    null,
  change_user    int          null,
  group_id       int          null,
  reference      varchar(45)  null
);

CREATE INDEX purchase_reference_index ON purchase (reference);
CREATE INDEX purchase_provider_index ON purchase (provider_id);

CREATE TABLE provider
(
  id           int auto_increment primary key,
  name         varchar(45) null,
  constraint name_uniq unique (name)
);

CREATE TRIGGER test.purchase_ref_insert
  BEFORE INSERT
  ON test.purchase
  FOR EACH ROW
BEGIN
  SET NEW.reference := concat(
      (SELECT name FROM provider WHERE id = NEW.provider_id),
      date_format(NEW.create_date, '%Y%m%d'), '/',
      (SELECT LPAD(IFNULL(MAX(SUBSTRING_INDEX(reference, '/', -1)) + 1, 0), 3, '0')
       FROM purchase
       WHERE date_format(NEW.create_date, '%Y%m%d') = date_format(create_date, '%Y%m%d')
         AND NEW.provider_id = provider_id
      )
  );
END
;

INSERT INTO provider (name) VALUE ('test');


SET time_zone = '+00:00';
INSERT INTO purchase (provider_id, create_date, create_user, group_id)
VALUE (1, '2019-01-30 02:36:58', 1, 3);


SET time_zone = '+05:00';
INSERT INTO purchase (provider_id, create_date, create_user, group_id)
VALUE (1, '2019-01-30 02:36:58', 1, 3);

SET time_zone = '-05:00';
INSERT INTO purchase (provider_id, create_date, create_user, group_id)
VALUE (1, '2019-01-30 02:36:58', 1, 3);


SET time_zone = '+00:00';
SELECT create_date, reference FROM purchase;

И вот что я получаю:

enter image description here

Ответы [ 2 ]

0 голосов
/ 29 января 2019

Я нашел решение: D

Мне просто нужно установить часовой пояс внутри триггера SET time_zone = 'SYSTEM';

date_format не относится к локализованному часовому поясу

CREATE TRIGGER test.purchase_ref_insert
  BEFORE INSERT
  ON test.purchase
  FOR EACH ROW
  BEGIN
    SET @time_zone_tmp := @@time_zone;
    SET time_zone = 'SYSTEM';
    SET NEW.reference := concat(
        (SELECT name FROM provider WHERE id = NEW.provider_id),
        date_format(NEW.create_date, '%Y%m%d'), '/',
        (SELECT LPAD(IFNULL(MAX(SUBSTRING_INDEX(reference, '/', -1)) + 1, 0), 3, '0')
         FROM purchase
         WHERE date_format(NEW.create_date, '%Y%m%d') = date_format(create_date, '%Y%m%d')
           AND NEW.provider_id = provider_id
        )
    );
    SET time_zone = @time_zone_tmp;
  END
;

enter image description here

0 голосов
/ 29 января 2019

Вы можете использовать @@session.time_zone в триггере, чтобы CONVERT_TZ заданной метки времени в системном часовом поясе, перед сравнением с таблицей.

CREATE TRIGGER purchase_ref_insert
  BEFORE INSERT ON purchase
  FOR EACH ROW
BEGIN
 SET @ts := CONVERT_TZ(NEW.create_date, @@session.time_zone, 'SYSTEM');
 SET @providerName := (SELECT name FROM provider WHERE id = NEW.provider_id);
 SET @refPostfix := (
         SELECT 
         LPAD(IFNULL(MAX(SUBSTRING_INDEX(reference, '/', -1)) + 1, 0), 3, '0')
         FROM purchase
         WHERE NEW.provider_id = provider_id
           AND CAST(CONVERT_TZ(create_date, @@session.time_zone, 'SYSTEM') AS DATE) = CAST(@ts AS DATE)
     );
 SET NEW.create_date_tz = @@session.time_zone;
 SET NEW.reference := CONCAT(@providerName, DATE_FORMAT(@ts, '%Y%m%d'), '/', @refPostfix);
END;

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

create_date_tz varchar(6)   not null default 'SYSTEM'

Таким образом, вы все еще можете показать, что create_date возвращается в часовом поясе клиента.

Тест на db <> fiddle здесь

Например:

 SELECT 
  id, create_date, reference, create_date_tz,
  CONVERT_TZ(create_date, 'SYSTEM', create_date_tz) as ts_at_TZ
 FROM purchase;

Возвращает:

id | create_date         | reference        | create_date_tz | ts_at_TZ           
 1 | 2019-01-30 07:30:01 | test20190130/000 | -05:00         | 2019-01-30 02:30:01
 2 | 2019-01-30 02:30:02 | test20190130/001 | +00:00         | 2019-01-30 02:30:02
 3 | 2019-01-29 21:30:03 | test20190129/000 | +05:00         | 2019-01-30 02:30:03
...