[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
Ich versuche, eine Beispieldatenbankdatei von MS SQL Server nach PostgreSQL zu konvertieren.
Ich habe also zwei kleine Probleme, die ich nicht lösen kann.
shipname NVARCHAR(40) NOT NULL,
Das ist
(stets) zwei Leerzeichen
Bezeichner (also Feldname) - immer [az] - alphabetisch in Kleinbuchstaben
gefolgt von einer unbekannten Anzahl von Leerzeichen
gefolgt von NVARCHAR(xy) NOT NULLoderEs kann gefolgt werden von NVARCHAR(xy) NULL
und ich möchte dies umwandeln in
shipname TEXT NOT NULL CHECK (LENGTH(shipname) <= xy),
oder
shipname TEXT NULL,
Was ich bisher habe:
sed 's/^ [a-z]+[ ]+NVARCHAR([0-9]+) NOT NULL/TEXT NOT NULL CHECK \(LENGTH\((\1) <= (\2)\)/g'
Also,
^
ist der Anfang der Zeichenfolgegefolgt von zwei Leerzeichen
gefolgt von meinem Feldnamen [az]+
gefolgt von einer beliebigen Anzahl von Leerzeichen [ ]+
NVARCHAR([0-9]+)
und ersetzen in
TEXT
gefolgt von NOT NULL, dann CHECK(LENGTH(xy) – Rückreferenz 1 – <= Rückreferenz 2 …
Ich habe verschiedene Permutationen und Kombinationen der oben genannten Punkte ausprobiert, aber nichts scheint für mich zu funktionieren.
[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
Erhalten Sie eine ungültige Rückreferenz...
Im Idealfall, und ich betoneim Idealfall, wenn die Zeichenfolge nach NVARCHAR(xy) ist NULL
undnicht NOT NULL
, ich möchte keine Längenprüfung, da es keinen Sinn ergibt, die LÄNGE eines NULL-Werts zu übernehmen. Dies ist ein bedingtes Verhalten. Ich bin nicht sicher, ob dies in regulären Ausdrücken möglich ist.
PS: Ich dachte, das wäre trivial.
Habe Daten wie diese:
N'Strada Provinciale 1234', N'Reggio Emilia', NULL, N'10289', N'Italy');
N'
Ich möchte das in ein einfaches Apostroph ändern '
(das N'
ist eine SQL Server-Sache), aber ich möchte das nicht NULL
in eine leere Zeichenfolge oder Schlimmeres ändern ULL
– also habe ich Folgendes versucht:
[pol@fedora data]$ sed 's/N\'\'/g TSQLV5.sql
aber bekommen
sed: -e expression #1, char 7: unterminated `s' command
sed
Ich weiß, dass ich viele davon verwendet habe , bin aber für alle awk
Befehle offen, die die erforderlichen Aufgaben ausführen könnten.
Antwort1
Da Sie fedora
„you have“ verwenden GNU sed
, sollte Folgendes funktionieren:
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,/'
Dies emuliert ein falsches „wenn“.
if
:
Innerhalb der Datenbankstruktur wird ein NOT
( ) gefunden. Anschließend wird der erste Sed-Befehl ausgeführt. Anschließend wird quit ( ) ausgeführt, ohne die zweite Anweisung auszuführen./NOT/
q0
else
:
Es wird kein NOT
Schlüsselwort gefunden und die zweite Instanz wird ausgeführt.
Für die zweite Anforderung:
sed "s/N'/'/g"
Suchen Sie global danach N'
und ersetzen Sie es nur durch '
. Ich fand es sinnvoll, '
durch "
als sed
Befehlszeilentrennzeichen zu ersetzen und es ohne viele Escapezeichen übersichtlicher zu machen.
Legen Sie das erste sed
in eine Datei:
#!/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,/
Der {
Befehl wird verwendet, um mehrere sed
Befehle zu gruppieren.
Dies q
ist der quit
Befehl, der zum sed
Beenden verwendet wird. Hier habe ich ihn verwendet, um sed
das Beenden zu erzwingen, bevor die letzte Zeile angezeigt wird, wenn der erste Test erfolgreich war.
Antwort2
Sie haben bereits Antworten erhalten, aber ich möchte noch hinzufügen, was bei Ihrem eigenen Ansatz schiefgelaufen ist, damit Sie daraus lernen können, anstatt einfach eine Lösung zu kopieren:
- Sie verwenden erweiterte reguläre Ausdrücke, haben aber vergessen, die
-E
Option anzugebensed
. - Sie möchten den Bezeichner wiederverwenden, haben ihn aber nicht in
()
()
Sie scheinen ERE -Gruppen mit wörtlichen zu vermischen . Sie meinen wahrscheinlichsed -E 's/^ ([a-z]+)[ ]+NVARCHAR\(([0-9]+)\) NOT NULL/TEXT NOT NULL CHECK \(LENGTH\((\1) <= (\2)\)/g'
- Der erste Teil bis zu den Leerzeichen wird im Ersatz nicht angezeigt. Sie müssen ihn auch gruppieren und als Referenz im Ersatz verwenden:
sed -E 's/^( ([a-z]+)[ ]+)NVARCHAR\(([0-9]+)\) NOT NULL/\1TEXT NOT NULL CHECK \(LENGTH\((\2) <= (\3)\)/g'
[ ]+
ist dasselbe wie+
. Kein Fehler, macht das Lesen aber verwirrender.- Die
g
Option ist überflüssig. Bei einem Anker wie^
oder$
im Muster ist eine Mehrfachersetzung nicht möglich. - Sie können mehrere Ausdrücke vermeiden, indem Sie Folgendes
NOT
optional machen: `sed -E 's/^( ([az]+) +)NVARCHAR(([0-9]+)) (NOT )?NULL/\1TEXT \4NULL CHECK (LENGTH((\2) <= (\3))/' - Möchte man hingegen das Häkchen weglassen, kann man das durch eine separate Ersetzung tun:
s/^( [a-z]+ +)NVARCHAR\(([0-9]+)\) NULL/\1TEXT NULL/
- Dir
s/N\'\'/g
fehlt das Trennzeichen zwischen Suchmuster und Ersetzung:s/N\'/\'/g
Am Ende haben Sie also
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'
Antwort3
sed
ist für einige Aufgaben großartig, aber für andere Aufgaben wird eine voll funktionsfähige Sprache benötigt, wie awk
oder perl
, mit Bedingungen und printf und mehr. Und vorzugsweise eine Sprache, die sich nicht wie ein abscheulicher Hybrid aus einem regulären Ausdruck und einem RPN-Rechner liest :-).
#!/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";
};
}
Dies ist eine ziemlich brachiale Methode, um Ihre Eingabe (ungefähr) in die gewünschte Ausgabe umzuwandeln. Ein paar Regex-Transformationen und etwas Code, um die „Felder“ ordentlich anzuordnen. Und ein paar zusätzliche Druckanweisungen, um an den entsprechenden Stellen Leerzeilen und das Testfeld einzufügen. Daher ist es nicht allgemein nützlich, kann aber bei Bedarf an andere SQL-Transformationen angepasst werden.
das Skript implementiert die Beschreibung in Ihrer Frage, nicht das, was in der „gewünschten Ausgabe“ angezeigt wird (so verfügen beispielsweise beide über
region
keinepostalcode
Längenprüfungen, da es sich um NULL-Felder handelt).
Ausgabe:
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)
);
Hier ist ein Unterschied zwischen der Ausgabe des Skripts und der gewünschten Ausgabe (nach dem Bereinigen, um Kommentare und einige überflüssige Leerzeichen zu entfernen):
- region TEXT NULL CHECK (LENGTH(region) <= 15),
- postalcode TEXT NULL CHECK (LENGTH(postalcode) <= 10),
+ region TEXT NULL,
+ postalcode TEXT NULL,
Andere Kommentare:
Sie möchten wahrscheinlich
PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
fürempid
postgresql hat einen VARCHAR(n)-Datentyp, der wahrscheinlich geeigneter als TEXT und viel einfacher zu transformieren ist:
s/NVARCHAR/VARCHAR/
. VARCHARs haben eine feste Länge, daher sind a) keine Längenbeschränkungsprüfungen erforderlich und b) sie sind schneller zu indizieren und zu durchsuchen.Die Standardeinstellung besteht darin, dass ein Feld NULL sein darf. Es besteht also keine wirkliche Notwendigkeit, es explizit als solches zu definieren.