Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
-- Триггер для docs
DROP PROCEDURE IF EXISTS docs_trg_proc$
CREATE PROCEDURE docs_trg_proc()
BEGIN
/* версия 00002 */
-- Проверки
DECLARE msg TEXT;
-- Документ закрыт. Изменения запрещены.
SET msg := (SELECT raise_error('Документ закрыт. Изменения запрещены.')
FROM docs_tmp_trg d
WHERE COALESCE(d.new_closed, d.old_closed) = 1 AND d.time = 'B'
AND (
d.type IN ('I', 'D')
OR (d.type = 'U'
AND d.new_closed = 1
AND d.old_closed = 1
-- Если изменился только статус, то проверку не делаем
AND only_col_changed(d.col_changed, 'status_id') IS NULL
)
)
);
-- ...
-- ...
-- ...
-- Заполним Юрлицо из склада, если оно пустое
UPDATE docs_tmp_trg
INNER JOIN organizations ON docs_tmp_trg.new_org_id_addr = organizations.id
SET docs_tmp_trg.new_org_id_ur = organizations.org_id_ur
WHERE docs_tmp_trg.type IN ('I', 'U') AND docs_tmp_trg.time = 'B' AND docs_tmp_trg.new_org_id_ur IS NULL;
-- И проверим что Юрлицо входит в ГО Юрлица
SET msg := (SELECT raise_error('Юрлицо должно входить в ГО Юрлица')
FROM docs_tmp_trg d
WHERE d.time = 'B' AND d.type IN ('I', 'U')
AND d.new_org_id_ur NOT IN (SELECT gr.org_id
FROM pos_org_gr gr
WHERE gr.gr_org_id = (SELECT value FROM sys_vars WHERE name = 'ГО_ЮРЛИЦА')
));
-- Заполним номер документа если он пустой
UPDATE docs_tmp_trg SET new_num = get_auto_num()
WHERE time = 'B' AND type IN ('I') AND new_num IS NULL;
-- ...
-- ...
-- ...
-- Закрытие документа
SELECT close_doc(d.new_id, IF(d.new_closed = 1 AND d.old_closed IS NULL, 1, -1))
INTO msg
FROM docs_tmp_trg d
WHERE d.time = 'A' AND d.type = 'U' AND (d.new_closed = 1 AND d.old_closed IS NULL OR d.new_closed IS NULL AND d.old_closed = 1)
;
END$
ALTER TABLE docs CHANGE closed doc_closed TINYINT(1)
...
SET msg := (SELECT raise_error((SELECT IFNULL(l.local_msg, l.msg)
FROM localizations l
WHERE l.msg = 'Документ закрыт. Изменения запрещены.'
AND local = @user_local)
)
FROM docs_tmp_trg d
WHERE COALESCE(d.new_closed, d.old_closed) = 1 AND d.time = 'B'
...
CREATE PROCEDURE raise_error(p_msg TEXT)
BEGIN
SET @raise_error_msg := COALESCE(p_msg
, (SELECT l.local_msg)
FROM localizations l
WHERE l.msg = p_msg AND local = @user_local
)
, '');
DROP TEMPORARY TABLE IF EXISTS mysql_error_generator;
CREATE TEMPORARY TABLE mysql_error_generator(raise_error VARCHAR(255) unique) engine=MEMORY;
INSERT INTO mysql_error_generator VALUES (IFNULL(p_msg, '')), (IFNULL(p_msg, ''));
END$
DROP TABLE IF EXISTS tmp$
CREATE TABLE IF NOT EXISTS tmp (id int(11))$
INSERT tmp VALUES (1), (NULL)$
CREATE TRIGGER `tmp_bef_ins_trg` BEFORE INSERT ON `tmp` FOR EACH ROW BEGIN CALL raise_error('Ошибка'); END$
CREATE TRIGGER `tmp_bef_upd_trg` BEFORE UPDATE ON `tmp` FOR EACH ROW BEGIN CALL raise_error('Ошибка'); END$
ALTER TABLE tmp CHANGE id id2 int(11)$
SELECT * FROM tmp$
DROP TABLE tmp$
SET @disable_docs_trg := 1;
ALTER TABLE docs ...
DROP ALL TRIGGERS
RECREATE ALL TRIGGERS
INSERT INTO docs ... SELECT .. FROM docs d ... WHERE d.date BETWEEN ...
INSERT INTO doc_pos ... SELECT .. FROM doc_pos .. docs ... WHERE d.date BETWEEN ...
При разработке приложений баз данных я использую очень простую мантру:
- если можно, сделай это с помощью одного оператора SQL;
- если это нельзя сделать с помощью одного оператора SQL, сделай это в PL/SQL;
- если это нельзя сделать в PL/SQL, попытайся использовать хранимую процедуру на языке Java;
- если это нельзя сделать в Java, сделай это в виде внешней процедуры на языке C;
- если это нельзя реализовать в виде внешней процедуры на языке C, надо серьезно подумать, зачем это вообще делать...
кроме того, что нужно разрабатывать еще нужно поддерживать. это очень круто, когда при очередной гениальной идее приходится делать переход между вашими «если».
нескоько простых выборок гораздо ефективней одной с джойнами
$sql = 'SELECT * FROM doc_pos WHERE doc_id = :doc_id';
$sth = $dbh->prepare($sql);
foreach ($conn->query('SELECT * FROM docs') as $row) {
$sth->execute(array(':doc_id' => $row['id']));
while ($r = $sth->fetch()) {
print_r($r);
}
}
$sql = 'SELECT p.*
FROM doc_pos p
INNER JOIN docs d ON p.doc_id = d.id';
foreach ($conn->query($sql) as $row) {
print_r($row);
}
$conn->query('SELECT * FROM docs'), то $sql = 'SELECT * FROM doc_pos', так как, судя по названию таблицы, doc_id != NULL.$sql = 'SELECT * FROM doc_pos WHERE doc_id IN (:doc_id_array)';
SELECT COUNT(*) doc_cn, GROUP_CONCAT(id SEPARATOR ', ')
FROM docs d
WHERE d.date > '2015-05-01';
+--------+
| doc_cn |
+--------+
| 6191 |
+--------+
1 row in set (0.02 sec)
SELECT COUNT(*) pos_doc_cn
FROM doc_pos
WHERE doc_id IN (40465,40651,/* ... 6191 элементов ... */40440,40574)
+------------+
| pos_doc_cn |
+------------+
| 94370 |
+------------+
1 row in set (0.14 sec)
mysql> SELECT COUNT(*) doc_pos_cn FROM docs d INNER JOIN doc_pos p ON d.id = p.doc_id WHERE d.date > '2015-05-01';
+------------+
| doc_pos_cn |
+------------+
| 94370 |
+------------+
1 row in set (0.06 sec)
SET @doc_id := (SELECT id FROM docs WHERE unique_field = :unique_field);
SELECT *
FROM doc_pos
WHERE doc_id = @doc_id
SELECT p.*
FROM doc_pos p
INNER JOIN docs d ON p.doc_id = d.id
WHERE d.unique_field = :unique_field
SELECT * FROM doc_pos
WHERE EXISTS (SELECT NULL
FROM docs
WHERE doc_pos.doc_id = docs.id
)
SELECT ...
FROM t_doc_pos p
INNER JOIN docs d ON p.doc_id = d.id
...
...
WHERE EXISTS (
...
UNION ALL
SELECT NULL
FROM auth_users
INNER JOIN user_docs_access ON user_docs_access.user_id = auth_users.user_id
AND auth_users.conn_id = CONNECTION_ID()
WHERE d.id = user_docs_access.doc_id
AND (get_db_mode() = 'show' OR user_docs_access.edit = 1 AND get_db_mode() = 'edit')</b>
)
...
WHERE EXISTS (
...
UNION ALL
SELECT NULL
FROM auth_users
INNER JOIN user_doc_type_access ON user_doc_type_access.user_id = auth_users.user_id
AND auth_users.conn_id = CONNECTION_ID()
WHERE d.doc_type_id = user_doc_type_access.doc_type_id
AND (get_db_mode() = 'show' OR user_doc_type_access.edit = 1 AND get_db_mode() = 'edit')</b>
)
...
WHERE EXISTS (
...
UNION ALL
SELECT NULL
FROM auth_users
INNER JOIN user_doc_type_n_access ON user_doc_type_n_access.user_id = auth_users.user_id
AND auth_users.conn_id = CONNECTION_ID()
WHERE d.doc_type_id = user_doc_type_n_access.doc_type_id
AND (get_db_mode() = 'show' OR user_doc_type_n_access.edit = 1 AND get_db_mode() = 'edit')</b>
)
...
WHERE EXISTS (
...
UNION ALL
SELECT NULL
FROM auth_users
INNER JOIN user_client_access ON user_client_access.user_id = auth_users.user_id
AND auth_users.conn_id = CONNECTION_ID()
INNER JOIN organizations cl ON cl.id = d.org_id_client -- Соединение с RLS-таблицей
WHERE d.org_id_client = user_client_access.org_id_client
AND (get_db_mode() = 'show' OR user_client_access.edit = 1 AND get_db_mode() = 'edit')</b>\
)
DELIMITER $
CREATE PROCEDURE raise_error(msg TEXT)
BEGIN
SET @raise_error_msg := IFNULL(msg, '');
DROP TEMPORARY TABLE IF EXISTS mysql_error_generator;
CREATE TEMPORARY TABLE mysql_error_generator(raise_error VARCHAR(255) unique) engine=MEMORY;
INSERT INTO mysql_error_generator VALUES (IFNULL(msg, '')), (IFNULL(msg, ''));
END$
class ExPDOException extends PDOException {
public function __construct(PDOException $e, PDO $connection) {
parent::__construct($e->getMessage(), 0, $e->getPrevious());
$this->code = $e->getCode();
$this->errorInfo = $e->errorInfo;
// Пользовательская ошибка
if ($e->getCode() == 23000 && strstr($e->getMessage(), "for key 'raise_error'")) {
$this->code = -20000;
$this->errorInfo[0] = -20000;
$this->errorInfo[1] = -20000;
$sql = 'SELECT @raise_error_msg msg';
$q = $connection->query($sql);
$msg = $q->fetchColumn();
$this->message = $msg;
$this->errorInfo[2] = $msg;
}
}
}
Реализация Row Level Security на MySQL