정규식 - SQL 작업

정규식 - 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

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]+)

그리고 대체

TEXTNOT 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');

간단한 아포스트로피 ( SQL Server의 경우) N'로 변경 하고 싶지만 빈 문자열 로 변경 하거나 더 나쁘게 변경하고 싶지 않으므로 다음 과 같이 시도합니다.'N'NULLULL

[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를 시뮬레이션합니다.

if:

db 구조에서 ()를 찾은 NOT다음 첫 번째 sed 명령을 실행하고 두 번째 명령문을 실행하지 않고 종료()합니다./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. 첫 번째 테스트가 성공하면 마지막 줄을 만나기 전에 강제 종료하기 위해 여기에서 사용하고 있습니다.quitsedsed

답변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 ( 길이((\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일부 작업에는 매우 유용하지만 다른 작업 에는 조건문 및 printf 등과 awk같은 모든 기능을 갖춘 언어가 필요합니다 . perl정규식과 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 변환을 수용하도록 조정할 수 있습니다.

  • 스크립트는 "필수 출력"에 표시된 내용이 아닌 질문에 설명된 내용을 구현합니다. 예를 들어 NULL 필드이기 때문에 길이 확인 및 길이 확인이 region없습니다 .postalcode

산출:

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에는 TEXT보다 더 적합하고 변환이 더 간단한 VARCHAR(n) 데이터 유형이 있습니다 s/NVARCHAR/VARCHAR/. VARCHAR의 길이는 고정되어 있으므로 a) 길이 제약 검사가 필요하지 않으며 b) 인덱싱 및 검색이 더 빠릅니다.

  • 필드가 NULL이 되도록 허용하는 것이 기본값이므로 명시적으로 정의할 필요가 없습니다.

관련 정보