跳到正文
IntoSQL 首页

将 SQL Server 转换为 PostgreSQL

拖入 SSMS 的"生成脚本"写出的包含架构和数据的脚本(.sql 或 .sql.gz,UTF-16 或 UTF-8),即可下载包含相同表、键、索引和数据的 PostgreSQL 脚本。PostgreSQL 无法原样存储的每个值,以及它不支持的每个对象,都会在报告中列出。

你的文件
sqlshop_2024_sqlserver.sql示例文件,11 kB
你的数据库

已转换 18 行,3 处修正,无拒绝

显示

shop_2024_sqlserver.sql 中的表SQL Server 转储 · 由 SQL Server Management Objects 写出 · utf-8

  • orders10 行2
    11 列
    idinteger not null
    customer_idinteger not null
    statusvarchar(10) not null
    totalnumeric(19,4) not null
    itemssmallint not null
    ordered_attimestamp(0) not null
    shipped_attimestamp(6)
    notetext
    detailstext
    order_refuuid not null
    total_with_taxnumeric(12,2)
  • customers8 行1
    8 列
    idinteger not null
    namevarchar(100) not null
    emailvarchar(200) not null
    countryvarchar(2) not null
    is_activeboolean not null
    signed_update
    created_attimestamptz(0) not null
    row_versionbytea

未转换

  • 视图 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';

修正

  1. orders 的 "items" 是无符号的,因此变为 smallint,并带有不能为负数的检查约束。
  2. orders 的 "shipped_at" 为 datetime2(7),其秒的位数多于 PostgreSQL 能保留的位数,因此改为 timestamp(6),并截断最后几位。
  3. 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 中报告记录
bitbooleanbit 存储 1 和 0,即 PostgreSQL 的 true 和 false。
tinyintsmallint,并带有确保非负的 CHECKSQL Server 的 tinyint 范围是 0 到 255;PostgreSQL 最小的整数类型有符号,因此用检查约束保持其范围。unsigned_widened
char(n), varchar(n), nchar(n), nvarchar(n), varchar(max), textvarchar(n) 和 textPostgreSQL 的 text 可容纳每个字符,因此 varchar 和 nvarchar 是同一类型。
decimal(p,s), money, smallmoneynumeric(p,s);money 为 numeric(19,4)保留每一位数字,money 保留四位小数。
datetime, datetime2, smalldatetimetimestamp(p)datetime 是挂钟读数,与不带时区的 timestamp 相同。
datetime2(7), time(7) and datetimeoffset(7)秒保留六位小数,第七位截断PostgreSQL 保留到微秒;第七位(100 纳秒)直接截断,从不四舍五入,因此没有值会进到下一秒或下一天。fraction_cut
datetimeoffsettimestamptz,时刻相同timestamptz 保留时刻;PostgreSQL 以会话的时区显示,而不是每个值原有的偏移量。
timetime(p)一天中的时刻相同。
json (SQL Server 2025)jsonbJSON 值相同。
uniqueidentifieruuid标识符相同,以小写写出。
binary, varbinary, imagebytea字节仍然是字节。
geometry, geographybytea,存放 SQL Server 自己的字节SSMS 以 SQL Server 的序列化格式写出空间值,这些字节会逐字节保留。spatial_as_native
rowversion, timestampbytea,空行版本由 SQL Server 自动设置,SSMS 不会为其写出值。rowversion_empty
sql_variant, hierarchyid, vectortextPostgreSQL 没有对应的类型;sql_variant 保留每个值的文本,hierarchyid 保留其路径,例如 /1/2/。xml 仍为 xml。type_replaced
collations that ignore case (_CI_)精确比较PostgreSQL 精确比较文本,因此在 SQL Server 中不区分大小写的列现在会区分 Ana 和 ana。case_insensitive_collation
IDENTITYGENERATED BY DEFAULT AS IDENTITY,从最大的 id 之后继续复制的 id 按原样导入,计数器从最大的 id 之后继续。
CREATE SEQUENCECREATE 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 valuesNULL不运行数据库的转换器无法对它们求值。expression_value
types IntoSQL doesn't knowtext文本可以容纳任何值;请在导入后更改类型。unknown_type
dates that aren't validNULL不是日期的值变为 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。同一个脚本可以在其中每个版本上导入,如有问题会在第一个错误处停止。