![]() |
Задавайте вопросы, мы ответим
Вы не зашли.
Есть таблица, хранящая обновления:
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() фигня.
Неактивен