After reading the following link..
https://dba.stackexchange.com/questions/94443/how-should-a-numeric-value-that-is-an-external-key-be-stored
I decided to alter a column from text to:
int(9) UNSIGNED ZEROFILL NOT NULL
However, I am not sure of the SQL statement to use. I know the below is not correct because it does not include the the 9 digits, unsigned zerofill and not NULL parameters.
ALTER TABLE "Organizations" ALTER COLUMN "EIN" TYPE INTEGER using "EIN"::INTEGER
UPDATE:
Since Postgres does not use zerofill or INT(9). What would be the recommended data type of an EIN number that is 9 digits?
I would recommend below as is in two statements:
ALTER TABLE "Organizations" ALTER COLUMN "EIN" TYPE INTEGER using "EIN"::INTEGER;
ALTER TABLE "Organizations" ALTER COLUMN "EIN" SET NOT NULL;
decoration with padding zeros can be done on select with client (or rule, which would be effectively just a view, selected instead, and thus I think overcomplicating here - ((and changing to int to select text with zeroes - does not sound reasonambe))), eg:
t=# select lpad(123::int::text,9,'0');
lpad
-----------
000000123
(1 row)
so If its needed, can be mocked up
For the 9-digit restriction, a domain over int can work:
CREATE DOMAIN ein AS int CHECK (VALUE>0 AND VALUE<1000000000);
Then ein can be used in declarations as a type, for instance:
=> CREATE TABLE test(id ein, t text);
CREATE TABLE
=> insert into test values(2*1e9::int);
ERROR: value for domain ein violates check constraint "ein_check"
=> insert into test values(100);
INSERT 0 1
The zerofill bit is different, it's about presentation, not storage,
and that part cannot be specialized for a domain.
You may instead apply to_char to the values, for example:
=> select to_char(id,'000000000') from test;
to_char
------------
000000100
and possibly access this through a stored view or a presentation
function that takes only the ein as argument
if you prefer to abstract this from the client.
To go further, you could create a full type with CREATE TYPE
backed with C code for the INPUT and OUTPUT function, and these functions could implement the 9-digit left-padded format as the input/output format, so that the user may never see anything else at the SQL level.
Related
I have a task to create a Liquibase migration to change a value affext in table trp_order_sold, which is right now int8, to varchar (or any other text type if it's more likely to be possible).
The script I made is following:
ALTER TABLE public.trp_order_sold
ALTER COLUMN affext SET DATA TYPE VARCHAR
USING affext::varchar;
I expected that USING affext::text; part is gonna work as a converter, however with or without it I am getting this error:
ERROR: operator does not exist: varchar >= integer
Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
Any hints on what I'm doing wrong? Also I am writing a PostgreSQL script but a working XML equivalent would be fine for me as well.
These would most typically use or depend on your column:
a generated column
a trigger
a trigger's when condition
a view or a rule
a check constraint
In my test (online demo) only the last one leads to the error you showed:
create table test_table(col1 int);
--CREATE TABLE
alter table test_table add constraint test_constraint check (col1 >= 1);
--ALTER TABLE
alter table test_table alter column col1 type text using col1::text;
--ERROR: operator does not exist: text >= integer
--HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
You'll have to check the constraints on your table with \d+ command in psql, or by querying the system tables:
SELECT con.*
FROM pg_catalog.pg_constraint con
INNER JOIN pg_catalog.pg_class rel
ON rel.oid = con.conrelid
INNER JOIN pg_catalog.pg_namespace nsp
ON nsp.oid = connamespace
WHERE nsp.nspname = 'your_table_schema'
AND rel.relname = 'your_table_name';
Then you will need to drop the constraint causing the problem and build a new one to work with your new data type.
Since integer 20 goes before integer 100, but text '20' goes after text '100', if you plan to keep the old ordering behaviour you'd need this type of cast:
case when affext<0 then '-' else '0' end||lpad(ltrim(affext::text,'-'),10,'0')
and then make sure new incoming affext values are cast accordingly in an insert and update trigger. Or use a numeric ICU collation similar to this.
On any update to the row (which would be somehow dumb and I would expect a performance warning on the documentation page then) or is it smart enough of analyzing the generation expression and only regenerate the computed column when the input column(s) have changed?
From the documentation it's rather clear
A stored generated column is computed when it is written (inserted or updated) and occupies storage as if it were a normal column. A virtual generated column occupies no storage and is computed when it is read. Thus, a virtual generated column is similar to a view and a stored generated column is similar to a materialized view (except that it is always updated automatically).
So it seams that the generated always column is generated always.
Below a small test case to verify
We define a immutable function used in the formula with pg_sleepinside to see if the function was called
create or replace function wait10s(x numeric)
returns int
as $$
SELECT pg_sleep(10);
select x as result;
$$ language sql IMMUTABLE;
Table DDL
create table t
(col1 numeric,
col2 numeric,
gen_col numeric generated always as ( wait10s(col2) ) STORED
);
Insert
as expected we wait 10 seconds
insert into t (col1, col2) values (1,1);
Update of column used in formula
update t set col2 = 2
Again expected wait
Update of column NOT used in formula
update t set col1 = 2
No wait so it seems that there is an optimizing step calling the formula only in case of necessity.
This makes perfect sense, but of course you should take it with care as this behavior is not documented and may change...
How can I set a default for column B to be the value in column A?
I know, it is possible in Microsoft SQL Server:
http://www.ideaexcursion.com/2010/04/19/default-column-value-to-identity-of-different-column/
Is it possible in PostgreSQL?
The linked example shows how to intialize one column with the value of the identity column of the same table.
That is possible in Postgres
create table identdefault
(
a serial not null,
b int not null default currval('identdefault_a_seq')
);
serial will create a sequence in the background that is named tablename_column_seq thus we know that the sequence for identdefault.a will be named identdefault_a_seq and we can access the last value through the currval function.
Running:
insert into identdefault default values;
insert into identdefault default values;
insert into identdefault default values;
select *
from identdefault
will output:
a | b
--+--
1 | 1
2 | 2
3 | 3
This seems to only work with Postgres 9.4, when I tried that with 9.3 (on SQLFiddle) I got an error. But in that case it is possible as well - you just can't use the "shortcut" serial but need to create the sequence explicitly:
create sequence identdefault_a_seq;
create table identdefault
(
a int not null default nextval('identdefault_a_seq'),
b int not null default currval('identdefault_a_seq')
);
insert into identdefault default values;
insert into identdefault default values;
insert into identdefault default values;
If you want to have an identical definition as with the serial column, you just need to make the sequence belong to the column:
alter sequence identdefault_a_seq owned by identdefault.a;
SQLFiddle: http://sqlfiddle.com/#!15/0aa34/1
The answer to the much broader question "How can I set a default for column B to be the value in column A?" is unfortunately: no, you can't (see klin's comment)
You can't do it with an actual DEFAULT, but it's trivial with a BEFORE trigger.
CREATE OR REPLACE FUNCTION whatever() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NEW.onecol := NEW.othercol;
RETURN NEW;
END;
$$;
CREATE TRIGGER whatever_tg
BEFORE INSERT ON mytable
FOR EACH ROW EXECUTE PROCEDURE whatever();
"You cannot do that" and postgres don't go well together. There's almost always a way you can do that (whatever "that" turns out to be).
The question is more like: How do you want to do it?
One way, that is nice to DB-Admins would be: Create a before-Trigger, manipulate the new row before it is written.
If your rules to create that new column are very fancy: Turn to one of the embedded languages (like perl).
So: Is it possible? Of course it is.
Can anybody tell me, why double-quoted types behave differently in PosqtgreSQL?
CREATE TABLE foo1 (pk int4); -- ok
CREATE TABLE foo2 (pk "int4"); -- ok
CREATE TABLE foo3 (pk int); -- ok
CREATE TABLE foo4 (pk "int"); -- fail: type "int" does not exist
CREATE TABLE foo5 (pk integer); -- ok
CREATE TABLE foo6 (pk "integer"); -- fail: type "integer" does not exist
I can't find anything about it in documentation. Is this a bug?
Any information would be greatly appreciated
Double quotes mean that an identifier is to be interpreted exactly as written. They cause case to be preserved instead of flattened, and they allow what would otherwise be a keyword to be interpreted as an identifier.
PostgreSQL's int is a parse-time transformation to the integer type. There is not actually any data type named int in the system catalogs:
regress=> select typname from pg_type where typname = 'int';
typname
---------
(0 rows)
It is instead handled as a parse-time transformation much like a keyword. So when you protect it from that transformation by quoting it, you're telling the DB to look for a real type by that name.
This can't really be undone in a backward compatible way, since it'd break someone's system if they created a type or table named "int". (Types and tables share the same namespace).
This is similar to how user is transformed to current_user. Rails developers often use User as a model name, which causes Rails to try to SELECT * FROM user; in the DB, but this is transformed at parse time to SELECT * FROM current_user;, causing confused users to wonder why their table has a single row with their username in it. Query generators should always quote identifiers, i.e. they should be generating SELECT * FROM "user";... but few do.
Yesterday we had a PostgreSQL database upgraded to version 9.1.3. We thought we had everything tested and ready, but there is a function we missed. It returns a table type like this:
CREATE OR REPLACE FUNCTION myfunc( patient_number varchar
, tumor_number_param varchar, facility_number varchar)
RETURNS SETOF patient_for_registrar
LANGUAGE plpgsql
AS
$body$
BEGIN
RETURN QUERY
SELECT cast(nfa.patient_id_number as varchar),
...
I only only give the first column of the select because that is where the error happens. Before today this function ran fine, but now it gives this error:
ERROR: structure of query does not match function result type
Detail: Returned type character varying does not match expected type
character varying(8) in column 1. Where: PL/pgSQL function
"getwebregistrarpatient_withdeletes" line 3 at RETURN QUERY [SQL
State=42804]
The column nfa.patient_id_number is text and is being cast for the column patient_id_number in patient_for_registrar that is varchar(8). After reading about this some I think the problem is because the column length isn't being specified when casting from text. But the problem is I've tried various combinations of substrings to fix this and none are solving the problem:
substring(cast(nfa.patient_id_number as varchar) from 1 for 8),
cast(substring(nfa.patient_id_number from 1 for 8) as varchar),
cast(substring(nfa.patient_id_number from 1 for 8) as varchar(8)),
Does anyone have any pointers?
Your function ..
RETURNS SETOF patient_for_registrar
The returned row type must match the declared type exactly. You did not disclose the definition of patient_for_registrar, probably the associated composite type of a table. I quote the manual about Declaration of Composite Types:
Whenever you create a table, a composite type is also automatically
created, with the same name as the table, to represent the table's row
type.
If the first column of that type (table) is defined varchar(8) (with length modifier) - as the error message indicates, you have to return varchar(8) with the same length modifier; varchar won't do. It is irrelevant for that matter whether the string length is only 8 characters, the data type has to match.
varchar, varchar(n) and varchar(m) are different data types for PostgreSQL.
Older versions did not enforce the type modifiers, but with PostgreSQL 9.0 this was changed for plpgsql:
PL/pgSQL now requires columns of composite results to match the
expected type modifier as well as base type (Pavel Stehule, Tom Lane)
For example, if a column of the result type is declared as
NUMERIC(30,2), it is no longer acceptable to return a NUMERIC of some
other precision in that column. Previous versions neglected to check
the type modifier and would thus allow result rows that didn't
actually conform to the declared restrictions.
Two basic ways to fix your problem:
You can cast the returned values to match the definition of patient_for_registrar:
nfa.patient_id_number::varchar(8)
Or you can change the RETURNS clause. I would use RETURNS TABLE and declare a matching composite type. Here is an example.
RETURNS TABLE (patient_for_registrar varchar, col2 some_type, ...)
As an aside: I never use varchar if I can avoid it - especially not with length modifier. It offers hardly anything that the type text couldn't do. If I need a length restriction, I use a column constraint which can be changed without rewriting the whole table.