简体   繁体   中英

Using regex in PSQL Case Statement

Working on a rails app with postgresql.

I am trying to implement a pg constraint using a case statement.

          ALTER TABLE geolocation_postcodes
        ADD CONSTRAINT canadian_format CHECK (
          CASE WHEN country_code = 'CA' AND code !~ '\A^[A-Z]\d[A-Z]\s\d[A-Z]\d\Z' THEN FALSE
          END
          );

When trying to insert an instance (which should pass) it is stopped by the constraint every time:

Input:

Geolocation::Postcode.insert(code: "G6V 4C9", country_code: "CA", created_at: Time.now, updated_at: Time.now)

Result:

ActiveRecord::StatementInvalid: PG::CheckViolation: ERROR:  new row for relation "geolocation_postcodes" violates check constraint "canadian_format" DETAIL:  Failing row contains (46, G6V 4C9, t, 2021-04-07 01:37:34.005891, 2021-04-07 01:37:34.005896, CA).

There are a few issues here:

  1. Your regex doesn't do what you think it does.
  2. You only cover Canadian postal codes (but this is probably intentional).
  3. I suspect that your CHECK constraint didn't get to the database properly.

(1) Your regex is attempting to use both \A (beginning of string) and ^ (beginning of line). You only want one of those and I'd go with \A for consistency with \Z and \A in Ruby:

\A[A-Z]\d[A-Z]\s\d[A-Z]\d\Z

(2) You can fix this by renaming the constraint to something like postal_code_format and restructuring the logic like:

code ~ case country_code
       when 'CA' then '\A[A-Z]\d[A-Z]\s\d[A-Z]\d\Z'
       when 'US' then ...
       end

That structure makes the SQL case look like a Ruby hash and keeps everything together and easy to read.

(3) This one is probably at the root of your problems. I'm guessing that you have something like this in a migration:

connection.execute(%Q(
  ALTER TABLE geolocation_postcodes
  ADD CONSTRAINT canadian_format CHECK (
    CASE WHEN country_code = 'CA' AND code !~ '\A^[A-Z]\d[A-Z]\s\d[A-Z]\d\Z' THEN FALSE
    END
  );
))

or a heredoc or other double quoted string. All those strings will interpret backslashes so \A will end up as A , \d as d , ... and by the time your regex gets to the database it will look like:

A^[A-Z]d[A-Z] d[A-Z]dZ

and your CHECK will fail.

If you use a single quoted string and the right regex, things should be okay:

connection.execute(%q(
  alter table geolocation_postcodes
  add constraint postal_code_format check (
    code ~ case country_code
           when 'CA' then '\A[A-Z]\d[A-Z]\s\d[A-Z]\d\Z'
           end
  )
))

The logic inside the check constraint should be for the positive match cases. So I would use here:

ALTER TABLE geolocation_postcodes
ADD CONSTRAINT canadian_format CHECK (
    (country_code = 'CA' AND code ~ '\A[A-Z][0-9][A-Z]\s[0-9][A-Z][0-9]\Z') OR
    -- other possible matching country codes here
);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM