[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
:
Если внутри структуры базы данных найдено (), выполняется первая команда sed, затем выполняется quit ( ) NOT
без выполнения второго оператора./NOT/
q0
else
:
Ключевое слово не NOT
найдено, и выполняется второй экземпляр.
Для второго требования:
sed "s/N'/'/g"
Глобально найдите N'
и замените его только на '
. Я обнаружил, что полезно заменить '
его "
на 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 (LENGTH((\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
или 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 имеют фиксированную длину, поэтому а) не нуждаются в проверках ограничений длины и б) быстрее индексируются и ищутся.По умолчанию поле может иметь значение NULL, поэтому нет необходимости явно определять их как таковые.