How correctly implement and enforce a default option among a list of rows is always present in postgresql

dba.stackexchange https://dba.stackexchange.com/questions/284728

  •  15-03-2021
  •  | 
  •  

Question

I have a list of address, and need to set which one is the default.

This look easy:

CREATE TABLE address (
  address_code  TEXT PRIMARY KEY CHECK (not_empty(address_code)),
  customer_code TEXT  NOT NULL CHECK (not_empty(customer_code)),
  place_code    TEXT NOT NULL CHECK (not_empty(place_code)), //ie: House, Main branch...
  is_default    BOOLEAN NOT NULL DEFAULT FALSE,
);

CREATE UNIQUE INDEX idx_address_is_default ON address (customer_code, is_default) WHERE (is_default = true);

However, if the default address is deleted or other address is set to be default, it must select other row, ie: I need to always have a default address (as long exist rows. Is ok to have zero).

For now I have a trigger:

CREATE OR REPLACE FUNCTION address_change_delete()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $function$
BEGIN
    IF OLD.is_default THEN
        --RAISE NOTICE 'DEL:%:%', OLD.is_default, NEW.is_default;
        UPDATE address SET is_default = true WHERE customer_code = OLD.customer_code AND address_code IN 
           (SELECT address_code FROM address WHERE customer_code = OLD.customer_code AND place_code <> OLD.place_code LIMIT 1);
    END IF;        

    RETURN OLD;  
END;
$function$;
CREATE OR REPLACE FUNCTION address_change_update()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $function$
BEGIN
    IF EXISTS (SELECT 1 FROM config
             WHERE name = CAST(pg_backend_pid() AS VARCHAR)) THEN
        --RAISE NOTICE 'REC:%', pg_backend_pid();
        -- it's recursing
        RETURN NEW;
    END IF;

    INSERT INTO config (name, value) VALUES (CAST(pg_backend_pid() AS VARCHAR), true);

    IF TG_OP = 'UPDATE' THEN
        IF OLD.is_default AND NEW.is_default = false THEN
          --RAISE NOTICE 'UP:%:%', OLD.is_default, NEW.is_default;
          UPDATE address SET is_default = true WHERE customer_code = NEW.customer_code AND address_code IN 
              (SELECT address_code FROM address WHERE customer_code = NEW.customer_code AND place_code <> NEW.place_code LIMIT 1);
        END IF;
    END IF;

    IF NEW.is_default AND (TG_OP = 'INSERT' OR OLD.is_default = false) THEN
      --RAISE NOTICE 'IN:%:%', OLD.is_default, NEW.is_default;         
      UPDATE address SET is_default = false WHERE customer_code = NEW.customer_code AND address_code IN 
        (SELECT address_code FROM address WHERE customer_code = NEW.customer_code AND place_code <> NEW.place_code);

    END IF;
    DELETE FROM config WHERE name = CAST(pg_backend_pid() AS VARCHAR);
    RETURN NEW;
END;
$function$;

CREATE TRIGGER address_change_delete
    AFTER DELETE ON
      address FOR EACH ROW
      EXECUTE PROCEDURE address_change_delete();
      
CREATE TRIGGER address_change
    BEFORE INSERT OR UPDATE ON
      address FOR EACH ROW
      EXECUTE PROCEDURE address_change_update(); 

Now this is causing me a lot of troubles and wonder if I'm overlooking something.

P.D: This come from data from outside, so I can't rely in a user to pick a address for me, that is why must be automated...

No correct solution

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top