Find what row holds a value which cannot be cast to integer - postgresql

I have some operations in heavy rearanging data tables which goes good so far.
In one table with more than 50000 rows I have text column where text should be numbers only.
Now I would like to convert it to integer column.
So:
ALTER TABLE mytable ALTER COLUMN mycolumn TYPE integer;
That produces an error 42804: *datatype_mismatch*
By reading docs I find solution:
ALTER TABLE mytable ALTER COLUMN mycolumn TYPE integer USING (TRIM(mycolumn)::integer);
But I am aware that data may not be correct in mean of number order since this "masks" an error and there is possibility that column was edited (by hand). After all, maybe is only trailing space added or some other minor editing was made.
I have backup of data.
How would I find which exact cell of given column contain an error and which value cannot be casted to int with some handy query suitable for use from pgadmin?
Please that query if is not complicated too much.

Expanding on #dystroy's answer, this query should cough the precise value of any offending rows:
CREATE OR REPLACE FUNCTION convert_to_integer(v_input text)
RETURNS INTEGER AS $$
BEGIN
BEGIN
RETURN v_input::INTEGER;
EXCEPTION WHEN OTHERS THEN
RAISE EXCEPTION 'Invalid integer value: "%". Returning NULL.', v_input;
RETURN NULL;
END;
END;
$$ LANGUAGE plpgsql;
Original answer:
If the following works:
ALTER TABLE mytable
ALTER COLUMN mycolumn TYPE integer USING (TRIM(mycolumn)::integer);
Then you should probably be able to run the following to locate the trash:
select mycolumn from mytable
where mycolumn::text <> (TRIM(mycolumn)::integer)::text;

Related

Error when creating a generated column in Postgresql

CREATE TABLE Person (
id serial primary key,
accNum text UNIQUE GENERATED ALWAYS AS (
concat(right(cast(extract year from current_date) as text), 2), cast(id as text)) STORED
);
Error: generation expression is not immutable
The goal is to populate the accNum field with YYid where YY is the last two letters of the year when the person was added.
I also tried the '||' operator but it was unsuccessful.
As you don't expect the column to be updated, when the row is changed, you can define your own function that generates the number:
create function generate_acc_num(id int)
returns text
as
$$
select to_char(current_date, 'YY')||id::text;
$$
language sql
immutable; --<< this is lying to Postgres!
Note that you should never use this function for any other purpose. Especially not as an index expression.
Then you can use that in a generated column:
CREATE TABLE Person
(
id integer generated always as identity primary key,
acc_num text UNIQUE GENERATED ALWAYS AS (generate_acc_num(id)) STORED
);
As #ScottNeville correctly mentioned:
CURRENT_DATE is not immutable. So it cannot be used int a GENERATED ALWAYS AS expression.
However, you can achieve this using a trigger nevertheless:
demo:db<>fiddle
CREATE FUNCTION accnum_trigger_function()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
NEW.accNum := right(extract(year from current_date)::text, 2) || NEW.id::text;
RETURN NEW;
END
$$;
CREATE TRIGGER tr_accnum
BEFORE INSERT
ON person
FOR EACH ROW
EXECUTE PROCEDURE accnum_trigger_function();
As #a_horse_with_no_name mentioned correctly in the comments: You can simplify the expression to:
NEW.accNum := to_char(current_date, 'YY') || NEW.id;
I am not exactly sure how to solve this problem (maybe a trigger), but current_date is a stable function not an immutable one. For the generated IDs I believe all function calls must be immutable. You can read more here https://www.postgresql.org/docs/current/xfunc-volatility.html
I dont think any function that gets the date can be immutable as Postgres defines this as "An IMMUTABLE function cannot modify the database and is guaranteed to return the same results given the same arguments forever." This will not be true for anything that returns the current date.
I think your best bet would be to do this with a trigger so on insert it sets the value.

Add constraint with CHECK on existing table with data

I want to add a constraint on an existing table. Below is my sql statement.
CREATE OR REPLACE FUNCTION constraintFunction(uuid, date)
RETURNS integer AS $total$
declare
total integer;
begin
select count(*) into total FROM table1 WHERE foreign_key_id= $1 AND dt= $2 and status ='A';
RETURN total;
END;
$total$ LANGUAGE plpgsql;
ALTER TABLE table1
ADD CONSTRAINT constraint1 CHECK ((constraintFunction(table1.foreign_key_id, table1.dt) < 1));
I get this error below when I execute the sql statements.
SQL Error [23514]: ERROR: check constraint "constraint1" is violated by some row
I have some records in table1. When I deleted the data with status = "A", the sql statement will work perfectly.
Is there any way that I can add the constraint without deleting my existing data in DB?
A check constraint is not the solution here, as the doc explicitly warn about not looking at other rows/tables, and to use an immutable function (always the same output for a given input, which can be cached)
Your validation fail because the said constraint asks that there is no row with the given value... if rows exist, the constraint fail, so you would want to exclude the rows having the same ID as the current row being validated.
That being said, a unique partial index is really what is needed here.
create unique index idx_A on table1 (foreign_key_id, dt) where status ='A';

record "new" has no field "cure" postgreSQL

so here's the thing,I have two tables: apointments(with a single p) and medical_folder and i get this
ERROR: record "new" has no field "cure"
CONTEXT: SQL statement "insert into medical_folder(id,"patient_AMKA",cure,drug_id)
values(new.id,new."patient_AMKA",new.cure,new.drug_id)"
PL/pgSQL function new_medical() line 3 at SQL statement
create trigger example_trigger after insert on apointments
for each row execute procedure new_medical();
create or replace function new_medical()
returns trigger as $$
begin
if apointments.diagnosis is not null then
insert into medical_folder(id,"patient_AMKA",cure,drug_id)
values(new.id,new."patient_AMKA",new.cure,new.drug_id);
return new;
end if;
end;
$$ language plpgsql;
insert into apointments(id,time,"patient_AMKA","doctor_AMKA",diagnosis)
values('30000','2017-05-24 0
07:42:15','4017954515276','6304745877947815701','M3504');
I have checked multiple times and all of my tables and columns are existing
Please help!Thank you!
Table structures are:
create table medical_folder (
id bigInt,
patient bigInt,
cure text,
drug_id bigInt);
create table apointments (
id bigint,
time timestamp without time zone,
"patient_AMKA" bigInt,
"doctor_AMKA" bigInt);
I was facing the same issue.
Change:
values(new.id,new."patient_AMKA",new.cure,new.drug_id);
to:
values(new.id,new."patient_AMKA",new."cure",new."drug_id");
This error means the table apointments (with 1 p) doesn't have a field named cure. The trigger occurs when inserting an apointment, so "new" is an apointment row. Maybe it is part of the diagnosis object?
The values for the second table are not available in the "new" row. You need a way to get and insert them, and using a trigger is not the easiest/clean way to go.
You can have your application do two inserts, one by table, and wrap them in a transaction to ensure they are both committed/rolled back. Another option, which lets you better enforce the data integrity, is to create a stored procedure that takes the values to be inserted in both tables and do the two inserts. You can go as far as forbidding user to write to the tables, effectively leaving the stored procedure the only way to insert the data.

INSERT a number in a column based on other columns OLD INSERTs

In PostgreSQL I have this table... (there is a primary key in the most left side "timestamp02" which is not shown in this image, please don't bother, its not important for the purpose of this question)
in the table above, all columns are entered via queries, except the "time_index" which I would like to be filled automatically via a trigger each time each row is filled.
This is the code to create the same table (without any value) so everyone could create it using the Postgre SQL query panel.
CREATE TABLE table_ebscb_spa_log02
(
pcnum smallint,
timestamp02 timestamp with time zone NOT NULL DEFAULT now(),
fn_name character varying,
"time" time without time zone,
time_elapse character varying,
time_type character varying,
time_index real,
CONSTRAINT table_ebscb_spa_log02_pkey PRIMARY KEY (timestamp02)
)
WITH (
OIDS=FALSE
);
ALTER TABLE table_ebscb_spa_log02
OWNER TO postgres;
What I would like the trigger to do is:
INSERT a number in the "time_index" column based on the INSERTed values of the "fn_name" and "time_type" columns in each row.
If both ("fn_name" and "time_type") do a combination (eg. Check Mails - Start) that doesn't exist in any row before (above), then INSERT 1 in the "time_index" column,
Elif both ("fn_name" and "time_type") do a combination that does exist in some row before (above), then INSERT the number following the one before(above) in the "time_index" column.
(pls look at the example table image, this trigger will produce every red highlighted square on it)
I have watch many, PostgreSQL tutorial videos, read many manuals, including these
http://www.postgresql.org/docs/9.4/static/sql-createtrigger.html
http://www.postgresql.org/docs/9.4/static/plpgsql-trigger.html
without any result.
I have tried so far this to create the function:
CREATE OR REPLACE FUNCTION on_ai_myTable() RETURNS TRIGGER AS $$
DECLARE
t_ix real;
n int;
BEGIN
IF NEW.time_type = 'Start' THEN
SELECT t.time_index FROM table_ebscb_spa_log02 t WHERE t.fn_name = NEW.fn_name AND t.time_type = 'Start' ORDER BY t.timestamp02 DESC LIMIT 1 INTO t_ix;
GET DIAGNOSTICS n = ROW_COUNT;
IF (n = 0) THEN
t_ix = 1;
ELSE
t_ix = t_ix + 1;
END IF;
END IF;
NEW.time_index = t_ix;
return NEW;
END
$$
LANGUAGE plpgsql;
And this to create the query:
CREATE TRIGGER on_ai_myTable
AFTER INSERT ON table_ebscb_spa_log02
FOR EACH ROW
EXECUTE PROCEDURE on_ai_myTable();
Then when I manually insert the values in the table, nothing change (no error message) time_index column just remain empty, what am I doing wrong???
Please some good PostgreSQL fellow programmer could give me a hand, I really have come to a death point in this task, I have any more ideas.
Thanks in advance
In an AFTER INSERT trigger, any changes you make to NEW.time_index will be ignored. The record is already inserted at this point; it's too late to modify it.
Create the trigger as BEFORE INSERT instead.

Getting Postgres to truncate values if necessary?

If I create a table mytable with column data as varchar(2) and then insert something like '123' into the column, postgres will give me an error for Value too long for type.
How can I have Postgres ignore this and truncate the value if necessary?
Also, I do not (when creating the query) know the actual size of the data column in mytable so I can't just cast it.
According to the postgres documentation, you have to explicitly cast it to achieve this behavior, as returning an error is a requirement of the SQL standard. Is there no way to inspect the table's schema before creating the query to know what to cast it to?
Use text type with trigger instead:
create table mytable (
data text
);
create or replace function mytable_data_trunc_trigger()
returns trigger language plpgsql volatile as $$
begin
NEW.data = substring(NEW.data for 2);
return NEW;
end;
$$;
create trigger mytable_data_truncate_trigger
before insert or update on mytable for each row
execute procedure mytable_data_trunc_trigger();
insert into mytable values (NULL),('1'),('12'),('123');
select * from mytable;
data
------
1
12
12
(4 rows)
Easiest is just substring
INSERT INTO mytable (data) VALUES (substring('123' from 1 for 2));
You could change the datatype to varchar, and then use a trigger to enforce the two char constraint.