Regulärer Ausdruck - SQL-Manipulation

Regulärer Ausdruck - SQL-Manipulation
[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 Zeichenfolge

  • gefolgt von zwei Leerzeichen

  • gefolgt von meinem Feldnamen [az]+

  • gefolgt von einer beliebigen Anzahl von Leerzeichen [ ]+

  • NVARCHAR([0-9]+)

und ersetzen in

TEXTgefolgt 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 NULLundnicht 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 NULLin 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

sedIch weiß, dass ich viele davon verwendet habe , bin aber für alle awkBefehle 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 NOTSchlü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 sedBefehlszeilentrennzeichen zu ersetzen und es ohne viele Escapezeichen übersichtlicher zu machen.


Legen Sie das erste sedin 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 sedBefehle zu gruppieren.

Dies qist der quitBefehl, der zum sedBeenden verwendet wird. Hier habe ich ihn verwendet, um seddas 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 -EOption anzugeben sed.
  • 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 gOption 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 NOToptional 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\'\'/gfehlt 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

sedist für einige Aufgaben großartig, aber für andere Aufgaben wird eine voll funktionsfähige Sprache benötigt, wie awkoder 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 regionkeine postalcodeLä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 IDENTITYfü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.

verwandte Informationen