Expresión regular: manipulación de SQL

Expresión regular: manipulación de SQL
[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 cuerda

  • seguido de dos espacios

  • seguido del nombre de mi campo [az]+

  • seguido de un no arbitrario. de espacios [ ]+

  • NVARCHAR([0-9]+)

y sustituir en

TEXTseguido 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 NULLyno 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 NULLa 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 sedmucho, pero estaría abierto a cualquier awkcomando que pueda realizar las tareas requeridas.

Respuesta1

Desde que usas fedoratienes GNU sedy 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 NOTse 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 seddentro 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 sedcomandos.

Este qes el quitcomando que se utiliza para sedsalir. Aquí lo uso para forzar sedla 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 -Eopción a sed.
  • 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 gopció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 NOTopcional: `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\'\'/gel 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

sedes excelente para algunas tareas, pero otras requieren un lenguaje con todas las funciones, como awko 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 regiony postalcodeno 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 IDENTITYporempid

  • 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.

información relacionada