![]() |
Задавайте вопросы, мы ответим
Вы не зашли.
Есть таблица, хранящая обновления:
create table if not exists change_log
(
CLID INT AUTO_INCREMENT,
TABLE_NAME CHAR(64),
RECORD_ID CHAR(255),
ACTION_TYPE ENUM('i', 'u', 'd', 'a'),
INSERT_TIME TIMESTAMP
DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
primary key(CLID)
)
ENGINE=MEMORY
#ROW_FORMAT=DYNAMIC
CHARACTER SET cp1251 COLLATE cp1251_general_ci;Из неё требуется выбрать последнее обновление для каждой записи каждой таблицы.
Последнее, во-первых, по timestamp, во-вторых, если timestamp у строк одинаковый, то по порядку вставки.
Пусть есть данные:
+------+------------+-----------+-------------+---------------------+ | CLID | TABLE_NAME | RECORD_ID | ACTION_TYPE | INSERT_TIME | +------+------------+-----------+-------------+---------------------+ | 1 | t1 | r0 | a | 2010-05-17 15:42:34 | | 2 | t1 | r0 | d | 2010-05-17 15:42:45 | | 3 | t2 | r0 | a | 2010-05-17 15:42:45 | | 4 | t2 | r1 | i | 2010-05-17 15:42:45 | +------+------------+-----------+-------------+---------------------+
Такой запрос:
select * from change_log group by TABLE_NAME, RECORD_ID, INSERT_TIME order by INSERT_TIME asc;
Выдаёт:
+------+------------+-----------+-------------+---------------------+ | CLID | TABLE_NAME | RECORD_ID | ACTION_TYPE | INSERT_TIME | +------+------------+-----------+-------------+---------------------+ | 1 | t1 | r0 | a | 2010-05-17 15:42:34 | | 3 | t2 | r0 | a | 2010-05-17 15:42:45 | | 4 | t2 | r1 | i | 2010-05-17 15:42:45 | +------+------------+-----------+-------------+---------------------+
Хотя должен: 2 | t1 | r0 | d, вместо первой записи.
Какой запрос нужно сделать для вывода последних изменений из списка?
Неактивен

SELECT TABLE_NAME, MAX(INSERT_TIME) AS INSERT_TIME, MAX(RECORD_ID) AS RECORD_ID.
Но надо понимать, что этот RECORD_ID может не соответствовать INSERT_TIME,
если он был больший. С другой стороны, если там использовать автоинкремент-
ное поле, то будет работать (т.к. порядок вставки тогда будет учтен).
Неактивен
SELECT TABLE_NAME, MAX(INSERT_TIME) AS INSERT_TIME, MAX(RECORD_ID) AS RECORD_ID.
Но надо понимать, что этот RECORD_ID может не соответствовать INSERT_TIME,
если он был больший.
Не, RECORD_ID не имеет отношения к этой таблице. Это ID записи, которая была изменена в таблице TABLE_NAME.
Т.е., например, RECORD_ID может быть "user@host", а может быть "123" для таблицы t1 и "123" для таблицы t2.
Мне нужно "объединить" все записи с одинаковыми RECORD_ID и одинаковыми TABLE_NAME в одну, у которой будут значения полей последней вставленной записи.
К тому же, пока что не знаю как быть с delete... Ведь, в принципе, если запись была удалена, нельзя её "объединять"?
С другой стороны, если там использовать автоинкремент-
ное поле, то будет работать (т.к. порядок вставки тогда будет учтен).
Хм... По CLID... Да, в принципе... Но почему вместо CLID не использовать INSERT_TIME? Или вместе с CLID, когда INSERT_TIME одинаков для одинаковых ао TABLE_NAME и RECORD_ID записей?
Только вот как это всё в запрос запихнуть - ума не приложу.
Неактивен

эээ… Вы вот это хотите?
SELECT TABLE_NAME, RECORD_ID, MAX(INSERT_TIME) AS INSERT_TIME, MAX(CLID) AS CLID
FROM change_log
GROUP BY TABLE_NAME, RECORD_ID
Неактивен
Не совсем. Все данные те, но как сюда добавить ACTION_TYPE?
SELECT MAX(CLID) AS CLID, TABLE_NAME, RECORD_ID, MAX(INSERT_TIME) AS INSERT_TIME, ACTION_TYPE FROM change_log GROUP BY TABLE_NAME, RECORD_ID; +------+------------+-----------+---------------------+-------------+ | CLID | TABLE_NAME | RECORD_ID | INSERT_TIME | ACTION_TYPE | +------+------------+-----------+---------------------+-------------+ | 2 | t1 | r0 | 2010-05-17 15:42:45 | a | | 3 | t2 | r0 | 2010-05-17 15:42:45 | a | | 4 | t2 | r1 | 2010-05-17 15:42:45 | i | +------+------------+-----------+---------------------+-------------+
В первом - ACTION_TYPE == 'd'. Чего-то я не соображаю...
Отредактированно Артём Н. (17.05.2010 17:50:16)
Неактивен

Нее, выбирать нужно только те строки, по которым группируете, и максимальные
значения. Остальные значения надо добывать присоединением (JOIN) изначальной
таблицы по всем получившимся полям ![]()
Неактивен
А как это сделать?
Понимаю, что у меня совсем с головой туго...
mysql> SELECT
-> MAX(clm.CLID) AS CL_ID, clm.TABLE_NAME, clm.RECORD_ID,
-> MAX(clm.INSERT_TIME) AS INSERT_TIME,
-> clj.ACTION_TYPE
-> FROM change_log clm join change_log clj on CL_ID = clj.CLID
-> GROUP BY TABLE_NAME, RECORD_ID;;
ERROR 1054 (42S22): Unknown column 'CL_ID' in 'on clause'
mysql> SELECT
-> MAX(clm.CLID) AS CL_ID, clm.TABLE_NAME, clm.RECORD_ID,
-> MAX(clm.INSERT_TIME) AS INSERT_TIME,
-> clj.ACTION_TYPE
-> FROM change_log clm, change_log clj
-> where MAX(clm.CLID) = clj.CLID
-> GROUP BY TABLE_NAME, RECORD_ID;;
ERROR 1111 (HY000): Invalid use of group function
И почему плохо через JOIN?
Неактивен
Единственный гарантированно уникальный ID - это CLID.
Неактивен

Аа, CLID уникальный? И, небось, автоинкрементальный. Давайте тогда упростим задачу:
В данном случае из «максимальный CLID» следует «максимальный INSERT_TIME», поэтому
второй можно вообще выкинуть. Тогда у нас остаётся очень простой подзапрос для вы-
борки нужных CLID и общий запрос какой-то такой:
SELECT *
FROM change_log
JOIN (SELECT MAX(CLID) AS CLID FROM change_log GROUP BY TABLE_NAME) subq USING (CLID);
Неактивен
Аа, CLID уникальный? И, небось, автоинкрементальный.
Ну, как следует из вышеприведённого CREATE TABLE - да. ![]()
С небольшой поправкой работает:
SELECT * FROM change_log JOIN (SELECT MAX(CLID) AS CLID FROM change_log GROUP BY TABLE_NAME, RECORD_ID) subq USING (CLID);
Но, конечно, не фига себе запрос... o.O JOIN с подзапросом... Плюс какой-то USING.
И ещё:
1.) А почему плохо через JOIN делать?
2.) Что будет, когда CLID дойдёт до макс. значения?
Неактивен

Хм... надо читать сообщения с начала
Я пытался врубиться в суть, и не заметил ![]()
1) В таком виде — очень даже не плохо. Плохо было в варианте, когда у нас был боль-
шой зверь с большим количеством полей. Для подзапроса надо ключик на (TABLE_NAME, RECORD_ID, CLID).
2) Перестанут вставляться строки ![]()
Неактивен
Спасибо.
С запросом понятно.
Перестанут вставляться строки
А если имеется меньше строк, чем макс. значение для типа ID? Но ID считается не с 0? Не будет ли "сброса" в 0?
Неактивен

Нет, не будет, разумеется. Иначе бы в мире периодически начинались мистические
баги с пересекающимися ID ![]()
Неактивен
Что же тогда с ним делать? Ну, т.е. таблица не будет слишком большой, поскольку ранние записи удаляются в триггере. Но что делать с ID?
Неактивен

Что Вас смущает? Что закончится INT? Поставьте BIGINT UNSIGNED, он не закончится.
Неактивен
Ну, в теории, он может закончиться... Меня смущает то, что количество записей будет сильно несоответствовать значению идентификатора.
Вот если-бы его как-то сбрасывать... Только как это сделать не очень сложно?
Отредактированно Артём Н. (19.05.2010 17:07:01)
Неактивен

Ну, в теории, если Вы будете его инкрементировать, скажем, 1000 раз в секунду, то Вам
его хватит на 2^64 / 1000 / 86400 / 365 = 584942417 лет. Можете не беспокоиться, Ваши
правнуки тоже не будут нести ответственность за переполнение ![]()
Неактивен
Ну, проблемы теоретических правнуков меня не очень-то волнуют. :-\
Меня больше интересует возможно ли сделать так, чтобы не переполнялось?
Неактивен

Можно скидывать счетчик. Но за возникшие проблемы сами будете нести ответственность ![]()
ALTER TABLE tablename AUTO_INCREMENT = 0;
Неактивен
Проблема не в том, как скинуть счётчик, а в том, что при сбросе начнётся отсчёт с нуля.
Но в таблице ещё останутся записи со старыми значениями счётчика.
В итоге... ![]()
Выглядит процедурка, которая заносит в таблицу изменения примерно так:
#
# Tables updation fixer.
#
DROP PROCEDURE IF EXISTS FixTableChange;
CREATE
DEFINER = 'root'@'%'
PROCEDURE FixTableChange(
IN v_table_name CHAR(64),
IN v_record_key CHAR(255),
IN v_action ENUM('i', 'u', 'd', 'a')
)
SQL SECURITY INVOKER
COMMENT 'Служебная. Фиксация обновлений таблиц. Вызывается в триггерах.'
sproc: BEGIN
declare client_refresh_rate INT;
insert into change_log(TABLE_NAME, RECORD_ID, ACTION_TYPE)
values(v_table_name, v_record_key, v_action);
select (REFRESH_TIME + 1) * 2 from self_info into client_refresh_rate;
# Очистка старых значений.
delete from change_log
where (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(INSERT_TIME) >= client_refresh_rate);
END;
/*==============================================================*/
/* Triggers : base_sum */
/*==============================================================*/
DROP TRIGGER IF EXISTS `base_sum_ins`;
DROP TRIGGER IF EXISTS `base_sum_upd`;
DROP TRIGGER IF EXISTS `base_sum_del`;
CREATE
DEFINER = 'root'@'%'
TRIGGER `base_sum_ins` AFTER INSERT ON `base_sum`
FOR EACH ROW
BEGIN
call FixTableChange('base_sum',
CONCAT_WS(';', NEW.ID_CLIENT_TYPE_GROUP, NEW.ID_CAR_TYPE), 'i');
END;И вопрос в том как сделать. Неужели придётся у всех менять ID?
Неактивен

Ненене, меня на это не разведете
Есть правильный способ, а есть Ваш. Я не буду
Вам помогать раскручивать Ваш способ, потому что он не правильный. Правильный —
не менять ID никогда, а когда они закончатся через пол миллиона лет — поменять
один раз руками, перестроив всю таблицу.
Неактивен
Зато, мой способ теоретически правильнее. ![]()
Ведь, по-идее, ID должен кончаться только тогда, когда число записей в таблице равно макс. значению BIGINT.
Сейчас мне пришло в голову только:
1.) Искать дельту между текущим мин. ID и 0.
2.) Обновлять, если требуется, ID всех записей.
3.) Устанавливать autoincrement в 0.
Просто, может, какое стандартное средство есть?
P.S.:
Ну, вообще-то, мне это не принципиально. Поскольку, без этого и так куча проблем.
Вот, например, с CURRENT_USER() фигня. ![]()
Неактивен