Javascript-форум (https://javascript.ru/forum/)
-   Node.JS (https://javascript.ru/forum/node-js-io-js/)
-   -   mysql multi insert (https://javascript.ru/forum/node-js-io-js/81307-mysql-multi-insert.html)

andrew76 07.11.2020 15:24

mysql multi insert
 
Добрый день уважаемые форумчане!

Входные данные: Клиент передает на сервер json oбъект который содержит в себе массив вида (id, quantity, price)

Моя функция для того чтоб сделать insert этого массива в db:

static addSale(products){

        //WHERE (SELECT stock FROM prais_informations WHERE code = ) > 20;
        let quer = 'INSERT INTO `sales` (`id`, `code`, `quantity`, `price`, `sum`, `idCheck`) VALUES ?;';            

        return new Promise((resolve, reject) => {
           
        pool.getConnection((err, connection) => {
            if (err)reject(err);
            
            connection.query(quer, [products.map(item => [null, item.id, item.quantity, item.price, item.sum, 3])], (err, rows) => {
                 if (err) {
                    reject(err);
                }

                resolve(rows);
                connection.release();
            });
        });
     });
    }


Вопрос в следующем, прежде чем сделать инсерт всех строк, нужно проверить не превышает ли количество в запросе, остаток в бд, если хотя бы одна строка не удовлетворяет условию, запрос не должен быть сделан.

Вариант я вижу один, создать дополнительную функцию, которая вернет в промисе AND нескольких селектов (true/false) и в части then уже делать insert запрос.

laimas 07.11.2020 15:43

Цитата:

Сообщение от andrew76
этого массива в db

Это как понимать, если MySQL не имеет такого типа данных? JSON пишется что-ли?

Alexandroppolus 07.11.2020 15:50

Цитата:

Сообщение от laimas
Это как понимать, если MySQL не имеет такого типа данных? JSON пишется что-ли?

скорее всего, используемая библиотека просто втыкает массив на место вопросика, делая запрос с несколькими строками после VALUES, mysql так может

laimas 07.11.2020 15:53

Цитата:

Сообщение от Alexandroppolus
mysql так может

Где и в чем так может, в Node? Может быть, я не знаю его и его драйверов.

laimas 07.11.2020 16:17

andrew76,
что с чем сравнивать не знаю, но зачастую отдельного запрос не требуется. Пример простой, если я правильно понял, то нужно типа такого:

"INSERT INTO table (count) SELECT N WHERE (SELECT balance FROM table LIMIT 1) > N"

где N - это вставляемое количество в поле count, balance - это поле остатка в этой же таблице. Тут используется просто поле со значением по умолчанию, для всех, поэтому и LIMIT 1. Если вставка для конкретного ID, то нужно взять для этого ID, но сама вставка при этом, это уже будет обновление. То есть остаток по идее, это уже другая таблица, откуда он и извлекается для ID. Иначе это только ON DUPLICATE KEY UPDATE.

andrew76 07.11.2020 20:24

Цитата:

Сообщение от laimas (Сообщение 530455)
Это как понимать, если MySQL не имеет такого типа данных? JSON пишется что-ли?

Обмен между клиентом и сервером в формате json, дальше бэкэнд обходит принятый объект map'ом и делает массив, ничего стороннего не используется.

andrew76 07.11.2020 20:25

Цитата:

Сообщение от laimas (Сообщение 530457)
Где и в чем так может, в Node? Может быть, я не знаю его и его драйверов.

Вот здесь есть simple example
https://www.mysqltutorial.org/mysql-nodejs/insert/

andrew76 07.11.2020 20:38

Цитата:

Сообщение от laimas (Сообщение 530458)
andrew76,
что с чем сравнивать не знаю, но зачастую отдельного запрос не требуется. Пример простой, если я правильно понял, то нужно типа такого:

"INSERT INTO table (count) SELECT N WHERE (SELECT balance FROM table LIMIT 1) > N"

где N - это вставляемое количество в поле count, balance - это поле остатка в этой же таблице. Тут используется просто поле со значением по умолчанию, для всех, поэтому и LIMIT 1. Если вставка для конкретного ID, то нужно взять для этого ID, но сама вставка при этом, это уже будет обновление. То есть остаток по идее, это уже другая таблица, откуда он и извлекается для ID. Иначе это только ON DUPLICATE KEY UPDATE.

Чтобы не быть голословным, вот таблицы:
https://ibb.co/NntTBDr

https://ibb.co/tm4z09n


В функции, которую я представил выше, формируется запрос вида:
INSERT INTO `sales` (`id`, `code`, `quantity`, `price`, `sum`, `idCheck`) 
VALUES 
(null, item.id, item.quantity, item.price, item.sum, 3), 
(null, item.id, item.quantity, item.price, item.sum, 3)
, ... , 
(null, item.id, item.quantity, item.price, item.sum, 3);


Но прежде чем сделать инсерты в `sales` нужно проверить, не превышает ли доступный остаток каждый элемент из VALUES.

1. Мне кажется одним запросом это не сделать, нужно написать новую функцию, которая примет массив, сравнит quantity c balance и вернет промис. Задача довольно типовая, поэтому я и задаюсь вопросом, как это реализовать правильно.

laimas 07.11.2020 21:07

Цитата:

Сообщение от andrew76
Вот здесь есть simple example

Вот это "?" называется "неименованные, или позиционные, псевдопеременные". Позиционные потому, что они должны быть указаны в той же последовательности в какой будут в запрос поступать данные. То есть, если вы записываете в базу массив [a, b, c], элементы которого должны попасть соответственно в поля a, b, c таблицы, то в запросе должно быть прописано ?, ?, ?. Где и в каком руководстве вы прочли, то передавая в запрос массив, его можно весь, скопом, привязать к одной псевдопеременной я не знаю. Сам MySQLi этого не делает, это может только драйвер, а есть ли такое в Node не в курсе. В случае если элементы массива будут иметь иной порядок, а вы этого не контролируете, то данные будут записаны не в те поля, и никакой драйвер эту ситуацию не исправит.

Другое дело PDO, в нем есть и именованные псевдопеременные. Не проблема разместить в теле запроса в любом месте :a, :b ... и передать для записи массив [a=> 12, b=> 15] (объект, в случае Node) и проблем не будет никогда. Есть ли в Node поддержка абстракций для работы с БД не в курсе.

А вообще такие запросы выгодно выполнять в случаях например множественной вставки или обновлений. В этом случае запрос подготавливается, проверяется, а затем в него вместо псевдопеременных просто подставляются новые данные.

laimas 07.11.2020 21:21

Цитата:

Сообщение от andrew76
не превышает ли доступный остаток каждый элемент из VALUES

Вдумайтесь в то, что вы написали, и это в контексте вставки в базу. Если "INSERT INTO `sales` (`id`" и при этом его значение указывается null, значит это новая запись, для поля id с автоинкрементом. Так? А если так, у кого нужно проверить значение, если записи как таковой в базе еще не существует?

Можно предполагать, например, это заказ/продажа пишется в таблицу А, то есть информация о неком продукте. Сами продукты описаны в таблице В. Тогда в одном запросе можно обратиться к таблице В, сравнив его остаток со вставляемым значением в таблицу А. Делается это так как я показывал.

Ваше же пояснение ни о чем не говорит, что с чем, откуда это все, не понять.

andrew76 07.11.2020 21:54

Цитата:

Сообщение от laimas (Сообщение 530468)
Вдумайтесь в то, что вы написали, и это в контексте вставки в базу. Если "INSERT INTO `sales` (`id`" и при этом его значение указывается null, значит это новая запись, для поля id с автоинкрементом. Так? А если так, у кого нужно проверить значение, если записи как таковой в базе еще не существует?

Можно предполагать, например, это заказ/продажа пишется в таблицу А, то есть информация о неком продукте. Сами продукты описаны в таблице В. Тогда в одном запросе можно обратиться к таблице В, сравнив его остаток со вставляемым значением в таблицу А. Делается это так как я показывал.

Ваше же пояснение ни о чем не говорит, что с чем, откуда это все, не понять.

Хорошо, завтра я над этим поработаю.

andrew76 09.11.2020 14:09

Не придумал ничего умнее чем в цикле создать запрос вида
SELECT true WHERE (SELECT prais_informations.stock FROM prais_informations WHERE prais_informations.code = 1 LIMIT 1) > 7 
UNION
 SELECT true WHERE (SELECT prais_informations.stock FROM prais_informations WHERE prais_informations.code = 2 LIMIT 1) > 3

вернуть промис и на основе ответа делать/не делать запрос insert.

SuperZen 09.11.2020 14:12

надо было триггер beforeInsert написать... и использовать транзацию)

andrew76 09.11.2020 14:18

Цитата:

Сообщение от SuperZen (Сообщение 530541)
надо было триггер beforeInsert написать... и использовать транзацию)

Пожалуйста опишите подробнее вашу рекомендацию, триггер это уже на стороне мускула, как я понимаю.

П.С, сейчас попробую сделать процедуру.
П.С, пошел смотреть продвинутый курс по mysql, бех этих знаний не обойтись.

SuperZen 09.11.2020 14:31

надо писать много букаф ).. по вашему получается: можно купить только половину, а можно и все, а можно и ничего, а если будет несколько пользователей, претендующих на одно и тоже... потом тень на плетень, сиди и думай ) кому и что показывать на какие запросы...

laimas 09.11.2020 14:57

'INSERT INTO `sales` (`id`, `code`, `quantity`, `price`, `sum`, `idCheck`)

и при это для id указывать null не обязательно, проще убрать id из запроса. А проверить надо с параметрами таблицы prais_informations? Что с чем?

andrew76 09.11.2020 15:07

SELECT prais_informations.stock <=  количество для товара 0*  FROM prais_informations WHERE prais_informations.code = код товара 0*
...
SELECT prais_informations.stock <= количество для товара n* FROM prais_informations WHERE prais_informations.code = код товара n*

* - приходит в запросе от клиентской части.

где n количество товаров в текущем заказе, товары не дублируются, если хотя бы один из этих запросов возвращает false заказ дальше не идет, запись в БД не происходит.

laimas 09.11.2020 15:12

Что с чем сравнивать? Это не сравнение, это получение параметров из другой таблицы. С какими полями таблицы `sales` нужно сравнивать эти параметры?

andrew76 09.11.2020 15:17

sales: таблица в которую просто идет запись(тот самый мульти инсерт, c этой таблицей ничего сравнивать не нужно, в неё только запись)

sales

prais_information AS pi: таблица в которой лежат остатки, от клиента приходит массив вида [id, quantity, price, sum]

Соответственно pi.code === id and pi.stock >= quantity

pi

laimas 09.11.2020 16:34

Да, не получится при многострочной вставке проверить, так как нельзя выбрать группы записей у несуществующего и сослаться не на что. Без временной таблицы не обойтись. Только в цикле:

var data = [
    [code, quantity, ....],
    [code, quantity, ....],
    ....
];

data.forEach((d)=> {
    query('INSERT INTO sales (code, quantity, ...) SELECT ' + d.join(',') + ' WHERE (SELECT stock FROM pi WHERE code=' + d.code + ') >=' + d.quantity)
})

andrew76 09.11.2020 21:42

Буду использовать временную таблицу.

Вам спасибо за подробное разъяснение. Начну просмотр курса sql на юдеми, после просмотра вернусь к задаче.

laimas 09.11.2020 22:30

Цитата:

Сообщение от andrew76
Буду использовать временную таблицу

Если ваше условие, что я проглядел, это нет вставки если хотя бы один товар из вставляемых не отвечает условию, то смысла во временной таблице нет, проще сделать запрос

'SELET 1 WHERE NOT EXISTS (SELECT * FROM pi WHERE code IN(sales.code, sales.code, ....) AND stock BETWEEN sales.quantity.min AND sales.quantity.max)'


и если вернуло, значит можно вставлять, иначе нет. Если же разрешено вставлять товары, которые отвечают условию, то функция INSERT возвращает число вставленных записей. То есть, если записывать в цикле и проверять результат возвращаемый, то можно вернуть пользователю отчет, какие товары не добавлены, или наоборот обрадовать.

andrew76 10.11.2020 08:20

Элегантное решение моих union select, сделать такой select и при правильном исходе в .then делать insert. Решение неплохое, но оно КМК не "энтерпрайз". В любом случае спасибо за помощь, я отпишусь в конце недели, как просмотрю и усвою курс. Спасибо.

laimas 10.11.2020 08:29

Цитата:

Сообщение от andrew76
Элегантное решение моих union select

Когда UNION считался элегантным решением? И зачем вообще оно нужно? Вы же хотите узнать есть ли недопустимые значения, а это как показано, для всех входящих sales.code и их диапазона значений, то есть нет ли значения stock в диапазоне мин/мах sales.quantity. Зачем же плодить юнионы, которые в данном случае как собаке пятая лапа?

andrew76 10.11.2020 11:08

Боюсь вы неверно поняли, я хотел сказать, что наговнокодил с UNION, вы же написали элегантное решение. Недопонимание вышло.

SuperZen 10.11.2020 11:13

напиши хранимую процедуру )) https://www.mysqltutorial.org/mysql-if-statement/

andrew76 10.11.2020 11:40

Как сделаю, выложу.

laimas 10.11.2020 18:20

Цитата:

Сообщение от andrew76
Боюсь вы неверно поняли

Видимо так, но если вы будете проверять запросом что я показывал, то из мин/макс значений вставляемых надо вычитать 1, так как BETWEEN не вернет "можно вставлять", если любое из значений будет равно stock, а у вас это разрешено.

andrew76 13.11.2020 09:42

Прикладываю процедуру, которую сделал на данный момент, из проблем insert в таблицу sales проходит, но пропускается один индекс (скрин).
Так же подскажите как правильно обновить остатки на складах, что то вроде(stackoverflow)
You can use a CASE statement to handle multiple if/then scenarios:

UPDATE table_to_update 
SET  cod_user= CASE WHEN user_rol = 'student' THEN '622057'
                   WHEN user_rol = 'assistant' THEN '2913659'
                   WHEN user_rol = 'admin' THEN '6160230'
               END
    ,date = '12082014'
WHERE user_rol IN ('student','assistant','admin')
  AND cod_office = '17389551';



Процедура:

BEGIN

DECLARE res INT;
DECLARE last_index INT;
SET @input := 'sales:  [  { id: 2, quantity: 5, price: 123, sum: 615 },  { id: 1, quantity: 9, price: 97, sum: 873 } ]';

SET @input:=REPLACE(@input, ' ', '');
SET @input:=REPLACE(@input, 'sales:', '');
SET @input:=REGEXP_REPLACE(@input, '([{,])([a-z])', '$1"$2');
SET @input:=REGEXP_REPLACE(@input, '([a-z]):', '$1":');

CREATE TEMPORARY TABLE new_tbl SELECT jtin.id AS jtin_id, jtin.quantity AS jtin_qty, pi.stock AS pi_qty, pi.price AS pi_price
FROM JSON_TABLE(@input,
                "$[*]"
                COLUMNS (id INT PATH "$.id" ,
                         quantity MEDIUMINT PATH "$.quantity",
                         price DECIMAL(7,2) PATH "$.price",
                         `sum` DECIMAL(8,2) PATH "$.sum"
                         )) AS jtin 
                LEFT JOIN prais_informations AS pi ON jtin.id  = pi.code;

SET res = (SELECT count(*) FROM new_tbl WHERE jtin_qty > pi_qty);

IF res = 0 THEN 
/*
  Инсертим новую запись, получившийся id, будет являтся id чека
*/
INSERT INTO checks(`data`, `user`) VALUES ( UNIX_TIMESTAMP(), 0);
SET last_index = (SELECT max(id) FROM checks);
/*
  Инсертим содержимое массива заказа в таблицу регистрации
*/
INSERT INTO sales(`code`, `quantity`, `price`, `sum`, `idCheck`) SELECT nt.jtin_id, nt.jtin_qty, nt.pi_price, (nt.jtin_qty * nt.pi_price), last_index FROM new_tbl AS nt;

/*
  Обновляем остатки на складе
*/

END IF

END

andrew76 13.11.2020 09:49

Как вариант можно сделать триггер на insert в sales и делать update в price_information.


Часовой пояс GMT +3, время: 23:54.