将 SQL Server 转换为 PostgreSQL
拖入 SSMS 的"生成脚本"写出的包含架构和数据的脚本(.sql 或 .sql.gz,UTF-16 或 UTF-8),即可下载包含相同表、键、索引和数据的 PostgreSQL 脚本。PostgreSQL 无法原样存储的每个值,以及它不支持的每个对象,都会在报告中列出。
已转换 18 行,3 处修正,无拒绝
shop_2024_sqlserver.sql 中的表SQL Server 转储 · 由 SQL Server Management Objects 写出 · utf-8
orders10 行2
11 列 id integer not null customer_id integer not null status varchar(10) not null total numeric(19,4) not null items smallint not null ordered_at timestamp(0) not null shipped_at timestamp(6) note text details text order_ref uuid not null total_with_tax numeric(12,2) customers8 行1
8 列 id integer not null name varchar(100) not null email varchar(200) not null country varchar(2) not null is_active boolean not null signed_up date created_at timestamptz(0) not null row_version bytea
未转换
- 视图 paid_orders
- 触发器 orders_no_negative
每种数据库都用自己的语言编写这些对象。请为 PostgreSQL 重写它们。
-- Written by IntoSQL from a SQL Server dump. Load it with: psql -v ON_ERROR_STOP=1 -f <file>
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET synchronous_commit = off;
SET maintenance_work_mem = '512MB';
CREATE TABLE orders (
id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
customer_id integer NOT NULL,
status varchar(10) DEFAULT 'pending' NOT NULL,
total numeric(19,4) NOT NULL,
items smallint DEFAULT 1 NOT NULL,
ordered_at timestamp(0) NOT NULL,
shipped_at timestamp(6),
note text,
details text,
order_ref uuid DEFAULT (gen_random_uuid()::text) NOT NULL,
total_with_tax numeric(12,2) GENERATED ALWAYS AS ((cast(total * (1.16) AS numeric(12,2)))::numeric(12,2)) STORED,
CONSTRAINT orders_items_check CHECK (items >= 0),
CONSTRAINT orders_check2 CHECK (status = 'cancelled' OR status = 'shipped' OR status = 'paid' OR status = 'pending'),
CONSTRAINT orders_check3 CHECK (total >= (0))
);
CREATE TABLE customers (
id integer GENERATED BY DEFAULT AS IDENTITY NOT NULL,
name varchar(100) NOT NULL,
email varchar(200) NOT NULL,
country varchar(2) DEFAULT 'US' NOT NULL,
is_active boolean DEFAULT true NOT NULL,
signed_up date,
created_at timestamptz(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
row_version bytea
);
COPY customers (id, name, email, country, is_active, signed_up, created_at) FROM stdin;
1 Ana Martínez ana@example.com MX t 2023-01-15 2023-01-15 10:24:00.000000+00:00
2 Zoë Müller zoe@example.de DE t 2023-02-03 2023-02-03 08:11:42.000000+00:00
3 José Peña jose@example.es ES f 2023-03-21 2023-03-21 19:05:17.000000+00:00
4 李雷 lilei@example.cn CN t 2023-05-09 2023-05-09 02:40:00.000000+00:00
5 Noah O'Brien noah@example.ie IE t \N 2023-06-30 14:00:00.000000+00:00
6 Amélie Laurent amelie@example.fr FR t 2024-01-02 2024-01-02 09:30:00.000000+00:00
7 Ravi Patel ravi@example.in IN f 2024-02-29 2024-02-29 23:59:59.000000+00:00
8 Sofia Rossi 🌻 sofia@example.it IT t 2024-04-12 2024-04-12 12:12:12.000000+00:00
\.
COPY orders (id, customer_id, status, total, items, ordered_at, shipped_at, note, details, order_ref) FROM stdin;
1 1 shipped 129.9000 3 2024-03-01T10:00:00.000000 2024-03-02T16:30:00.123456 \N {"gift": false} 0b7a4c2e-3f1d-4e8a-9c55-1a2b3c4d5e6f
2 2 paid 54.0000 1 2024-03-02T11:15:00.000000 \N Gift wrap 🎁 {"gift": true, "wrap": "red"} 1c8b5d3f-4a2e-4f9b-8d66-2b3c4d5e6f70
3 1 cancelled 12.5000 1 2024-03-03T09:45:00.000000 \N Customer changed their mind \N 2d9c6e4a-5b3f-4a0c-9e77-3c4d5e6f7081
4 4 shipped 980.0000 12 2024-03-05T02:10:00.000000 2024-03-07T08:00:00.000000 请小心轻放 {"fragile": true} 3e0d7f5b-6c4a-4b1d-8f88-4d5e6f708192
5 5 pending 45.7500 2 2024-03-08T17:20:00.000000 \N Leave at the door; ring twice \N 4f1e806c-7d5b-4c2e-9a99-5e6f708192a3
6 6 shipped 310.4000 5 2024-03-10T13:00:00.000000 2024-03-11T09:00:00.000000 Fragile: "glass" {"fragile": true} 50f2917d-8e6c-4d3f-8baa-6f708192a3b4
7 8 paid 76.2000 2 2024-03-11T18:45:00.000000 \N \N \N 6103a28e-9f7d-4e40-9cbb-708192a3b4c5
8 2 shipped 19.9900 1 2024-03-12T08:00:00.000000 9999-12-31T23:59:59.999999 Shipped date set to the maximum by an old import \N 7214b39f-a08e-4f51-8dcc-8192a3b4c5d6
9 3 shipped 64.0000 2 2024-03-14T15:30:00.000000 2024-03-15T10:00:00.000000 Two parcels {"parcels": [1, 2]} 8325c4a0-b19f-4062-9edd-92a3b4c5d6e7
10 7 paid 1499.0000 1 2024-03-15T21:05:00.000000 \N C:\\Users\\ravi\\invoice.pdf \N 9436d5b1-c2a0-4173-8fee-a3b4c5d6e7f8
\.
-- Keys, indexes and constraints, after the rows so each builds once.
CREATE UNIQUE INDEX orders_pkey ON orders (id);
CREATE INDEX orders_idx_orders_customer ON orders (customer_id);
CREATE INDEX orders_idx_orders_status_date ON orders (status, ordered_at);
CREATE UNIQUE INDEX customers_pkey ON customers (id);
CREATE UNIQUE INDEX customers_email ON customers (email);
ALTER TABLE orders ADD CONSTRAINT orders_pkey PRIMARY KEY USING INDEX orders_pkey;
ALTER TABLE customers ADD CONSTRAINT customers_pkey PRIMARY KEY USING INDEX customers_pkey;
ALTER TABLE customers ADD CONSTRAINT customers_email UNIQUE USING INDEX customers_email;
ALTER TABLE orders ADD CONSTRAINT orders_fkey FOREIGN KEY (customer_id) REFERENCES customers (id) NOT VALID;
DO $f$ BEGIN
ALTER TABLE orders VALIDATE CONSTRAINT orders_fkey;
EXCEPTION WHEN foreign_key_violation THEN
RAISE WARNING 'foreign key % has rows without a parent: it stays NOT VALID, and new rows are checked', 'orders_fkey';
END $f$;
SELECT setval(pg_get_serial_sequence('orders', 'id'), LEAST(GREATEST(COALESCE((SELECT max(id) FROM orders)::numeric, 0) + 1, 1), 2147483647)::bigint, false);
SELECT setval(pg_get_serial_sequence('customers', 'id'), LEAST(GREATEST(COALESCE((SELECT max(id) FROM customers)::numeric, 0) + 1, 1), 2147483647)::bigint, false);
COMMENT ON TABLE customers IS 'People who bought at least once';
修正
- orders 的 "items" 是无符号的,因此变为 smallint,并带有不能为负数的检查约束。
- orders 的 "shipped_at" 为 datetime2(7),其秒的位数多于 PostgreSQL 能保留的位数,因此改为 timestamp(6),并截断最后几位。
- customers 的 "row_version" 是 SQL Server 自动设置的行版本;转储中没有它的值,因此改为 bytea,并保持为空。
需要检查
- 源数据中有 6 个文本列比较时不区分大小写;PostgreSQL 精确比较,因此唯一键和搜索的行为可能不同。 在需要的列上使用不区分大小写的排序规则或列类型。
- 2 个视图、触发器、例程或事件未转换;报告逐一列出了它们。 请在 PostgreSQL 中手动重新创建它们。
- orders 上的检查约束 check1 无法为 PostgreSQL 写出(function isjson),因此未创建。 如果它重要,请手动编写该检查约束。
- orders 上的索引 idx_orders_pending 使用了无法为 PostgreSQL 写出的表达式(filtered index),因此未创建。 如果查询需要,请手动创建该索引。
文件在这台电脑上转换,永远不会上传。
其他示例:
从 SQL Server 到 PostgreSQL 的变化
每一行是转换器应用的一条规则,以及该规则更改你的数据时在报告中写入的记录。
bit
boolean
bit 存储 1 和 0,即 PostgreSQL 的 true 和 false。
tinyint
smallint,并带有确保非负的 CHECK
SQL Server 的 tinyint 范围是 0 到 255;PostgreSQL 最小的整数类型有符号,因此用检查约束保持其范围。
报告记录: unsigned_widened
char(n), varchar(n), nchar(n), nvarchar(n), varchar(max), text
varchar(n) 和 text
PostgreSQL 的 text 可容纳每个字符,因此 varchar 和 nvarchar 是同一类型。
decimal(p,s), money, smallmoney
numeric(p,s);money 为 numeric(19,4)
保留每一位数字,money 保留四位小数。
datetime, datetime2, smalldatetime
timestamp(p)
datetime 是挂钟读数,与不带时区的 timestamp 相同。
datetime2(7), time(7) and datetimeoffset(7)
秒保留六位小数,第七位截断
PostgreSQL 保留到微秒;第七位(100 纳秒)直接截断,从不四舍五入,因此没有值会进到下一秒或下一天。
报告记录: fraction_cut
datetimeoffset
timestamptz,时刻相同
timestamptz 保留时刻;PostgreSQL 以会话的时区显示,而不是每个值原有的偏移量。
time
time(p)
一天中的时刻相同。
json (SQL Server 2025)
jsonb
JSON 值相同。
uniqueidentifier
uuid
标识符相同,以小写写出。
binary, varbinary, image
bytea
字节仍然是字节。
geometry, geography
bytea,存放 SQL Server 自己的字节
SSMS 以 SQL Server 的序列化格式写出空间值,这些字节会逐字节保留。
报告记录: spatial_as_native
rowversion, timestamp
bytea,空
行版本由 SQL Server 自动设置,SSMS 不会为其写出值。
报告记录: rowversion_empty
sql_variant, hierarchyid, vector
text
PostgreSQL 没有对应的类型;sql_variant 保留每个值的文本,hierarchyid 保留其路径,例如 /1/2/。xml 仍为 xml。
报告记录: type_replaced
collations that ignore case (_CI_)
精确比较
PostgreSQL 精确比较文本,因此在 SQL Server 中不区分大小写的列现在会区分 Ana 和 ana。
报告记录: case_insensitive_collation
IDENTITY
GENERATED BY DEFAULT AS IDENTITY,从最大的 id 之后继续
复制的 id 按原样导入,计数器从最大的 id 之后继续。
CREATE SEQUENCE
CREATE SEQUENCE,选项相同
PostgreSQL 序列的工作方式相同。SSMS 不会写出序列当前到达的位置,因此它从创建时的值开始。
computed columns
生成列,STORED
PostgreSQL 13 到 17 只支持存储的生成列;公式相同,CONCAT 的写法让 NULL 像在 SQL Server 中一样视为空。
报告记录: virtual_as_stored
computed columns with functions PostgreSQL lacks
普通列
无法为 PostgreSQL 写出的公式会留下一个普通列和一条报告记录;SSMS 不会为计算列写出值。
报告记录: generated_not_translated
defaults with functions PostgreSQL lacks, and NEXT VALUE FOR
无默认值
无法为 PostgreSQL 写出的默认值会被省略,并在报告中列出。
报告记录: default_not_translated
CHECK constraints
已转换
检查约束相同,在导入每一行时检查。
CHECK constraints with functions PostgreSQL lacks
不创建
无法为 PostgreSQL 写出的检查约束会被省略,并在报告中列出。
报告记录: check_skipped
CHECK constraints added WITH NOCHECK or disabled
不创建
SQL Server 不执行这些检查约束,因此行可能违反它们。
报告记录: check_not_enforced
filtered and columnstore indexes
不创建
IntoSQL 在列上写出 b-tree 索引;其他索引请在导入后重新创建。
报告记录: index_skipped
foreign keys
外键,在行之后创建
行可以按任意顺序导入,之后由外键检查。
foreign keys to tables the dump doesn't create
不创建
外键无法引用不存在的表。
报告记录: foreign_key_skipped
tables on a partition scheme
单个表,包含每个分区的行
行保持不变;如有需要,请在 PostgreSQL 中重新对该表分区。
报告记录: partitions_dropped
temporal tables (SYSTEM_VERSIONING)
两个表:当前行和历史记录
表及其历史表作为普通表复制;PostgreSQL 不会自动保留历史记录。
报告记录: system_versioning_dropped
NOT NULL columns with values that become NULL
去除 NOT NULL
值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。
报告记录: not_null_relaxed
views, functions, procedures, triggers, synonyms, types and partition functions
在报告中列出
它们的代码用 T-SQL 编写,因此会列出来,由你重新创建。
报告记录: not_converted_objects
scripts without CREATE TABLE (data only)
根据值推断类型
只含数据的脚本只有行,因此类型来自值。
报告记录: no_schema
expressions in INSERT values
NULL
不运行数据库的转换器无法对它们求值。
报告记录: expression_value
types IntoSQL doesn't know
text
文本可以容纳任何值;请在导入后更改类型。
报告记录: unknown_type
dates that aren't valid
NULL
不是日期的值变为 NULL,报告会统计其数量。
报告记录: date_out_of_range
NUL characters in text
删除
PostgreSQL 的 text 不能存储 NUL 字符。该字符会被删除,报告会统计受影响的值。
报告记录: nul_removed
text that isn't valid in its encoding
错误的字节变为替换字符
PostgreSQL 文本必须是有效的 UTF-8。
报告记录: invalid_utf8_replaced
text decoded twice (ñ)
按原样复制
脚本中存储的正是这些字符,因此按原样复制;报告会指出它们。
报告记录: mojibake_suspected
| 在 SQL Server 中 | 在 PostgreSQL 中 | 报告记录 |
|---|---|---|
| bit | booleanbit 存储 1 和 0,即 PostgreSQL 的 true 和 false。 | |
| tinyint | smallint,并带有确保非负的 CHECKSQL Server 的 tinyint 范围是 0 到 255;PostgreSQL 最小的整数类型有符号,因此用检查约束保持其范围。 | unsigned_widened |
| char(n), varchar(n), nchar(n), nvarchar(n), varchar(max), text | varchar(n) 和 textPostgreSQL 的 text 可容纳每个字符,因此 varchar 和 nvarchar 是同一类型。 | |
| decimal(p,s), money, smallmoney | numeric(p,s);money 为 numeric(19,4)保留每一位数字,money 保留四位小数。 | |
| datetime, datetime2, smalldatetime | timestamp(p)datetime 是挂钟读数,与不带时区的 timestamp 相同。 | |
| datetime2(7), time(7) and datetimeoffset(7) | 秒保留六位小数,第七位截断PostgreSQL 保留到微秒;第七位(100 纳秒)直接截断,从不四舍五入,因此没有值会进到下一秒或下一天。 | fraction_cut |
| datetimeoffset | timestamptz,时刻相同timestamptz 保留时刻;PostgreSQL 以会话的时区显示,而不是每个值原有的偏移量。 | |
| time | time(p)一天中的时刻相同。 | |
| json (SQL Server 2025) | jsonbJSON 值相同。 | |
| uniqueidentifier | uuid标识符相同,以小写写出。 | |
| binary, varbinary, image | bytea字节仍然是字节。 | |
| geometry, geography | bytea,存放 SQL Server 自己的字节SSMS 以 SQL Server 的序列化格式写出空间值,这些字节会逐字节保留。 | spatial_as_native |
| rowversion, timestamp | bytea,空行版本由 SQL Server 自动设置,SSMS 不会为其写出值。 | rowversion_empty |
| sql_variant, hierarchyid, vector | textPostgreSQL 没有对应的类型;sql_variant 保留每个值的文本,hierarchyid 保留其路径,例如 /1/2/。xml 仍为 xml。 | type_replaced |
| collations that ignore case (_CI_) | 精确比较PostgreSQL 精确比较文本,因此在 SQL Server 中不区分大小写的列现在会区分 Ana 和 ana。 | case_insensitive_collation |
| IDENTITY | GENERATED BY DEFAULT AS IDENTITY,从最大的 id 之后继续复制的 id 按原样导入,计数器从最大的 id 之后继续。 | |
| CREATE SEQUENCE | CREATE SEQUENCE,选项相同PostgreSQL 序列的工作方式相同。SSMS 不会写出序列当前到达的位置,因此它从创建时的值开始。 | |
| computed columns | 生成列,STOREDPostgreSQL 13 到 17 只支持存储的生成列;公式相同,CONCAT 的写法让 NULL 像在 SQL Server 中一样视为空。 | virtual_as_stored |
| computed columns with functions PostgreSQL lacks | 普通列无法为 PostgreSQL 写出的公式会留下一个普通列和一条报告记录;SSMS 不会为计算列写出值。 | generated_not_translated |
| defaults with functions PostgreSQL lacks, and NEXT VALUE FOR | 无默认值无法为 PostgreSQL 写出的默认值会被省略,并在报告中列出。 | default_not_translated |
| CHECK constraints | 已转换检查约束相同,在导入每一行时检查。 | |
| CHECK constraints with functions PostgreSQL lacks | 不创建无法为 PostgreSQL 写出的检查约束会被省略,并在报告中列出。 | check_skipped |
| CHECK constraints added WITH NOCHECK or disabled | 不创建SQL Server 不执行这些检查约束,因此行可能违反它们。 | check_not_enforced |
| filtered and columnstore indexes | 不创建IntoSQL 在列上写出 b-tree 索引;其他索引请在导入后重新创建。 | index_skipped |
| foreign keys | 外键,在行之后创建行可以按任意顺序导入,之后由外键检查。 | |
| foreign keys to tables the dump doesn't create | 不创建外键无法引用不存在的表。 | foreign_key_skipped |
| tables on a partition scheme | 单个表,包含每个分区的行行保持不变;如有需要,请在 PostgreSQL 中重新对该表分区。 | partitions_dropped |
| temporal tables (SYSTEM_VERSIONING) | 两个表:当前行和历史记录表及其历史表作为普通表复制;PostgreSQL 不会自动保留历史记录。 | system_versioning_dropped |
| NOT NULL columns with values that become NULL | 去除 NOT NULL值必须变为 NULL 的列无法保留 NOT NULL,否则导入会中止。 | not_null_relaxed |
| views, functions, procedures, triggers, synonyms, types and partition functions | 在报告中列出它们的代码用 T-SQL 编写,因此会列出来,由你重新创建。 | not_converted_objects |
| scripts without CREATE TABLE (data only) | 根据值推断类型只含数据的脚本只有行,因此类型来自值。 | no_schema |
| expressions in INSERT values | NULL不运行数据库的转换器无法对它们求值。 | expression_value |
| types IntoSQL doesn't know | text文本可以容纳任何值;请在导入后更改类型。 | unknown_type |
| dates that aren't valid | NULL不是日期的值变为 NULL,报告会统计其数量。 | date_out_of_range |
| NUL characters in text | 删除PostgreSQL 的 text 不能存储 NUL 字符。该字符会被删除,报告会统计受影响的值。 | nul_removed |
| text that isn't valid in its encoding | 错误的字节变为替换字符PostgreSQL 文本必须是有效的 UTF-8。 | invalid_utf8_replaced |
| text decoded twice (ñ) | 按原样复制脚本中存储的正是这些字符,因此按原样复制;报告会指出它们。 | mojibake_suspected |
将脚本导入 PostgreSQL
脚本先创建表、复制行,再添加键、索引和外键,因此导入速度快,并会在遇到第一个错误时停止。
psql -v ON_ERROR_STOP=1 -d mydb -f sql_server.postgresql.sql外键先以 NOT VALID 添加,然后再验证。如果某行指向不存在的行,脚本会告诉你是哪个约束,而不是中途失败。
对于较大的转储,请先创建数据库,然后在服务器所在的机器上用 psql 运行脚本。
IntoSQL Pro 可以通过桌面应用或命令行将文件直接导入 PostgreSQL,无需生成脚本。
如何转储 SQL Server 以获得最佳结果
转换器读取转储中的内容。以下选项能让转储保留更多数据:
- 在 SSMS 中右键单击数据库,依次选择"任务"、"生成脚本",然后选择表。dbatools、mssql-scripter 以及其他基于同一库的工具也会写出相同的脚本。
- 在"高级"中,将"要编写脚本的数据的类型"设为"架构和数据"。只含架构的脚本中,表没有行。
- 以 SSMS 默认的 Unicode 或 UTF-8 保存:两者的读取方式相同,也支持 gzip。
哪些内容不会转换
视图、函数、存储过程、触发器、同义词、规则和安全策略都是用 T-SQL 编写的。它们会按名称在报告中列出,绝不会被悄悄丢弃,以便你为 PostgreSQL 重写。
关于 SQL Server 转 PostgreSQL 的问题
这里没有你要的答案?请通过联系页面给我们留言,会有专人回复。 联系页面
我的转储会被上传吗?
不会。转换器在你的浏览器中运行,分块从磁盘读取转储,因此转储不会离开你的电脑。桌面应用和命令行也在你的电脑上转换。
转储可以多大?
转换器从不把整个转储放进内存:它按范围读取文件两次,边读边写出脚本。几 GB 的转储可以在浏览器、桌面应用和命令行中转换。
它能读取 SSMS 保存的 UTF-16 文件吗?
能。SSMS 默认以 UTF-16 保存脚本,而转换器同样能读取 UTF-16 和 UTF-8,无论是否经过 gzip 压缩,批处理之间的 GO 也能识别。
脚本可以来自哪些 SQL Server 版本?
由 SSMS、dbatools 或 mssql-scripter 从 SQL Server 2017 及更高版本写出的脚本。每个测试脚本都来自 SQL Server 2017、2022 和 2025,并与其源数据逐个单元格比较。
IDENTITY 值会保留吗?
会。IDENTITY 列变为标识列,每一行都保留其 id,下一个 id 从最大的 id 之后继续。序列以相同的选项创建。
money、datetimeoffset 和其他 SQL Server 类型会怎样处理?
bit 变为 boolean,money 变为 numeric(19,4),uniqueidentifier 变为 uuid,xml 仍为 xml,datetimeoffset 变为 timestamptz。datetime2 保留六位秒的小数,第七位截断。不区分大小写的列改为精确比较,报告会说明是哪些列。
存储过程、触发器和视图怎么处理?
它们会按名称在报告中列出,但不会转换,因为每种数据库都用自己的语言编写它们。
脚本可以导入哪些 PostgreSQL 版本?
PostgreSQL 13 到 18。同一个脚本可以在其中每个版本上导入,如有问题会在第一个错误处停止。