惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

AI
AI
Cloudbric
Cloudbric
Schneier on Security
Schneier on Security
V2EX - 技术
V2EX - 技术
N
News and Events Feed by Topic
Hacker News: Ask HN
Hacker News: Ask HN
L
LINUX DO - 最新话题
V
Visual Studio Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
W
WeLiveSecurity
博客园 - 司徒正美
Jina AI
Jina AI
S
Secure Thoughts
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 三生石上(FineUI控件)
The Last Watchdog
The Last Watchdog
S
Security @ Cisco Blogs
S
SegmentFault 最新的问题
Attack and Defense Labs
Attack and Defense Labs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
SecWiki News
SecWiki News
Google DeepMind News
Google DeepMind News
T
Troy Hunt's Blog
H
Heimdal Security Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
N
News and Events Feed by Topic
雷峰网
雷峰网
Last Week in AI
Last Week in AI
IT之家
IT之家
Project Zero
Project Zero
O
OpenAI News
腾讯CDC
The Hacker News
The Hacker News
L
Lohrmann on Cybersecurity
T
Tenable Blog
博客园_首页
C
Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
S
Schneier on Security
Scott Helme
Scott Helme
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
人人都是产品经理
人人都是产品经理
P
Privacy International News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

暗无天日

读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日 读 — GitHub Trending 里的 Claude Code 技能包 读 — Prompt Caching 省钱指南 TIL: Emacs 中那些跟鼠标配合的冷门快捷键 - 暗无天日 读:Anvil——把 Emacs 变成 AI 的工具服务器 读:Emacs 代码折叠终极指南 - 暗无天日 读:Clojure 搭车客指南 - 暗无天日 git推送失败后恢复仓库损坏的完整记录 - 暗无天日 多智能体系统的两个有效模式——以及对 Claude Code 用户的启示 - 暗无天日 用 Org Babel 写 Literate 博文:扩展执行 + 定制导出 proced:Emacs 内置的进程查看器 - 暗无天日 从 proced 定制中学到的 Elisp 模式 读:让 Emacs proced 在 macOS 上显示 CPU 和内存 异步编程的函数着色税 - 暗无天日 链式调用的代价:JavaScript 和 Clojure 的共同教训 - 暗无天日 hyperfine:命令行基准测试工具 - 暗无天日 管道中的变量去哪了?——子 shell 作用域陷阱 - 暗无天日 开源包装器的信任陷阱:四个危险信号 - 暗无天日 程序员愿意为 AI 写文档,却不愿为同事写 - 暗无天日 mktemp: Shell 脚本中临时文件的安全陷阱与最佳实践 - 暗无天日 WSL9x —— 在 Windows 9x 里跑 Linux 内核 6.19 用 ox.el 做你想做的事 —— org-export 高级编程指南 读:Hot-wiring the Lisp Machine —— 用纯 Elisp 构建零依赖的 Org 静态站点生成器 Elisp 性能优化的六个实战教训 - 暗无天日 fcitx5 下 Emacs 无法切换输入法的排查 - 暗无天日 ERT 测试交互命令的三种方式 - 暗无天日 SEM Assistant: 当 Elisp 守护进程遇上 LLM 用 dmsg 给 Elisp 加上结构化调试日志 用 org-habit 追踪非每日习惯 - 暗无天日 Clojure X-Men:当编程语言特性变成超能力 - 暗无天日 TIL: 用 diff-hl 在 fringe 中显示 git 变更 读:llm-test —— 用 LLM agent 驱动 Emacs 测试 TIL: AI 时代的橡皮鸭调试 - 暗无天日 fcitx 启动后键盘输入卡顿的排查 - 暗无天日 TIL: 早期网页的图片热区导航 - 暗无天日 读 Seeing the Whole System 用 Emacs 自动生成每周链接推荐 - 暗无天日 读:ASCII control characters in my terminal 读 What to learn - 暗无天日 Lisp 的括号之痛——一个愚人节玩笑揭开的老伤疤 - 暗无天日 一本书该"线性读"还是"并行读" - 暗无天日 读 How to Monetize a Blog:一篇伪装成变现指南的讽刺文 Python Mock 第三方依赖的四种策略 - 暗无天日 Emacs Lisp 热重载实用指南 - 暗无天日 Prot 的 Emacs 配置哲学 - 暗无天日 TIL: 从直播对谈中学到的三个 Emacs 技巧 - 暗无天日 TIL: 自动使用项目虚拟环境的 Python - 暗无天日 TIL: 让 Help buffer 自动获得焦点 一条命令让本地开发用上 HTTPS —— slim 工具介绍 用 fsck 检查和修复 Linux 文件系统 排查Linux进程"卡死"实战:从strace到gdb全流程 - 暗无天日 PostgreSQL 索引:从基础到你可能不知道的高级用法 - 暗无天日 用 .pdbrc 自定义 Python 调试器 ANSI 转义码的标准化现状 - 暗无天日 终端程序的潜规则 - 暗无天日 PARA Org-mode 测试配置 - 暗无天日 AI越强越辣鸡?控制论说这是必然的 - 暗无天日 AI 越强越需要你盯着——反馈循环实操指南 - 暗无天日 你的AI代理正在偷你的密钥——四种你没想到的泄露通道 - 暗无天日 LLM 在 DevOps 中的三种角色 - 暗无天日 写作风格的反建议 - 暗无天日 反驳本质复杂性——Dan Luu 论为什么《没有银弹》错了 - 暗无天日 文件充满了危险——Dan Luu 谈文件系统的可靠性陷阱 - 暗无天日 AI 时代的 PARA 方法:用 Org-mode 和 AI 打造个人知识管理系统 Linux 数据去重学习笔记 - 暗无天日 创建跨平台 ZIP 文件的隐藏陷阱:Extra Field - 暗无天日 X11 Forwarding 排障指南 - 暗无天日 IP欺骗端口扫描:当别人冒充你去扫描别人 - 暗无天日 Linux 输入栈全景解析:从硬件按键到屏幕响应 - 暗无天日 Unix 系统中那些被埋没的配置开关——以 FontConfig 为例 - 暗无天日 在Linux上限制儿童使用电脑 - 暗无天日 GIF不仅仅是一种图片格式——用GIF流做些奇怪的事 - 暗无天日 Leiningen 学习笔记:Clojure 项目构建与管理从入门到实战配置 - 暗无天日 Google SRE Book 读书笔记 - 暗无天日 yes 管道 head 发生了什么 - 暗无天日 为什么 nohup 在 crontab 中不起作用 Bash中的Indirection与Nameref - 暗无天日 Linux PAM 简介 - 暗无天日 从Linux ISO文件启动计算机 - 暗无天日 用 Bash 打造一个Screen Locker 用GitHub Actions自动构建EGO博客 - 暗无天日 blocking I/O 的作用 - 暗无天日 mobileog 手机端同步提示Error:2 No such file 的解决方法 回收 WSL2 VHDX 文件占用空间 使用 org-mode columnview 生成任务列表 - 暗无天日 Emacs 作为 MPD 客户端 - 暗无天日 移动文件路径却不破坏org file link的方法 - 暗无天日 如何合理的导出help link 成HTML - 暗无天日 笑话理解之Biology - 暗无天日
读:PostgreSQL 随机测试数据生成——从快速造数到自动化填充 - 暗无天日
2026-05-05 · via 暗无天日

开篇

PostgreSQL 里造随机测试数据,有两条路。

一条轻量:几个内置函数拼一条 INSERT … SELECT,几分钟搞定一张表。适合开发阶段快速验证。

另一条完整:用 PL/pgSQL 写一个 DO 块(匿名块),动态读取 information_schema 里的列定义,根据每列的数据类型自动生成对应值——不管表结构怎么变,代码都能自动适配。适合需要反复填充多张表的场景。

DZone 上有一篇教程讲的是后一种方案。这篇把两条路都过一遍。

随机数的几个基本函数

PostgreSQL 的 random() 返回 0 到 1 之间的浮点数。配合 floor() 就能生成整数:

SELECT floor(random() * 100 + 1)::int AS num;

执行结果:

 num
-----
  88

generate_series() 用来生成连续的行,是批量造数据的核心工具:

SELECT generate_series(1, 10) AS i;

执行结果:

 i
----
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10

从数组中随机取一个值,用来造枚举型字段(如姓名、状态):

SELECT (ARRAY['张三', '李四', '王五', '赵六'])[floor(random() * 4 + 1)] AS name;

执行结果:

 name
------
 王五

随机日期,用日期加上随机天数:

SELECT '2020-01-01'::date + floor(random() * 365 * 5)::int AS rand_date;

执行结果:

 rand_date
------------
 2024-01-22

UUID 用 gen_random_uuid() (PostgreSQL 13+ 内置,老版本需要安装 pgcrypto 扩展):

SELECT gen_random_uuid() AS uid;

执行结果:

 uid
--------------------------------------
 7126489b-a7ae-4a1c-a02c-fea47513560a

合体技:一条 INSERT 搞定

把上面的函数组合起来,用 INSERT INTO ... SELECT 一次性插入 100 条随机数据:

CREATE TABLE IF NOT EXISTS test_users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50),
    amount NUMERIC(10,2),
    status BOOLEAN,
    created_at DATE
);

INSERT INTO test_users (id, name, amount, status, created_at)
SELECT
    i,
    (ARRAY['张三', '李四', '王五', '赵六'])[floor(random() * 4 + 1)],
    round((random() * 10000)::numeric, 2),
    random() > 0.5,
    '2020-01-01'::date + floor(random() * 365 * 5)::int
FROM generate_series(1, 100) AS i;

查询验证:

SELECT count(*), count(DISTINCT name), min(created_at), max(created_at) FROM test_users;

执行结果:

 count | count |    min    |    max
-------+-------+-----------+-----------
   100 |     4 | 2020-01-16 | 2024-12-27

重现:固定随机种子

上面每次执行得到的数据都不同。如果需要在回归测试中重现相同数据,用 setseed() 固定随机种子:

SELECT setseed(0.5);

setseed() 接受 [-1, 1] 范围内的参数,调用后当前会话的 random() 会生成确定序列。把 SELECT setseed() 放在 INSERT 之前,相同种子每次产生相同结果。

PL/pgSQL 动态方案:自动适配任意表结构

前面那种写法,每张表都得手写 INSERT 和随机值表达式。表一多就烦了。

原文的方案是写一段 PL/pgSQL 匿名块(DO 块),执行完就丢弃,不保存为函数或过程。它会动态读取表的列定义,根据列类型自动构造 INSERT 语句,不管表结构怎么变都能填。

先准备一张表

假设有这样一张表:

CREATE TABLE IF NOT EXISTS test_schema.test_tab2 (
    id BIGINT NOT NULL,
    fname VARCHAR(50),
    lname VARCHAR(50),
    create_date DATE,
    status BOOLEAN,
    CONSTRAINT test_tab1_pkey PRIMARY KEY (id)
);

完整 DO 块

下面这个 DO 块,指定模式名和表名后,自动从 information_schema 读出列定义,根据每列的数据类型生成对应的随机值,插入指定行数:

DO $$
DECLARE
    rec_count INTEGER := 10;
    col RECORD;
    col_list TEXT := '';
    val_list TEXT := '';
    sql_stmt TEXT;
    i INTEGER;
    tbl_schema TEXT := 'test_schema';
    tbl_name TEXT := 'test_tab2';
    random_date DATE;
    random_status BOOLEAN;
BEGIN
        FOR col IN
        SELECT column_name, data_type
        FROM information_schema.columns
        WHERE table_schema = tbl_schema
          AND table_name = tbl_name
        ORDER BY ordinal_position
    LOOP
        col_list := col_list || col.column_name || ', ';
    END LOOP;
    col_list := left(col_list, length(col_list) - 2);

        FOR i IN 1..rec_count LOOP
        val_list := '';

        FOR col IN
            SELECT column_name, data_type
            FROM information_schema.columns
            WHERE table_schema = tbl_schema
              AND table_name = tbl_name
            ORDER BY ordinal_position
        LOOP
            CASE col.data_type
                WHEN 'bigint' THEN
                    val_list := val_list || i || ', ';
                WHEN 'character varying' THEN
                    val_list := val_list || quote_literal(col.column_name || '_' || i) || ', ';
                WHEN 'text' THEN
                    val_list := val_list || quote_literal(col.column_name || '_' || i) || ', ';
                WHEN 'date' THEN
                    random_date := '2000-01-01'::date
                                   + trunc(random() * 366 * 10)::int;
                    val_list := val_list || quote_literal(random_date) || ', ';
                WHEN 'boolean' THEN
                    random_status := (i % 2 = 0);
                    val_list := val_list || random_status || ', ';
                WHEN 'uuid' THEN
                    val_list := val_list || 'gen_random_uuid(), ';
                ELSE
                    val_list := val_list || 'NULL, ';
            END CASE;
        END LOOP;

        val_list := left(val_list, length(val_list) - 2);

        sql_stmt := format(
            'INSERT INTO %I.%I (%s) VALUES (%s);',
            tbl_schema, tbl_name,
            col_list, val_list
        );

        RAISE NOTICE 'Executing: %', sql_stmt;
        EXECUTE sql_stmt;
    END LOOP;
END $$;

代码做了什么

这个 DO 块的核心逻辑分三层:

收集列名

第一次遍历 information_schema 把所有列名拼成 col_list ,形如 id, fname, lname, create_date, status 。这部分跟具体数据类型无关,只取列名。

按类型分发

第二次遍历对每一列用 CASE col.data_type 判断数据类型,生成对应的随机值:

数据类型 生成方式 示例结果
bigint 用循环变量 i 直接赋值 1, 2, 3…
character varying / text 列名加行号, quote_literal() 自动加引号 'fname_1', 'fname_2'
date 基准日期加随机天数 '2003-07-15'
boolean 偶数行 TRUE,奇数行 FALSE TRUE / FALSE
uuid 调用 gen_random_uuid() 3e7c8e1a-…
未识别的类型 NULL NULL

quote_literal() 是一个实用函数,给字符串值自动加上单引号并转义特殊字符,避免拼 SQL 时出现引号错误。

动态执行

每次内层循环结束后,用 format() 把列名和值拼成完整的 INSERT 语句, %I 占位符会自动给标识符加双引号(防止 SQL 注入)。然后用 EXECUTE 执行这条动态 SQL。同时用 RAISE NOTICE 打印生成的 SQL,方便调试。

执行结果

执行后查询:

SELECT * FROM test_schema.test_tab2;

验证结果:

 id |  fname   |  lname   | create_date | status
----+----------+----------+-------------+--------
  1 | fname_1  | lname_1  | 2009-07-12  | f
  2 | fname_2  | lname_2  | 2005-08-08  | t
  3 | fname_3  | lname_3  | 2005-02-22  | f
  4 | fname_4  | lname_4  | 2004-05-25  | t
  5 | fname_5  | lname_5  | 2008-03-23  | f
  6 | fname_6  | lname_6  | 2005-03-17  | t
  7 | fname_7  | lname_7  | 2008-01-05  | f
  8 | fname_8  | lname_8  | 2005-11-17  | t
  9 | fname_9  | lname_9  | 2005-01-04  | f
 10 | fname_10 | lname_10 | 2006-05-23  | t
(10 rows)

两条路怎么选

维度 内置函数(轻量方案) PL/pgSQL(自动方案)
上手成本 低,几个函数组合就行 高,需要理解 DO 块和 PL/pgSQL
每张表的工作量 手写 INSERT,每张表几分钟 改 DO 块前两行的表名和模式名就行
灵活度 每列可以自由定制 值生成逻辑由 data_type 决定,定制需改 CASE
适合场景 一两张表,临时造数据 多张表,反复需要填充测试数据
依赖 不需要额外扩展 需要 pgcrypto 扩展(9.x 老版本才需要)

选哪条路取决于你有几张表。一两张表, generate_series() 加几个函数几分钟就写完了。五张十张表,或者需要反复重建测试数据,PL/pgSQL 方案值得投资。