1

I am writing a PostgreSQL function. I want to write an immutable function.

The following is an excerpt from a PostgreSQL manual.

IMMUTABLE indicates that the function cannot modify the database and always returns the same result when given the same argument values; that is, it does not do database lookups or otherwise use information not directly present in its argument list. If this option is given, any call of the function with all-constant arguments can be immediately replaced with the function value.

My understanding of the passage above is that you are not allowed to use IMMUTABLE when you have to access a table within the function.

But my simple experimentation with IMMUTABLE functions does not give me any errors or faluty results. I wonder I may write immutable functions when I am certain that the data in the table are not going to change. Is it safe to use a User Defined Immutable function in a production environment?

Below is the script I wrote to check if immutable functions with a table access cause any errors or produce any faulty results.

drop table if exists customer;
create table customer (
cust_no        numeric not null,
cust_nm        character varying(100),
register_date  timestamp(0),
register_dt    varchar(8),
cust_status_cd varchar(1),
register_channel_cd varchar(1),
cust_age       numeric(3),
active_yn      varchar(1),
sigungu_cd     varchar(5),
sido_cd        varchar(2)
);
insert into customer
select i, chr(65+mod(i,26))||i::text||'CUST_NM'
     , current_date - mod(i,10000)
     , to_char((current_date - mod(i,10000)),'yyyymmdd') as register_dt
     , mod(i,5)+1 as cust_status_cd
     , mod(i,3)+1 as register_channel_cd
     , trunc(random() * 100) +1 as age
     , case when mod(i,22) = 0 then 'N' else 'Y' end as active_yn
     , case when mod(i,1000) = 0 then '11007'
            when mod(i,1000) = 1 then '11006'
            when mod(i,1000) = 2 then '11005'
            when mod(i,1000) = 3 then '11004'
            when mod(i,1000) = 4 then '11003'
            when mod(i,1000) = 5 then '11002'
            else '11001' end                  as sigungu_cd
      , case when mod(i,3) = 0 then '01'
             when mod(i,3) = 1 then '02'
             when mod(i,3) = 2 then '03' end as sido_cd
  from generate_series(1,1000000) a(i);
ALTER TABLE customer ADD CONSTRAINT customer_pk
  PRIMARY KEY (cust_no);

create table com_code (
group_cd  varchar(10),
cd        varchar(10),
cd_nm     varchar(100));
insert into com_code values ('G1','11001','SEOUL')
                           ,('G1','11002','PUSAN')
                           ,('G1','11003','INCHEON')
                           ,('G1','11004','DAEGU')
                           ,('G1','11005','JAEJU')
                           ,('G1','11006','ULEUNG')
                           ,('G1','11007','ETC');
insert into com_code values ('G2','1','Infant')
                           ,('G2','2','Child')
                           ,('G2','3','Adolescent')
                           ,('G2','4','Adult')
                           ,('G2','5','Senior');
insert into com_code values ('G3','01','Jeonbuk')
                           ,('G3','02','Kangwon')
                           ,('G3','03','Chungnam');
alter table com_code add constraint com_code_pk
  primary key (group_cd, cd);

The following is the script to create a stable function.

CREATE OR REPLACE FUNCTION F_GET_NM_STABLE (
    p_cust_no  IN NUMERIC)
RETURNS VARCHAR
LANGUAGE PLPGSQL STABLE
AS
$$
DECLARE
  v_out_name  VARCHAR(100);
BEGIN
  SELECT B.CD_NM
    INTO v_out_name
    FROM CUSTOMER A LEFT JOIN COM_CODE B
      ON (A.SIGUNGU_CD = B.CD
          AND A.CUST_NO = p_cust_no)
   WHERE B.GROUP_CD = 'G1';
  RETURN v_out_name;
END;
$$

The following is the script to create an immutable function.

CREATE OR REPLACE FUNCTION F_GET_NM_IMMUTABLE (
    p_cust_no  IN NUMERIC)
RETURNS VARCHAR
LANGUAGE PLPGSQL IMMUTABLE
AS
$$
DECLARE
  v_out_name  VARCHAR(100);
BEGIN
  SELECT B.CD_NM
    INTO v_out_name
    FROM CUSTOMER A LEFT JOIN COM_CODE B
      ON (A.SIGUNGU_CD = B.CD
          AND A.CUST_NO = p_cust_no)
   WHERE B.GROUP_CD = 'G1';
  RETURN v_out_name;
END;
$$

The following is the script to test those functions above.

SELECT SIGUNGU_CD, F_GET_NM_STABLE(CUST_NO)
  FROM CUSTOMER
 WHERE CUST_NO BETWEEN 1 AND 5;
SELECT SIGUNGU_CD, F_GET_NM_IMMUTABLE(CUST_NO)
  FROM CUSTOMER
 WHERE CUST_NO BETWEEN 1 AND 5;

When I run the queries above I don't see any errors.

I am trying to create an immutable function because it is extremely performant in some queries which use the immutable function in the where clause.

JAEGEUN YU
  • 61
  • 5
  • A word to the wise: trying to outsmart the developers of a database system as sophisticated as postgreSQL is not a sustainable idea. Don't do what you propose unless you **have no other choice** for performance or other reasons and you **document it carefully**. The way postgreSQL optimizes queries may change in future and cause your strangely defined function to start giving wrong results. – O. Jones Nov 30 '21 at 11:38

1 Answers1

1

The main reason you would want immutable functions is so you can use them in indexes. If you promise something is immutable the database will believe you. But then if it turns out you were wrong about immutability, such indexes may return incorrect results.

jjanes
  • 37,812
  • 5
  • 27
  • 34