Как я могу получить хэш всей таблицы в postgresql?
Я бы хотел довольно эффективный способ сконденсировать всю таблицу на значение хеша.
У меня есть инструменты, которые генерируют целые таблицы данных, которые затем могут использоваться для создания дополнительных таблиц и т.д. Я пытаюсь реализовать упрощенную систему сборки для координации прогонов сборки и избежания повторной работы. Я хочу иметь возможность записывать хэши входных таблиц, чтобы потом проверить, изменились ли они. Построение таблицы занимает несколько минут или часов, поэтому приемлемо тратить несколько секунд на хеширование.
Хак, который я использовал, - это просто передать вывод pg_dump в md5sum, но для этого требуется перенос всего дампа таблицы по сети на хэш в локальном поле. В идеале я хотел бы создать хэш на сервере базы данных.
Нахождение хэш-значения строки в postgresql дает мне способ вычисления хеша для строки за раз, которая затем может быть скомбинирована.
Приветствуются любые советы.
Редактировать сообщение, в котором я закончил: ответ tinychen не работал у меня напрямую, потому что я не мог использовать "plpgsql", по-видимому. Когда я реализовал функцию в SQL вместо этого, она работала, но была очень неэффективной для больших таблиц. Поэтому вместо объединения всех хешей строк и последующего хэширования я переключился на "катящийся хеш", где предыдущий хэш объединяется с текстовым представлением строки, а затем хэшируется для создания следующего хэша. Это было намного лучше; по-видимому, работает md5 на коротких строках миллионы лишних раз лучше, чем конкатенация коротких строк в миллионы раз.
create function zz_concat(text, text) returns text as
'select md5($1 || $2);' language 'sql';
create aggregate zz_hashagg(text) (
sfunc = zz_concat,
stype = text,
initcond = '');
Ответы
Ответ 1
просто сделайте так, чтобы создать функцию агрегации хеш-таблицы.
create function pg_concat( text, text ) returns text as '
begin
if $1 isnull then
return $2;
else
return $1 || $2;
end if;
end;' language 'plpgsql';
create function pg_concat_fin(text) returns text as '
begin
return $1;
end;' language 'plpgsql';
create aggregate pg_concat (
basetype = text,
sfunc = pg_concat,
stype = text,
finalfunc = pg_concat_fin);
то вы можете использовать функцию pg_concat для калибровки хэш-значения таблицы.
select md5(pg_concat(md5(CAST((f.*)AS text)))) from f order by id
Ответ 2
Я знаю, что это старый вопрос, однако это мое решение:
SELECT
md5(CAST((array_agg(f.* order by id))AS text)) /* id is a primary key of table (to avoid random sorting) */
FROM
foo f;
Ответ 3
SELECT md5(array_agg(md5((t.*)::varchar))::varchar)
FROM (
SELECT *
FROM my_table
ORDER BY 1
) AS t
Ответ 4
Что касается алгоритма, вы можете XOR все отдельные хеши MD5 или объединить их и хеш-конкатенацию.
Если вы хотите сделать это полностью на стороне сервера, вы, вероятно, должны создать свою собственную функцию агрегации, которую вы могли бы вызвать.
select my_table_hash(md5(CAST((f.*)AS text)) from f order by id
В качестве промежуточного шага вместо копирования всей таблицы клиенту вы можете просто выбрать результаты MD5 для всех строк и запустить их через md5sum.
В любом случае вам нужно установить фиксированный порядок сортировки, иначе вы можете получить разные контрольные суммы даже для тех же данных.
Ответ 5
У меня было аналогичное требование, которое можно было использовать при тестировании решения для репликации специализированных таблиц.
@Революционное решение MD5 (которое он приложил к вопросу) кажется довольно эффективным, но было несколько ловушек, которые сбивали меня с толку.
Первый (упомянутый в некоторых других ответах) заключается в том, что вам необходимо убедиться, что агрегат выполняется в известном порядке по таблице, которую вы проверяете. Синтаксис для этого - например.
select zz_hashagg(CAST((example.*)AS text) order by id) from example;
Обратите внимание, что order by
находится внутри агрегата.
Во-вторых, использование CAST((example.*)AS text
не даст одинаковых результатов для двух таблиц с тем же содержимым столбца, если только столбцы не были созданы в том же порядке. В моем случае это не гарантировалось, поэтому, чтобы получить истинное сравнение, мне пришлось перечислить столбцы отдельно, например:
select zz_hashagg(CAST((example.id, example.a, example.c)AS text) order by id) from example;
Для полноты (в случае, если последующее редактирование должно удалить его), вот определение zz_hashagg из вопроса @Ben:
create function zz_concat(text, text) returns text as
'select md5($1 || $2);' language 'sql';
create aggregate zz_hashagg(text) (
sfunc = zz_concat,
stype = text,
initcond = '');
Ответ 6
Отличные ответы.
В случае, если кто-то не хочет использовать функции агрегации, но поддерживает поддержку таблиц размером в несколько GiB, вы можете использовать это, у которого мало шансов на производительность при наилучших ответах в случае больших таблиц.
CREATE OR REPLACE FUNCTION table_md5(
table_name CHARACTER VARYING
, VARIADIC order_key_columns CHARACTER VARYING [])
RETURNS CHARACTER VARYING AS $$
DECLARE
order_key_columns_list CHARACTER VARYING;
query CHARACTER VARYING;
first BOOLEAN;
i SMALLINT;
working_cursor REFCURSOR;
working_row_md5 CHARACTER VARYING;
partial_md5_so_far CHARACTER VARYING;
BEGIN
order_key_columns_list := '';
first := TRUE;
FOR i IN 1..array_length(order_key_columns, 1) LOOP
IF first THEN
first := FALSE;
ELSE
order_key_columns_list := order_key_columns_list || ', ';
END IF;
order_key_columns_list := order_key_columns_list || order_key_columns[i];
END LOOP;
query := (
'SELECT ' ||
'md5(CAST(t.* AS TEXT)) ' ||
'FROM (' ||
'SELECT * FROM ' || table_name || ' ' ||
'ORDER BY ' || order_key_columns_list ||
') t');
OPEN working_cursor FOR EXECUTE (query);
-- RAISE NOTICE 'opened cursor for query: ''%''', query;
first := TRUE;
LOOP
FETCH working_cursor INTO working_row_md5;
EXIT WHEN NOT FOUND;
IF first THEN
SELECT working_row_md5 INTO partial_md5_so_far;
ELSE
SELECT md5(working_row_md5 || partial_md5_so_far)
INTO partial_md5_so_far;
END IF;
-- RAISE NOTICE 'partial md5 so far: %', partial_md5_so_far;
END LOOP;
-- RAISE NOTICE 'final md5: %', partial_md5_so_far;
RETURN partial_md5_so_far :: CHARACTER VARYING;
END;
$$ LANGUAGE plpgsql;
Используется как:
SELECT table_md5(
'table_name', 'sorting_col_0', 'sorting_col_1', ..., 'sorting_col_n'
);