如何在PostgreSQL数据库中按内容查找所有完全相同的表?
我之前帮客户处理过类似的PostgreSQL大规模表去重排查需求,针对你说的这种表结构完全一致、但内容(含排序)重复的情况,给你几个实用的方案,一步步来:
第一步:先锁定结构完全一致的表(可选但推荐)
虽然你提到所有表结构都相同,但保险起见,可以先通过这个查询确认哪些表的列数、列名完全匹配,避免后续做无用功:
SELECT array_agg(table_name ORDER BY table_name) AS tables_with_same_structure, column_count, column_list FROM ( SELECT table_name, COUNT(*) AS column_count, array_agg(column_name ORDER BY column_name) AS column_list FROM information_schema.columns WHERE table_schema = 'public' -- 替换成你的表所在的schema GROUP BY table_name ) t GROUP BY column_count, column_list HAVING COUNT(*) > 1;
如果结果里所有600张表都在同一个分组里,就可以直接进入下一步了。
第二步:计算每张表的内容哈希(含排序)
核心思路是:给每张表的内容按固定列排序后生成唯一哈希值,内容完全一致的表哈希值必然相同。
首先创建一个临时表来存储每张表的哈希结果:
CREATE TEMP TABLE table_content_hashes ( table_name text PRIMARY KEY, content_hash text NOT NULL );
然后用PL/pgSQL脚本批量遍历所有表,计算哈希:
DO $$ DECLARE rec record; column_list text; hash_sql text; BEGIN -- 获取目标表的列名列表(取第一张表的列,因为结构都一致) SELECT string_agg(column_name, ', ') INTO column_list FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ( SELECT table_name FROM information_schema.columns WHERE table_schema = 'public' LIMIT 1 ) ORDER BY column_name; -- 遍历所有基础表 FOR rec IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' LOOP -- 构造计算哈希的SQL:按所有列排序后聚合,生成MD5哈希 hash_sql := format( 'INSERT INTO table_content_hashes (table_name, content_hash) SELECT %L, md5(cast(array_agg(t ORDER BY %s) as text)) FROM %I.%I t', rec.table_name, column_list, 'public', rec.table_name ); EXECUTE hash_sql; END LOOP; END $$;
第三步:找出内容重复的表
最后查询哈希值相同的表分组,就是你要找的内容完全一致的表:
SELECT content_hash, array_agg(table_name ORDER BY table_name) AS duplicate_tables FROM table_content_hashes GROUP BY content_hash HAVING COUNT(*) > 1;
优化小技巧(针对大表)
如果你的表数据量很大,全表扫描计算哈希会比较慢,可以先通过行数快速排除不可能重复的表:
SELECT reltuples, array_agg(relname ORDER BY relname) AS tables_with_same_row_count FROM pg_stat_user_tables WHERE schemaname = 'public' GROUP BY reltuples HAVING COUNT(*) > 1;
这里的reltuples是PostgreSQL统计的近似行数,能帮你快速缩小范围,只对行数相同的表计算哈希,节省时间。如果需要精确行数,可以替换成(SELECT count(*) FROM public.relname),但大表会比较耗时。
另外,如果你担心md5碰撞的概率(虽然极低),可以换成sha256函数,把md5(...)改成encode(sha256(cast(array_agg(t ORDER BY %s) as bytea)), 'hex')即可。
内容的提问来源于stack exchange,提问作者Dumbo




