Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Наиболее типичной причиной неудачи является нехватка практических знаний по используемой СУБД — элементарное непонимание основ работы используемого инструментального средства. Подход по принципу «черного ящика» требует осознанного решения: оградить разработчиков от СУБД. Их заставляют не вникать ни в какие особенности ее функционирования. Причины использования этого подхода связаны с опасениями, незнанием и неуверенностью. Разработчики слышали, что СУБД — это «сложно», язык SQL, транзакции и целостность данных — не менее «сложно». Решение: не заставлять никого делать что-либо «сложное». Будем относиться к СУБД, как к черному ящику, и найдем инструментальное средство, которое сгенерирует необходимый код. Изолируем себя несколькими промежуточными уровнями, чтобы не пришлось сталкиваться непосредственно с этой «сложной» СУБД.
[...]
Вот типичный сценарий такого рода разработки.
- Разработчики были полностью обучены графической среде разработки или соответствующему языку программирования (например, Java), использованных для создания клиентской части приложения. Во многих случаях они обучались несколько недель, если не месяцев.
- Команда разработчиков ни одного часа не изучала СУБД Oracle и не имела никакого опыта работы с ней. Многие разработчики вообще впервые сталкивались с СУБД.
- В результате разработчики столкнулись с огромными проблемами, связанными с производительностью, обеспечением целостности данных, зависанием приложений и т.д. (но пользовательский интерфейс выглядел отлично).
[...]
Странная идея о том, что разработчик приложения баз данных должен быть огражден от СУБД, чрезвычайно живуча. Многие почему-то считают, что разработчикам не следует тратить время на изучение СУБД. Неоднократно приходилось слышать: «СУБД Oracle — самая масштабируемая в мире, моим сотрудникам не нужно ее изучать, потому что СУБД со всеми проблемами справится сама». Действительно, СУБД Oracle — самая масштабируемая. Однако написать плохой код, который масштабироваться не будет, в Oracle намного проще, чем написать хороший, масштабируемый код. Можно заменить СУБД Oracle любой другой СУБД — это утверждение останется верным. Это факт: проще писать приложения с низкой производительностью, чем высокопроизводительные приложения. Иногда очень легко создать однопользовательскую систему на базе самой масштабируемой СУБД в мире, если не знать, что делаешь. СУБД — это инструмент, а неправильное применение любого инструмента может привести к катастрофе. Вы будете щипцами колоть орехи так же, как молотком? Можно, конечно, и так, но это неправильное использование инструмента, и результат вас не порадует. Аналогичные результаты будут и при игнорировании особенностей используемой СУБД.
Вы, наверное, уже поняли направление моей мысли. Я ссылался на другие СУБД и описывал различия реализации одних и тех же возможностей в каждой из них. Я убежден: за исключением некоторых приложений, исключительно читающих из базы данных, создать полностью независимое от СУБД и при этом масштабируемое приложение крайне сложно и даже практически невозможно, не зная особенностей работы всех СУБД.
Плюс, как правило, этой логикой владеет только тот, кто ее реализовал. И, как выше написали, никому не рассказывает что это за логика, и почему именно так написана.Очень странное утверждение. И почему это справедливо только для БЛ в БД?
А что если придется менять СУБД?
Мы писали-писали на .Net-стеке с MS SQL Server, а потом ВНЕЗАПНО осознали, что нам нужен LAMP
Было бы интересно узнать, какая серьезная причина может привести к смене СУБД посреди проекта.
Вы можете возразить и сказать, а как же кластер?
… требует допиливания для поддержания ACID.
Итоговый код триггера будет выглядеть так:
SELECT o.max_limit, o.name INTO max_limit, client_name FROM org o WHERE o.id = NEW.org_id_client; IF NEW.sum > max_limit THEN CALL raise_error(CONCAT('Сумма ... не может превышать лимит ...')); END IF;
if ($this->sum > $this->org->max_limit) {
throw new MaxLimitException('Сумма ... не может превышать лимит ...');
}
Или более красивый вариант с использованием функции
SET msg := (SELECT raise_error(CONCAT('Сумма ... не может превышать лимит ...')) FROM org o WHERE o.id = NEW.org_id_client AND NEW.sum > o.max_limit );
Я создал триггеры, которые в каждом BEFORE триггере создают MEMORY TEMPORARY TABLE с одноимёнными столбцами
Кода стало меньше, он весь в одном месте и он не дублируется! Такой код поддерживать очень легко.
UPDATE doc_pos_tmp_trg
INNER JOIN docs ON doc_pos_tmp_trg.new_doc_id = docs.id
SET dp.new_price = get_price(dp.new_material_id, d.org_id_client)
WHERE dp.time = 'B' AND dp.type = 'I';
// function DocPos::beforeInsert()
$this->price = getPrice($this->material_id, $this->doc->org_id_client);
UPDATE docs
INNER JOIN doc_pos_tmp_trg ON docs.id IN (doc_pos_tmp_trg.new_doc_id, doc_pos_tmp_trg.old_doc_id)
SET sum = IFNULL(docs.sum, 0)
- CASE
WHEN doc_pos_tmp_trg.old_doc_id = id
THEN IFNULL(doc_pos_tmp_trg.old_kol * doc_pos_tmp_trg.old_price, 0)
ELSE 0
END
+ CASE
WHEN doc_pos_tmp_trg.new_doc_id = id
THEN IFNULL(doc_pos_tmp_trg.new_kol * doc_pos_tmp_trg.new_price, 0)
ELSE 0
END
WHERE doc_pos_tmp_trg.time = 'A';
// function DocPos::beforeInsert()
$oldAttributes = $this->oldAttributes;
$docId = ($this->doc_id ?: $oldAttributes['doc_id']);
$doc = Doc::find($docId);
if ($doc->sum === null) {
$doc->sum = 0;
}
if ($oldAttributes['doc_id'] !== null) {
$doc->sum -= $oldAttributes['kol'] * $oldAttributes['price'];
}
if ($this->doc_id !== null) {
$doc->sum += $this->kol * $this->price;
}
$doc->save();
В коде будет проще и понятнее:
if ($this->sum > $this->org->max_limit) { throw new MaxLimitException('Сумма ... не может превышать лимит ...'); }
INSERT INTO docs ... SELECT .. FROM docs d ... WHERE d.date BETWEEN ...
INSERT INTO doc_pos ... SELECT .. FROM doc_pos .. docs ... WHERE d.date BETWEEN ...
Вижу тут как минимум один лишний запрос из php к mysql серверу
Внешний php сервис писать который Питон будет дёргать?
Не создаются лишние переменные, нет переключения контекста, меньше логических операций, тупо меньше кода
1. $oldAttributes = $this->oldAttributes;
Видимо придётся дёргать SELECT'ом — доп. нагрузка
У Вас будет генериться много запросов которые гоняются между php <=> mysql. Ещё нагрузка.
Как бы Вы реализовали, например, сервис копирования документов за период.
Нет. При загрузке данных сущности из БД устанавливаются свойства в $this и те же самые в $this->oldAttributes. При обработке меняются данные в $this, а если надо, можно прочитать прежние значения из $this->oldAttributes.для PL/SQL и PL/PgSQL давно можно использовать пользовательские переменные.
зато нет проблем с контролем доступа к таблицам, авторизацией
Я даже количество символов сравнил. В варианте на PHP кода меньше. То, что не создаются переменные с понятными именами, это скорее минус.
Не совсем понял, что вы имели в виду насчет контекста, но то, что в рамках одного условия логические операторы служат разным целям, это тоже понятности не добавляет.
При загрузке данных сущности из БД устанавливаются свойства в $this и те же самые в $this->oldAttributes. При обработке меняются данные в $this, а если надо, можно прочитать прежние значения из $this->oldAttributes.
Селекты такие же, как и в вашем коде.
SELECT m.name
, get_price(m.id, d.org_id_client, d.date) price
, cs.stock
FROM materials m
LEFT JOIN docs d ON d.id = @doc_id
LEFT JOIN cur_stock cs ON cs.warehouse_id = d.warehouse_id AND m.id = cs.material_id
WHERE m.name LIKE CONCAT('%', IFNULL(@term, ''), '%')
LIMIT 50
Не очень понятно, зачем копировать сущности, но это ладно.Просто пример массовой обработки данных.
Скорее всего, я бы дернул их таким же селектом, обработал, и вставил новые данные в один или несколько групповых инсертов. Связанные сущности загружаются через дополнительный запрос с IN. В сложных случаях никто не мешает вызывать из кода запросы с INNER JOIN. То есть, счет запросов к базе идет на единицы или в крайнем случае на десятки, но никак не 100500. И мне кажется, это не такая частая операция, чтобы из-за нее переносить всю логику в базу.
это как раз в варианте с БЛ на сервере нет этих проблем
if ($entity->user_id == $currentUser->id), в сложном через RBAC if ($user->can('viewEntity', ['entity' => $entity]))Когда при загрузке? Если в момент загрузки страницы, то так не пойдёт, т.к. пока пользователь на странице oldAttributes могут измениться. Так что придётся считать перед изменением и ещё заблокировать строку пока вы на клиенте вычисляете остальные значения полей, чтобы в этот период никто не изменил строку.
а это существенная просадка производительности
прайс, скидка клиента общая, скидка клиента по производителю, скидка клиента по товару, курс валюты. В итоге на вставку одной позиции документа надо поочерёдно сгенерить 5 запросов и обменяться ими php и mysql'ю.
Я уж боюсь представить как бы Вы реализовали selectList для выбора товара с ценой и остатком без использования логики в БД.
$term = 'Material';
$query = Materials::find();
$query->joinWith('curStocks');
$query->where(['warehouse_id' => $document->warehouse_id]);
$query->andWhere(['like', 'name', $term]);
$query->limit(50);
$materials = $query->all();
// foreach по результатам будет в обоих вариантах, независимо от местонахождения бизнес-логики
$data = [];
foreach ($materials as $material) {
$price = getPrice($material, $document);
$data[$material->id] = $material->name . ' - ' . $price;
}
renderSelectList($data);
Вы мне описали имплементацию обработки одной(!) строки позиции
<?php
class Config {
public $db_host = '127.0.0.1';
public $db_username = 'username';
public $db_password = 'password';
public $db_name = 'habr';
}
<?php
require_once 'config.php';
$config = new Config();
$db = new PDO("mysql:host=" . $config->db_host.";dbname=".$config->db_name, $config->db_username, $config->db_password);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$db->exec('SET names utf8');
$db->exec('SET storage_engine=innoDB');
$db->exec("SET sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,ONLY_FULL_GROUP_BY'");
//$cn = isset($_REQUEST['cn'])?$_REQUEST['cn'] * 1:10000;
$material_cn = 10000;
$client_cn = 1000;
$db->exec("SET FOREIGN_KEY_CHECKS=0");
// Документы
$db->exec("DROP TABLE IF EXISTS `docs`");
$db->exec("CREATE TABLE `docs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` date NOT NULL,
`org_id_addr` int(11) NOT NULL, -- Склад
`org_id_client` int(11) NOT NULL,
PRIMARY KEY (`id`)
)");
$db->exec("INSERT `docs` (`id`, `date`, `org_id_addr`, `org_id_client`) VALUES (20515, '2016-10-12', 1, 500)");
// Материалы
$db->exec("DROP TABLE IF EXISTS `materials`");
$db->exec("CREATE TABLE `materials` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`manufact_id` int(11) NOT NULL, -- Производитель
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
)");
$q = $db->prepare("INSERT materials (name, manufact_id) VALUES (CONCAT('Материал товар №', LPAD(:name, 5, '0')), :manufact_id)");
for ($i = 1; $i <= $material_cn; $i++) {
$q->execute(['name' => $i, 'manufact_id' => (($i % 10) + 1)]);
}
// Остатки
$db->exec("DROP TABLE IF EXISTS `cur_stock`");
$db->exec("CREATE TABLE `cur_stock` (
`mat_id` int(11) NOT NULL,
`org_id` int(11) NOT NULL, -- Склад
`kol` decimal(10,3) NOT NULL,
PRIMARY KEY (`org_id`,`mat_id`),
KEY `mat_id` (`mat_id`),
CONSTRAINT `cur_stock_ibfk_1` FOREIGN KEY (`mat_id`) REFERENCES `materials` (`id`)
)");
//echo PHP_EOL.'<br>INSERT INTO cur_stock = '.
$db->exec("INSERT INTO cur_stock (mat_id, org_id, kol)
SELECT m.id mat_id, o.org_id, FLOOR(RAND() * 500) kol
FROM
(SELECT id FROM materials ORDER BY RAND() LIMIT ".floor($material_cn / 1.5).") m
, (SELECT 1 org_id UNION ALL SELECT 2 UNION ALL SELECT 3) o");
// Прайсы клиентов
$db->exec("DROP TABLE IF EXISTS `client_price`");
$db->exec("CREATE TABLE `client_price` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`org_id` int(11) NOT NULL, -- Клиент
`price_type_id` int(11) NOT NULL, -- Тип прайса
`org_id_manufact` int(11) DEFAULT NULL, -- Производитель
`mat_id` int(11) DEFAULT NULL, -- Материал
`discount` decimal(5,2) DEFAULT NULL, -- Скидка
PRIMARY KEY (`id`),
UNIQUE KEY `org_id` (`org_id`,`price_type_id`,`org_id_manufact`,`mat_id`,`discount`),
KEY `price_type_id` (`price_type_id`),
KEY `mat_id` (`mat_id`),
KEY `org_id_manufact` (`org_id_manufact`),
CONSTRAINT `client_price_ibfk_1` FOREIGN KEY (`mat_id`) REFERENCES `materials` (`id`)
)");
// Общие прайсы
$q = $db->prepare("INSERT client_price (org_id, price_type_id) VALUES (:org_id, :price_type_id)");
for ($i = 1; $i <= $client_cn; $i++) {
$q->execute(['org_id' => $i, 'price_type_id' => (($i % 2) + 1)]);
}
// Скидки по производителю
$db->exec("INSERT INTO client_price (org_id, price_type_id, org_id_manufact)
SELECT o.org_id
, FLOOR(1 + RAND() * 3) price_type_id
, m.manufact_id
FROM
(SELECT DISTINCT manufact_id FROM materials) m
, (SELECT DISTINCT org_id FROM client_price ORDER BY CASE WHEN org_id = 500 THEN 1 ELSE 2 END, RAND() LIMIT ".floor($client_cn / 10).") o
ORDER BY RAND()
LIMIT ".floor($client_cn / 2));
// Скидки по материалам
$db->exec("INSERT INTO client_price (org_id, price_type_id, mat_id)
SELECT o.org_id
, FLOOR(1 + RAND() * 3) price_type_id
, m.id
FROM
(SELECT id FROM materials ORDER BY RAND() LIMIT ".floor($material_cn / 10).") m
, (SELECT DISTINCT org_id FROM client_price ORDER BY CASE WHEN org_id = 500 THEN 1 ELSE 2 END, RAND() LIMIT ".floor($client_cn / 10).") o
ORDER BY RAND()
LIMIT ".(floor($client_cn / 10) * floor($material_cn / 10) / 10));
// Курсы валют
$db->exec("DROP TABLE IF EXISTS `exchange_rate`");
$db->exec("CREATE TABLE IF NOT EXISTS `exchange_rate` (
`currency_id` int(11) NOT NULL,
`date` date NOT NULL,
`value` decimal(29,15) NOT NULL,
PRIMARY KEY (`currency_id`,`date`)
)");
$q = $db->prepare("INSERT exchange_rate (currency_id, date, value) VALUES (:currency_id, DATE(NOW()) - INTERVAL :i DAY, :value)");
for ($i = 100; $i >= 0; $i--) {
$q->execute(['currency_id' => 2, 'i' => ($i + 1), 'value' => 100 - ($i / 100)]);
}
// Прайсы
$db->exec("DROP TABLE IF EXISTS prices");
$db->exec("CREATE TABLE `prices` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`price_type_id` int(11) NOT NULL,
`tmc_id` int(11) NOT NULL,
`date` date NOT NULL,
`price` decimal(29,15) NOT NULL,
`currency_id` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
UNIQUE KEY `tmc_id` (`tmc_id`,`price_type_id`,`date`),
KEY `price_type_id` (`price_type_id`),
CONSTRAINT `prices_ibfk_2` FOREIGN KEY (`tmc_id`) REFERENCES `materials` (`id`)
)");
$db->exec("INSERT INTO prices (price_type_id, tmc_id, date, price, currency_id)
SELECT pt.price_type_id, m.id
, d.date
, FLOOR(RAND() * 1000) price
, FLOOR(1 + RAND() * 2) currency_id
FROM materials m
, (SELECT DATE(NOW()) - INTERVAL dd.id MONTH AS date FROM materials dd ORDER BY id LIMIT 10) d -- Даты
, (SELECT 1 price_type_id UNION ALL SELECT 2 UNION ALL SELECT 3) pt -- Типы прайсов
");
// Функция get_price
$db->exec("DROP FUNCTION IF EXISTS get_client_price");
$db->exec("CREATE FUNCTION get_client_price(v_org_id INT, v_mat_id INT, v_date DATE)
RETURNS decimal(29,15) READS SQL DATA
BEGIN
/* версия 00002 */
DECLARE v_price_date date;
DECLARE v_price decimal(29,15);
DECLARE EXIT HANDLER FOR NOT FOUND BEGIN
RETURN NULL;
END;
SET @client_price_type_id := NULL;
SET @client_price_discount := NULL;
SET @client_price_date := NULL;
-- Определяем тип прайса
SELECT price_type_id, discount
INTO @client_price_type_id, @client_price_discount
FROM (
SELECT cp.price_type_id, cp.discount, 10 rule
FROM client_price cp
WHERE cp.org_id = v_org_id
AND cp.mat_id = v_mat_id
AND cp.org_id_manufact IS NULL
UNION ALL
SELECT cp.price_type_id, cp.discount, 50 rule
FROM client_price cp
INNER JOIN materials m ON cp.org_id_manufact = m.manufact_id
WHERE cp.org_id = v_org_id
AND m.id = v_mat_id
AND cp.mat_id IS NULL
UNION ALL
SELECT cp.price_type_id, cp.discount, 100 rule
FROM client_price cp
WHERE cp.org_id = v_org_id
AND cp.mat_id IS NULL
AND cp.org_id_manufact IS NULL
) t
ORDER BY rule
LIMIT 1;
-- Определяем цену
SELECT p.price
* CASE WHEN p.currency_id = 1 THEN 1
ELSE (SELECT value FROM exchange_rate r WHERE p.currency_id = r.currency_id AND r.date <= v_date ORDER BY r.date DESC LIMIT 1)
END
* (100 - IFNULL(@client_price_discount, 0)) / 100 price
, p.date
INTO v_price, @client_price_date
FROM prices p
WHERE p.price_type_id = @client_price_type_id
AND p.tmc_id = v_mat_id
AND p.date <= v_date
ORDER BY p.date DESC
LIMIT 1;
RETURN v_price;
END;
");
$db->exec("SET FOREIGN_KEY_CHECKS=1");
echo sprintf("%.6f", microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]);
<?php
require_once 'config.php';
$config = new Config();
$db = new PDO("mysql:host=" . $config->db_host.";dbname=".$config->db_name, $config->db_username, $config->db_password);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$db->exec('SET names utf8');
$db->exec('SET storage_engine=innoDB');
$db->exec("SET sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,ONLY_FULL_GROUP_BY'");
$term = isset($_REQUEST['term'])?$_REQUEST['term']:'';
$doc_id = isset($_REQUEST['doc_id'])?$_REQUEST['doc_id']:null;
$page = @$_REQUEST['page']*1?$_REQUEST['page']:1;
//$per_page = 1000;
$per_page = @$_REQUEST['page']*1?$_REQUEST['per_page']:50;
$sql = "SELECT SQL_NO_CACHE CONCAT(m.name
, ' - ', IFNULL(TRIM(get_client_price(d.org_id_client, m.id, d.date)) + 0, '')
, ' - ', IFNULL(cs.kol, '')
) name
FROM materials m
LEFT JOIN docs d ON d.id = :doc_id
LEFT JOIN cur_stock cs ON cs.org_id = d.org_id_addr AND m.id = cs.mat_id
WHERE m.name LIKE CONCAT('%', IFNULL(:term, ''), '%')
AND NOW() = NOW()
ORDER BY m.name
";
$sql .= PHP_EOL.'LIMIT '.(($page - 1) * $per_page).', '.$per_page;
$q = $db->prepare($sql);
$q->execute(['doc_id' => $doc_id, 'term' => $term]);
$q->setFetchMode(PDO::FETCH_ASSOC);
$data = $q->fetchAll();
$return = [];
$return['total_count'] = ($page - 1) * $per_page + (sizeof($data) < $per_page?sizeof($data):($per_page+1));
$return['items'] = $data;
$return['time'] = sprintf("%.6f", microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]);
echo json_encode($return);
<?php
require_once 'config.php';
$config = new Config();
$db = new PDO("mysql:host=" . $config->db_host.";dbname=".$config->db_name, $config->db_username, $config->db_password);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$db->exec('SET names utf8');
$db->exec('SET storage_engine=innoDB');
$db->exec("SET sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,ONLY_FULL_GROUP_BY'");
$term = isset($_REQUEST['term'])?$_REQUEST['term']:'';
$doc_id = isset($_REQUEST['doc_id'])?$_REQUEST['doc_id']:null;
$page = @$_REQUEST['page']*1?$_REQUEST['page']:1;
//$per_page = 1000;
$per_page = @$_REQUEST['page']*1?$_REQUEST['per_page']:50;
// Построением sql запросов будет заниматься ОРМ, но для простоты пишу итоговые запросы который сгенерит ОРМ
// Документ
$sql = "SELECT SQL_NO_CACHE * FROM docs WHERE id = :doc_id AND NOW() = NOW()";
$q = $db->prepare($sql);
$q->execute(['doc_id' => $doc_id]);
$doc = $q->fetch();
// Материалы
$sql = "SELECT SQL_NO_CACHE m.id, m.name
, cs.kol
FROM materials m
LEFT JOIN cur_stock cs ON cs.org_id = :org_id AND m.id = cs.mat_id
WHERE m.name LIKE CONCAT('%', :term, '%')
AND NOW() = NOW()";
$sql .= PHP_EOL.'LIMIT '.(($page - 1) * $per_page).', '.$per_page;
$q = $db->prepare($sql);
$q->execute(['org_id' => $doc['org_id_addr'], 'term' => $term]);
$materials = $q->fetchAll();
function get_client_price($mat, $doc){
global $db;
// Определяем скидку по клиенту
// Скидка по товару
$sql = "SELECT SQL_NO_CACHE cp.price_type_id, cp.discount
FROM client_price cp
WHERE cp.org_id = :org_id
AND cp.mat_id = :mat_id
AND cp.org_id_manufact IS NULL
AND NOW() = NOW()";
$q = $db->prepare($sql);
$q->execute(['org_id' => $doc['org_id_client'], 'mat_id' => $mat['id']]);
$r = $q->fetch();
if ($r === false) {
// Скидка по производителю
$sql = "SELECT SQL_NO_CACHE cp.price_type_id, cp.discount
FROM client_price cp
INNER JOIN materials m ON cp.org_id_manufact = m.manufact_id
WHERE cp.org_id = :org_id
AND m.id = :mat_id
AND cp.mat_id IS NULL
AND NOW() = NOW()";
$q = $db->prepare($sql);
$q->execute(['org_id' => $doc['org_id_client'], 'mat_id' => $mat['id']]);
$r = $q->fetch();
if ($r === false) {
// Общая скидка
$sql = "SELECT SQL_NO_CACHE cp.price_type_id, cp.discount
FROM client_price cp
WHERE cp.org_id = :org_id
AND cp.mat_id IS NULL
AND cp.org_id_manufact IS NULL
AND NOW() = NOW()";
$q = $db->prepare($sql);
$q->execute(['org_id' => $doc['org_id_client']]);
$r = $q->fetch();
if ($r === false) {
$r = ['price_type_id' => null, 'discount' => 0];
}
}
}
$client_price_type_id = $r['price_type_id'];
$client_price_discount = $r['discount'];
//exit($doc['org_id_client'].' - '.$client_price_type_id.' - '.$client_price_discount);
// Определяем цену
$sql = "SELECT SQL_NO_CACHE p.price, p.date, p.currency_id
FROM prices p
WHERE p.price_type_id = :client_price_type_id
AND p.tmc_id = :mat_id
AND p.date <= :date
AND NOW() = NOW()
ORDER BY p.date DESC
LIMIT 1";
$q = $db->prepare($sql);
$q->execute(['client_price_type_id' => $client_price_type_id, 'mat_id' => $mat['id'], 'date' => $doc['date']]);
$r = $q->fetch();
if ($r === false) {
return null;
}
$price = (100 - ($client_price_discount?$client_price_discount:0)) * $r['price'] / 100;
// Если Валюта не Рубль, то пересчитаем по курсу на дату
if ($r['currency_id'] != 1) {
$sql = "SELECT SQL_NO_CACHE value FROM exchange_rate r WHERE :currency_id = r.currency_id AND r.date <= :date AND NOW() = NOW() ORDER BY r.date DESC LIMIT 100";
$q = $db->prepare($sql);
$q->execute(['currency_id' => $r['currency_id'], 'date' => $doc['date']]);
//$r = $q->fetch(); $value = $r['value'];
$value = $q->fetchColumn();
$price = $price * $value;
}
return $price;
}
$data = [];
foreach ($materials as $material) {
$price = get_client_price($material, $doc);
$data[] = ['name' => $material['name'].' - '.$price.' - '.$material['kol']];
}
$return = [];
$return['total_count'] = ($page - 1) * $per_page + (sizeof($data) < $per_page?sizeof($data):($per_page+1));
$return['items'] = $data;
$return['time'] = sprintf("%.6f", microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]);
echo json_encode($return);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<div>Per page <input type="number" value="500" id="per_page"></div>
<div>Номер документа <input type="number" value="20515" id="doc_id"></div>
<div>Список из app <select id="sl_app" style="width: 400px;"></select> <span id="time_sl_app"></span></div>
<div>Список из DB <select id="sl_db" style="width: 400px;"></select> <span id="time_sl_db"></span></div>
<script>
$(function(){
$('#sl_db').select2({allowClear: true
, placeholder: "Выберите значение"
, language: 'ru'
, ajax: {url: "get_db_list.php"
, dataType: 'json'
, delay: 250
, data: function (params) {
return {
term: params.term
, page: params.page
, doc_id: $('#doc_id').val()
, per_page: $('#per_page').val()
};
}
, processResults: function (data, params) {
params.page = params.page || 1;
$('#time_sl_db').text(data.time);
return {
results: data.items
, pagination: {
more: (params.page * 50) < data.total_count
}
};
}
}
, templateResult: function(repo) {
if (repo.loading) return repo.text;
return repo.name;
}
, templateSelection: function(repo) {
return repo.name || repo.text;
}
, escapeMarkup: function(markup) {
return markup;
}
});
$('#sl_app').select2({allowClear: true
, placeholder: "Выберите значение"
, language: 'ru'
, ajax: {url: "get_app_list.php"
, dataType: 'json'
, delay: 250
, data: function (params) {
return {
term: params.term
, page: params.page
, doc_id: $('#doc_id').val()
, per_page: $('#per_page').val()
};
}
, processResults: function (data, params) {
params.page = params.page || 1;
$('#time_sl_app').text(data.time);
return {
results: data.items
, pagination: {
more: (params.page * 50) < data.total_count
}
};
}
}
, templateResult: function(repo) {
if (repo.loading) return repo.text;
return repo.name;
}
, templateSelection: function(repo) {
return repo.name || repo.text;
}
, escapeMarkup: function(markup) {
return markup;
}
});
})
</script>
</body>
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.full.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/ru.js"></script>
</html>
Реализация бизнес-логики в MySQL