[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
Estoy intentando convertir un archivo de base de datos de muestra de MS SQL Server a PostgreSQL.
Entonces, tengo dos pequeños inconvenientes que no puedo resolver.
shipname NVARCHAR(40) NOT NULL,
Eso es
(siempre) dos espacios
identificador (es decir, nombre de campo) - siempre [az] - alfabético en minúsculas
seguido de un número desconocido de espacios
seguido de NVARCHAR(xy) NO NULOopuede ir seguido de NVARCHAR(xy) NULL
y quiero transformar esto en
shipname TEXT NOT NULL CHECK (LENGTH(shipname) <= xy),
o
shipname TEXT NULL,
Lo que tengo hasta ahora:
sed 's/^ [a-z]+[ ]+NVARCHAR([0-9]+) NOT NULL/TEXT NOT NULL CHECK \(LENGTH\((\1) <= (\2)\)/g'
Entonces,
^
es el comienzo de la cuerdaseguido de dos espacios
seguido del nombre de mi campo [az]+
seguido de un no arbitrario. de espacios [ ]+
NVARCHAR([0-9]+)
y sustituir en
TEXT
seguido de NOT NULL y luego CHECK(LENGTH(xy) - referencia posterior 1 - <= referencia posterior 2...
Probé varias permutaciones y combinaciones de lo anterior, pero nada parece funcionar para mí.
[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
Obtener referencia anterior no válida...
Idealmente, y recalcoidealmente, si la cadena que sigue a NVARCHAR(xy) es NULL
yno NOT NULL
, No quiero ninguna verificación de longitud, porque no tiene sentido tomar la LONGITUD de un NULL... este es un comportamiento condicional; no estoy seguro de si es posible en expresiones regulares...
PD: pensé que esto sería trivial.
Tener datos como este:
N'Strada Provinciale 1234', N'Reggio Emilia', NULL, N'10289', N'Italy');
Quiero cambiarlo N'
a un simple apóstrofe '
( N'
es una cosa de SQL Server) pero no quiero cambiarlo NULL
a una cadena vacía, o algo peor ULL
, así que lo intenté:
[pol@fedora data]$ sed 's/N\'\'/g TSQLV5.sql
pero consigue
sed: -e expression #1, char 7: unterminated `s' command
Sé que lo he usado sed
mucho, pero estaría abierto a cualquier awk
comando que pueda realizar las tareas requeridas.
Respuesta1
Desde que usas fedora
tienes GNU sed
y esto debería funcionar:
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,/'
Esto emula un if falso.
if
:
Se encuentra un NOT
( /NOT/
) dentro de la estructura de la base de datos, luego se ejecuta el primer comando sed y luego se sale ( q0
) sin ejecutar la segunda instrucción.
else
:
no NOT
se encuentra ninguna palabra clave y se ejecuta la segunda instancia.
Para el segundo requisito:
sed "s/N'/'/g"
Búscalo globalmente N'
y reemplázalo solo con '
. Me resultó útil cambiarlo por '
el delimitador de línea de comando y hacerlo más limpio sin muchos escapes."
sed
Pon el primero sed
dentro de un archivo:
#!/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,/
El {
comando se utiliza para agrupar más sed
comandos.
Este q
es el quit
comando que se utiliza para sed
salir. Aquí lo uso para forzar sed
la salida antes de encontrar la última línea si la primera prueba fue exitosa.
Respuesta2
Ya tienes respuestas, pero quiero agregar lo que salió mal en tu propio enfoque, para que puedas aprender de ello en lugar de simplemente copiar alguna solución:
- Usas expresiones regulares extendidas, pero olvidaste darle la
-E
opción ased
. - Quiere reutilizar el identificador, pero no lo incluyó en
()
- Pareces mezclar
()
grupos ERE con grupos literales. Probablemente quieras decirsed -E 's/^ ([a-z]+)[ ]+NVARCHAR\(([0-9]+)\) NOT NULL/TEXT NOT NULL CHECK \(LENGTH\((\1) <= (\2)\)/g'
- La primera parte hasta los espacios no aparece en el reemplazo. También necesitas agruparlo y usarlo como referencia en el reemplazo:
sed -E 's/^( ([a-z]+)[ ]+)NVARCHAR\(([0-9]+)\) NOT NULL/\1TEXT NOT NULL CHECK \(LENGTH\((\2) <= (\3)\)/g'
[ ]+
es lo mismo que+
. No es un error, pero hace que la lectura sea más confusa.- La
g
opción es superflua. Con un ancla como^
o$
en el patrón no es posible realizar múltiples reemplazos. - Puede evitar múltiples expresiones haciendo lo
NOT
opcional: `sed -E 's/^( ([az]+) +)NVARCHAR(([0-9]+)) (NOT )?NULL/\1TEXT \4NULL CHECK ( LONGITUD((\2) <= (\3))/' - Por otro lado, si deseas omitir el cheque, puedes hacerlo con un reemplazo por separado:
s/^( [a-z]+ +)NVARCHAR\(([0-9]+)\) NULL/\1TEXT NULL/
- Omite
s/N\'\'/g
el separador entre patrón de búsqueda y reemplazo:s/N\'/\'/g
Entonces terminas con
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'
Respuesta3
sed
es excelente para algunas tareas, pero otras requieren un lenguaje con todas las funciones, como awk
o perl
, con condicionales, printf y más. Y preferiblemente un lenguaje que no se lea como un horrible híbrido de expresión regular y calculadora 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";
};
}
Este es un método de fuerza bruta para transformar su entrada en (aproximadamente) la salida deseada. algunas transformaciones de expresiones regulares y algo de código para alinear bien los "campos". y algunas declaraciones de impresión adicionales para agregar líneas en blanco y el campo de prueba en los lugares apropiados. Como tal, no es genéricamente útil, pero puede adaptarse para adaptarse a otras transformaciones de SQL según sea necesario.
el script implementa la descripción en su pregunta, no lo que se muestra en el "resultado deseado" (por ejemplo, ambos
region
ypostalcode
no tienen comprobaciones de longitud porque son campos NULL).
Producción:
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)
);
Aquí hay una diferencia entre el resultado del script y el resultado deseado (después de limpiar para eliminar comentarios y algunos caracteres de espacio extraños):
- region TEXT NULL CHECK (LENGTH(region) <= 15),
- postalcode TEXT NULL CHECK (LENGTH(postalcode) <= 10),
+ region TEXT NULL,
+ postalcode TEXT NULL,
Otros comentarios:
Probablemente quieras
PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
porempid
postgresql tiene un tipo de datos VARCHAR(n), que probablemente sea más apropiado que TEXT y mucho más sencillo de transformar:
s/NVARCHAR/VARCHAR/
. Los VARCHAR tienen una longitud fija, por lo que a) no necesitan comprobaciones de restricción de longitud yb) son más rápidos de indexar y buscar.Permitir que un campo sea NULL es el valor predeterminado, por lo que no hay necesidad real de definirlos explícitamente como tales.