Как найти соответствие email из json с базой данных и записать данные в MySQL?

89
17 июня 2021, 05:10

Пытаюсь в dle cms сохранять дополнительную информацию о пользователе в базу данных, при этом дополнительные данные получаю из JSON со стороннего сервиса.

Хочу запускать скрипт по крону и проверять соответствие email из json с email зарегистрированных на сайте пользователей dle_users - email.

Образец json данных:

[{"id":323,"userId":676,"createdAt":"2019-08-23T13:05:56.830Z","status":"APPROVED","currency":"RUR","paySystem":"visa","paySystemGroup":"creditCard","total":69.00,"ipAddress":"246.227.2.4","OneTimeOffer":false,"ware":{"id":812,"title":"Заголовок.","type":"DIGITAL","userId":728},"income":297.08,"customer":{"id":8832,"fio":"Петрович","email":"test@test.ru"}}, {"id":324,"userId":677,"createdAt":"2019-08-23T14:05:56.830Z","status":"APPROVED","currency":"RUR","paySystem":"visa","paySystemGroup":"creditCard","total":69.00,"ipAddress":"246.227.2.4","OneTimeOffer":false,"ware":{"id":813,"title":"Заголовок.","type":"DIGITAL","userId":729},"income":297.08,"customer":{"id":8833,"fio":"Петрович","email":"test2@test.ru"}}, {"id":325,"userId":678,"createdAt":"2019-08-23T15:05:56.830Z","status":"APPROVED","currency":"RUR","paySystem":"visa","paySystemGroup":"creditCard","total":69.00,"ipAddress":"246.227.2.4","OneTimeOffer":false,"ware":{"id":814,"title":"Заголовок.","type":"DIGITAL","userId":730},"income":297.08,"customer":{"id":8834,"fio":"Петрович","email":"test3@test.ru"}}]

Json дерево

Всего в json ответе 10 позиций.

Мне нужно записать в базу данные id, ware->id, ware->title, income если найдено соответствие по email адресу.

Т.е. как-то нужно из json брать email и искать, не зарегистрирован ли на dle сайте пользователь с данным email адресом и если найден данный адрес, необходимо из json получить данные id, ware->id, ware->title, income и сохранить их в базу dle_users, в таблицу данного пользователя.

На сайте может быть зарегистрировано несколько тысяч пользователей. Обращаться к Json хочу как можно чаще по cron, чтобы на сайте была всегда актуальная информация.

Как верно реализовать такой функционал, чтобы система не висла и при повторном обращении к json, ранее полученные данные вновь не добавлялись в базу (видимо нужно выполнить проверку по id из json, т.к. он уникален)?

Еще, очень важно, в образце базы я не добавил поля под id, ware->id, ware->title, income т.к. не знаю как сделать это верно, если проверять ранее размещенные данные, то id из json нужно сохранять так, чтобы он был уникальным...

Помогите разобраться, пожалуйста. Направьте в нужном направлении.

Спасибо.

Ниже дамп базы данных, таблица dle_users

--
-- Структура таблицы `dle_users`
--
CREATE TABLE `dle_users` (
  `email` varchar(50) NOT NULL DEFAULT '',
  `password` varchar(255) NOT NULL DEFAULT '',
  `name` varchar(40) NOT NULL DEFAULT '',
  `user_id` int(11) NOT NULL,
--
-- Добавил 4 поля
--
  `paym_id` text,
  `paym_ware_id` text,
  `paym_title` text,
  `paym_income` varchar(40) DEFAULT NULL,
  `news_num` mediumint(8) NOT NULL DEFAULT '0',
  `comm_num` mediumint(8) NOT NULL DEFAULT '0',
  `user_group` smallint(5) NOT NULL DEFAULT '4',
  `lastdate` varchar(20) NOT NULL DEFAULT '',
  `reg_date` varchar(20) NOT NULL DEFAULT '',
  `banned` varchar(5) NOT NULL DEFAULT '',
  `allow_mail` tinyint(1) NOT NULL DEFAULT '1',
  `info` text NOT NULL,
  `signature` text NOT NULL,
  `foto` varchar(255) NOT NULL DEFAULT '',
  `fullname` varchar(100) NOT NULL DEFAULT '',
  `land` varchar(100) NOT NULL DEFAULT '',
  `favorites` text NOT NULL,
  `pm_all` smallint(5) NOT NULL DEFAULT '0',
  `pm_unread` smallint(5) NOT NULL DEFAULT '0',
  `time_limit` varchar(20) NOT NULL DEFAULT '',
  `xfields` text NOT NULL,
  `allowed_ip` varchar(255) NOT NULL DEFAULT '',
  `hash` varchar(32) NOT NULL DEFAULT '',
  `logged_ip` varchar(46) NOT NULL DEFAULT '',
  `restricted` tinyint(1) NOT NULL DEFAULT '0',
  `restricted_days` smallint(4) NOT NULL DEFAULT '0',
  `restricted_date` varchar(15) NOT NULL DEFAULT '',
  `timezone` varchar(100) NOT NULL DEFAULT '',
  `news_subscribe` tinyint(1) NOT NULL DEFAULT '0',
  `comments_reply_subscribe` tinyint(1) NOT NULL DEFAULT '0',
  `twofactor_auth` tinyint(1) NOT NULL DEFAULT '0',
  `cat_add` varchar(500) NOT NULL DEFAULT '',
  `cat_allow_addnews` varchar(500) NOT NULL DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
--
-- Дамп данных таблицы `dle_users`
--
INSERT INTO `dle_users` (`email`, `password`, `name`, `user_id`, `news_num`, `comm_num`, `user_group`, `lastdate`, `reg_date`, `banned`, `allow_mail`, `info`, `signature`, `foto`, `fullname`, `land`, `favorites`, `pm_all`, `pm_unread`, `time_limit`, `xfields`, `allowed_ip`, `hash`, `logged_ip`, `restricted`, `restricted_days`, `restricted_date`, `timezone`, `news_subscribe`, `comments_reply_subscribe`, `twofactor_auth`, `cat_add`, `cat_allow_addnews`) VALUES
('test2@test.ru', '$2y$10$HizMAqnS70XtPyOAhEOBZunFUr0sTf6BxuhAF.jF5064WmVSiInS2', 'Admin', 1, 5, 2, 1, '1566571668', '1566008190', '', 1, '', '', '', '', '', '', 0, 0, '', '', '', 'a5d5341fa03117f2046f5a0b8f1589d0', '33.212.78.120', 0, 0, '', '', 0, 0, 0, '', '');
--
-- Индексы сохранённых таблиц
--
--
-- Индексы таблицы `dle_users`
--
ALTER TABLE `dle_users`
  ADD PRIMARY KEY (`user_id`),
  ADD UNIQUE KEY `name` (`name`),
  ADD UNIQUE KEY `email` (`email`);
--
-- AUTO_INCREMENT для сохранённых таблиц
--
--
-- AUTO_INCREMENT для таблицы `dle_users`
--
ALTER TABLE `dle_users`
  MODIFY `user_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
COMMIT;

Попробовал таким образом найти и сохранить данные, но что-то не получилось :(

Данные не сохраняются в базе.

$pdo = new PDO("mysql:host = localhost; dbname = ...",..., ...);
foreach ($parsedResponse as $singleUserResponse) {
   $paymId = $singleUserResponse['id'];
   $paymWareId = $singleUserResponse['ware']['id'];
   $paymTitle = $singleUserResponse['ware']['title'];
   $paymIncome = $singleUserResponse['income'];
   $email = $singleUserResponse['customer']['email'];
   $stmtGetUserByEmail = $pdo->prepare("SELECT email FROM dle_users WHERE email='$email' AND paym_income IS NULL");
  //var_dump ($stmtGetUserByEmail);
  // SELECT email FROM dle_users WHERE email='test@test.ru' AND paym_income IS NULL
  //перечисляются email не из базы, а из json
   $stmtGetUserByEmail->execute([$email]);
   $userId = $stmtGetUserByEmail->fetch();
if ($userId) {
   $updateUserPaymData = $pdo->prepare("UPDATE dle_users SET paym_income = $paymIncome, paym_id = $paymId WHERE email= $email");
   $stmt= $pdo->prepare($updateUserPaymData);
   $stmt->execute([$paymIncome, $paymId, $userId]);
}
}
Answer 1

Один из способов решить такую задачу -- сделать просто, потом оптимзировать. Попробуем?

Шаг 1.

Т.е. как-то нужно из json брать email и искать, не зарегистрирован ли на dle сайте пользователь с данным email адресом и если найден данный адрес, необходимо из json получить данные id, ware->id, ware->title, income и сохранить их в базу dle_users, в таблицу данного пользователя.

Распарсили json и бежим по коллекции

$payments = json_decode($json, true);
foreach ($payments as $payment) {
    $db->query('
        update `dle_users`
        set
            `paym_id` = :paym_id,
            `paym_ware_id` = :paym_ware_id,
            `paym_title` = :paym_title,
            `paym_income` = :paym_income
        where `email` = :email
    ', [
        ':paym_id' => $payment['id'],
        ':paym_ware_id' => $payment['ware']['id'],
        ':paym_title' => $payment['ware']['title'],
        ':paym_income' => $payment['income'],
        ':email' => $payment['customer']['email'],
    ]);
}

$db->query придумал чтобы сократить немного код и донести смысл. Если делать такой запрос в базу, то всё работает!

Шаг 2.

Вы наверняка слышали про запросы в базу в цикле :-) Такое решение не самое производительное. Время оптимизаций!

Мы можем бахнуть все обновления одним запросом. UPDATE так не умеет, но умеет INSERT ON DUPLICATE KEY UPDATE.

$db->query('
    insert into `dle_users` (
        `id`,
        `paym_id`,
        `paym_ware_id`,
        `paym_title`,
        `paym_income`
    ) values :values
    on duplicate key update
        `paym_id` = values(`paym_id`),
        `paym_ware_id` = values(`paym_ware_id`),
        `paym_title` = values(`paym_title`),
        `paym_income` = values(`paym_income`)
', [':values' => $values]);

Мы могли бы сделать только один запрос на обновление, если бы у нас были id пользователй с имэйлами из json. Слава богу добыть id совсем не сложно!

$payments = json_decode($json, true);
$emails = array_map(function($payment) {
    return $payment['customer']['email'];
}, $payments);
$result = $db->query('
    select
        `id`,
        `email`
    from `dle_users`
    where `email` in (:emails)
', [':emails' => $emails]);
$dleUsersIndex = array_column($result->rows(), null, 'email');
$values = [];
foreach ($payments as $payment) {
    $dleUser = $dleUsersIndex[$payment['customer']['email']]?? null; 
    if ($dleUser) {
        $values[] = [
            $dleUser['id'],
            $payment['id'],
            $payment['ware']['id'],
            $payment['ware']['title'],
            $payment['income'],
        ];
    }
}

Делаем запрос INSERT ON DUPLICATE KEY UPDATE и все обновления прилетают в одном запросе! Если это бы это был InnoDB, то нам были не страшны одновременные вызовы (один запрос либо выполнится либо нет, данные будут согласованы). В MyISAM можно лочить таблицу или забить на всю эту консистентность :-)

Тут можно остановится.

Шаг 3 (для особо упорных).

Сейчас всё это добро притормаживает на поиске пользователей в базе по имэйлу. Это происходит из-за того, что по полю email нет индекса. Надо построить. Но загвоздка том, что b-tree индекс по varchar не самое клёвое решение (его немного раздует на миллионах записей). Выручил бы хэш-индекс, но в MyISAM и InnoDB их нет :-(

Но не стоит унывать! Давно прдуман хак с эрзацем хэш-индекса. Просто добовляем в таблицу вычисляемое поле emailHash binary(16) not null и при изменении имэйла записываем туда сырой (не шестнадцатеричное представление) md5 (вообще можно использовать и другие хэш-функции). Ну и строим индекс по нему, а потом при поиске по имэйлу добавляем в условие emailHash = unhex(md5(:email)). Теперь вообще будет летать.

READ ALSO
Вывести картинки из одного поля

Вывести картинки из одного поля

В базе mysql в одном поле pic есть вот такие данные:

99
Вывод таблицы через RedBeanPHP

Вывод таблицы через RedBeanPHP

У меня такая проблема: есть бд, в которой есть таблица users, и мне нужно эту самую таблицу вывести, но выводится только слово 'Array' и больше ничегоВот...

80
Как сделать фильтр на продуктов на laravel

Как сделать фильтр на продуктов на laravel

Проблемма вот такаяЕсть у меня интернет магазин на Laravel 5

118
Подскажите по функции работы со строкой

Подскажите по функции работы со строкой

Подскажите пожалуйста как создать функцию Gol на php по обработке строки вида "36,67,90,90"Это минуты забитых голов в футбольном матче

111