[pol@fedora data]$ lsb_release -a
LSB Version: :core-4.1-amd64:core-4.1-noarch
Distributor ID: Fedora
Description: Fedora release 34 (Thirty Four)
Release: 34
Codename: ThirtyFour
我正在尝试将示例数据库文件从 MS SQL Server 转换为 PostgreSQL。
所以,我有两个无法解决的小问题。
shipname NVARCHAR(40) NOT NULL,
那是
(总是) 两个空格
标识符(即字段名称) - 始终 [az] - 小写字母
后跟未知数量的空格
后跟 NVARCHAR(xy) NOT NULL或者它后面可能是 NVARCHAR(xy) NULL
我想把它变成
shipname TEXT NOT NULL CHECK (LENGTH(shipname) <= xy),
或者
shipname TEXT NULL,
到目前为止我所拥有的:
sed 's/^ [a-z]+[ ]+NVARCHAR([0-9]+) NOT NULL/TEXT NOT NULL CHECK \(LENGTH\((\1) <= (\2)\)/g'
所以,
^
是字符串的开头后面跟两个空格
后面是我的字段名称 [az]+
后面跟着一个任意的编号。空格 [ ]+
NVARCHAR([0-9]+)
并代入
TEXT
后跟 NOT NULL,然后 CHECK(LENGTH(xy) - 反向引用 1 - <= 反向引用 2...
我已经尝试了上述的各种排列和组合,但似乎没有什么对我有用。
[pol@fedora data]$ sed 's/^ [a-z]+[ ]+NVARCHAR([0-9]+) NOT NULL/TEXT NOT NULL CHECK \(LENGTH\((\1) <= (\2)\)/g'
sed: -e expression #1, char 87: invalid reference \2 on `s' command's RHS
获取无效的反向引用...
理想情况下,我强调理想地,如果 NVARCHAR(xy) 后面的字符串是NULL
并且不是 NOT NULL
,我不想进行任何长度检查 - 因为采用 NULL 的长度是没有意义的...这是条件行为 - 不确定在正则表达式中是否可能......
ps 认为这是微不足道的。
有这样的数据:
N'Strada Provinciale 1234', N'Reggio Emilia', NULL, N'10289', N'Italy');
我想将 更改N'
为简单的撇号'
(这N'
是 SQL Server 的事情),但我不想将 更改NULL
为空字符串,或更糟ULL
- 所以我尝试:
[pol@fedora data]$ sed 's/N\'\'/g TSQLV5.sql
但得到
sed: -e expression #1, char 7: unterminated `s' command
我知道我已经使用了sed
很多,但愿意接受任何awk
可以执行所需任务的命令。
答案1
既然你使用fedora
了,GNU sed
那么这应该可以工作:
s=" shipname NVARCHAR(40) NOT NULL,"
echo "$s" | sed -E '/NOT/{s/^ ([[:lower:]]+)\s*NVARCHAR\(([[:digit:]]+)\) NOT NULL,$/\1 TEXT NOT NULL CHECK \(LENGTH\(\1\) <= \2\),/;q0} ; s/^ ([[:lower:]]+)/\1 TEXT NULL,/'
这模拟了一个假的 if。
if
:
在 db 结构中找到a NOT
( ),然后执行第一个 sed 命令,然后退出 ( ) 而不执行第二条语句。/NOT/
q0
else
:
没有NOT
找到关键字,执行第二个实例。
对于第二个要求:
sed "s/N'/'/g"
全局搜索N'
并将其替换为 only '
。我发现'
与命令行分隔"
符交换很有用sed
,可以使其更干净,而无需进行大量转义。
将第一个放入sed
文件中:
#!/bin/sed -Ef
# If a NOT is found execute this:
# capture the column name and the value of this
/NOT/ {
s/^ ([[:lower:]]+)\s*NVARCHAR\(([[:digit:]]+)\) NOT NULL,$/\1 TEXT NOT NULL CHECK \(LENGTH\(\1\) <= \2\),/
# Quit without execute the other statement
q0
}
# Else: If we are here then the database
# structure does not contains a length for the column;
# so it should be NULL
s/^ ([[:lower:]]+)/\1 TEXT NULL,/
该{
命令用于将更多sed
命令组合在一起。
这q
是quit
命令,用于sed
退出。sed
如果第一次测试成功,我在这里使用它在遇到最后一行之前强制退出。
答案2
您已经得到了答案,但我想添加您自己的方法中出现的问题,这样您就可以从中学习,而不仅仅是复制一些解决方案:
- 您使用扩展正则表达式,但忘记提供该
-E
选项sed
。 - 您想要重用该标识符,但没有将其括在
()
- 您似乎将 ERE
()
组与文字组混合在一起。你可能的意思是sed -E 's/^ ([a-z]+)[ ]+NVARCHAR\(([0-9]+)\) NOT NULL/TEXT NOT NULL CHECK \(LENGTH\((\1) <= (\2)\)/g'
- 直到空格的第一部分不会在替换中显示。您还需要将其分组并在替换时将其用作参考:
sed -E 's/^( ([a-z]+)[ ]+)NVARCHAR\(([0-9]+)\) NOT NULL/\1TEXT NOT NULL CHECK \(LENGTH\((\2) <= (\3)\)/g'
[ ]+
与 相同+
。这不是错误,但会使阅读更加混乱。- 该
g
选项是多余的。对于像^
或那样的锚点,$
不可能进行多次替换。 - 您可以通过设置
NOT
可选选项来避免多个表达式: `sed -E 's/^( ([az]+) +)NVARCHAR(([0-9]+)) (NOT )?NULL/\1TEXT \4NULL CHECK (长度((\2) <= (\3))/' - 另一方面,如果您想省略检查,可以通过单独的替换来实现:
s/^( [a-z]+ +)NVARCHAR\(([0-9]+)\) NULL/\1TEXT NULL/
- 您
s/N\'\'/g
错过了搜索模式和替换之间的分隔符:s/N\'/\'/g
所以你最终会得到
sed -E 's/^( ([a-z]+) +)NVARCHAR\(([0-9]+)\) NOT NULL/\1TEXT NOT NULL CHECK \(LENGTH\((\2) <= (\3)\)/
s/^( [a-z]+ +)NVARCHAR\(([0-9]+)\) NULL/\1TEXT NULL/
s/N\'/\'/g'
答案3
sed
对于某些任务来说非常有用,但其他一些任务需要功能齐全的语言,例如awk
or perl
,带有条件和 printf 等。最好是一种读起来不像正则表达式和 RPN 计算器的可怕混合体的语言:-)。
#!/usr/bin/perl
use strict;
while(<>) {
# print verbatim any lines that don't define an identifier
unless (m/^\s+\S/) { print; next };
# print a blank line before certain identifiers
print "\n" if m/birthdate|address|phone/;
# various regex transformations for IDENTITY and VARCHAR fields
s/\s+NOT NULL IDENTITY/ GENERATED BY DEFAULT AS IDENTITY/;
s/([[:lower:]]+)\s+NVARCHAR\((\d+)\) NOT NULL/$1 TEXT NOT NULL CHECK (LENGTH($1) <= $2)/;
s/\s+NVARCHAR\((\d+)\)\s+NULL/ TEXT NULL/;
# remove length checks from NULL definitions
s/\s+CHECK.*/,/ if /(?<!NOT) NULL/;
# add a comma at the end of the mgrid line if it's not there
s/\s*$/,/ if /mgrid/ && ! /,\s*$/;
# hacky crap to nicely format "TYPE (NOT )?NULL" output.
my @F = split;
my $identifier = shift @F;
my $type = shift @F;
$type .= " " . shift @F if ($F[0] =~ /NOT/);
$type = sprintf "%-8s", $type;
$type .= " " . shift @F if ($F[0] =~ /NULL/);
printf " %-15s %-13s%s\n", $identifier, $type, join(" ",'',@F);
# print the test_field definition after mgrid
if ($identifier eq 'mgrid') {
print " test_field TEXT NULL CHECK (LENGTH(test_field) <= 25)\n";
};
}
这是一种将输入转换为(大致)所需输出的相当强力的方法。一些正则表达式转换,以及一些代码来很好地排列“字段”。以及一些额外的打印语句,用于在适当的位置添加空行和 test_field。因此,它不是一般有用,但可以根据需要进行调整以适应其他 SQL 转换。
该脚本实现了问题中的描述,而不是“所需输出”中显示的内容(因此,例如, 和 都
region
没有postalcode
长度检查,因为它们是 NULL 字段)。
输出:
CREATE TABLE employee
(
empid INT GENERATED BY DEFAULT AS IDENTITY,
lastname TEXT NOT NULL CHECK (LENGTH(lastname) <= 20),
firstname TEXT NOT NULL CHECK (LENGTH(firstname) <= 10),
title TEXT NULL,
titleofcourtesy TEXT NULL,
birthdate DATE NOT NULL,
hiredate DATE NOT NULL,
address TEXT NOT NULL CHECK (LENGTH(address) <= 60),
city TEXT NOT NULL CHECK (LENGTH(city) <= 15),
region TEXT NULL,
postalcode TEXT NULL,
country TEXT NOT NULL CHECK (LENGTH(country) <= 15),
phone TEXT NOT NULL CHECK (LENGTH(phone) <= 24),
mgrid INT NULL,
test_field TEXT NULL CHECK (LENGTH(test_field) <= 25)
);
以下是脚本输出与所需输出的差异(清理以删除注释和一些无关的空格字符后):
- region TEXT NULL CHECK (LENGTH(region) <= 15),
- postalcode TEXT NULL CHECK (LENGTH(postalcode) <= 10),
+ region TEXT NULL,
+ postalcode TEXT NULL,
其他的建议:
你可能
PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
想要empid
postgresql 有一个 VARCHAR(n) 数据类型,它可能比 TEXT 更合适,并且转换起来更简单:
s/NVARCHAR/VARCHAR/
。 VARCHAR 具有固定长度,因此 a) 不需要长度约束检查,b) 索引和搜索速度更快。允许字段为 NULL 是默认设置,因此不需要明确定义它们。