Postgresql RAISE EXCEPTION when operation get 0 or less in Update - postgresql

I have three tables:
CREATE TABLE public.art_movimientos
(
cmovimiento bigint NOT NULL DEFAULT nextval('art_movimientos_cmovimiento_seq'::regclass),
tipo character varying(3) NOT NULL, -- Tipos de Valores:...
fecha_mov timestamp without time zone NOT NULL,
documento integer NOT NULL,
control integer,
fecha_doc timestamp without time zone NOT NULL,
corden integer NOT NULL DEFAULT 0,
calmacen integer NOT NULL,
calmacen2 integer,
status character varying(13) NOT NULL DEFAULT 'PENDIENTE'::bpchar, -- PENDIENTE...
donado integer NOT NULL DEFAULT 0,
monto_mov numeric(11,2) NOT NULL DEFAULT 0.00,
monto_desc numeric(11,2) NOT NULL DEFAULT 0.00,
monto_total numeric(11,2) NOT NULL DEFAULT 0.00,
observacion text,
casiento integer,
crea_user character varying(25),
crea_date timestamp without time zone,
mod_user character varying(25),
mod_date timestamp without time zone,
cproveedor integer NOT NULL DEFAULT 0
)
CREATE TABLE public.art_movimientos_det
(
cmovimiento_det bigint NOT NULL DEFAULT nextval('art_movimientos_det_cmovimiento_det_seq'::regclass),
cmovimiento integer NOT NULL,
cart_generico integer NOT NULL,
cunidad integer NOT NULL DEFAULT 1,
cant numeric(11,2) NOT NULL DEFAULT 0.00,
iva numeric(11,2) NOT NULL DEFAULT 0.00,
costou numeric(11,2) NOT NULL DEFAULT 0.00,
crea_user character varying(25),
crea_date timestamp without time zone,
mod_user character varying(25),
mod_date timestamp without time zone,
cart_comercial integer NOT NULL,
costot numeric(11,2) NOT NULL DEFAULT 0.00
)
CREATE TABLE public.ordencompra_det
(
corden_det bigint NOT NULL DEFAULT nextval('ordencompra_det_corden_det_seq'::regclass),
corden integer NOT NULL,
cart_comercial integer NOT NULL,
cunidad integer NOT NULL DEFAULT 1,
cant numeric(11,2) NOT NULL DEFAULT 0.00,
costou numeric(11,2) NOT NULL DEFAULT 0.00,
iva numeric(11,0) NOT NULL DEFAULT 0.00,
costot numeric(11,2) NOT NULL DEFAULT 0.00,
crea_user character varying(25),
crea_date timestamp without time zone,
mod_user character varying(25),
mod_date timestamp without time zone,
cant_restante numeric(11,2) NOT NULL DEFAULT 0
)
I have a procedure that reduce the cant_restante in ordencompra_det:
UPDATE ordencompra_det AS od
SET cant_restante = cant_restante - s.cant_real
FROM (SELECT am.corden, md.cart_comercial,(md.cant*u.multiplicador)cant_real FROM art_movimientos am INNER JOIN art_movimientos_det md ON am.cmovimiento=md.cmovimiento INNER JOIN art_und u ON md.cunidad=u.cunidad WHERE md.cmovimiento=cmov) AS s
WHERE od.corden=s.corden and od.cart_comercial=s.cart_comercial
But sometimes i get 0 or less in cant_restante, how i can do a check, if the update result below 0 ? i don't wanna have negative values "/, if i get negatives values, rollback the Update and raise a exception?
I'm using postgresql function (procedure) for it (because I'm doing a lot stuffs in the DB)

There are more possibilities:
use table constraint:
CREATE TABLE ordencompra_det(
...
cant_restante numeric(11,2) NOT NULL DEFAULT 0 CHECK(can_restante >= 0),
...
)
Use a check function:
CREATE OR REPLACE FUNCTION only_positive(numeric)
RETURNS numeric AS $$
BEGIN
IF $1 < 0 THEN
RAISE EXCEPTION '%s is not positive', $1;
END IF;
RETURN $1;
END;
$$ LANGUAGE plpgsql IMMUTABLE STRICT;
UPDATE ordencompra_det
SET cant_restante = only_positive(cant_restante - s.cant_real)
...
The first way should be preferred.

Related

How to detect if default value is constant or expression in Postgresql

In Postgresql, how can I know default value is constant or expression?
Example:
CREATE TABLE IF NOT EXISTS default_test (
id serial primary key,
name varchar(255) NOT NULL,
father_name varchar(255) DEFAULT 'NULL', -- CONSTANT
nn2 varchar(255) DEFAULT ('NULL'), -- CONSTANT
nn3 varchar(255) DEFAULT (null), -- CONSTANT
nn4 varchar(255) DEFAULT 'the default value', -- CONSTANT
last_name varchar(255) NULL, -- CONSTANT
dt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, -- EXPRESSION
dt2 timestamp NOT NULL DEFAULT '2011-11-11 00:00:00', -- CONSTANT
ts timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP /*ON UPDATE CURRENT_TIMESTAMP*/, -- EXPRESSION
ts2 timestamp NOT NULL DEFAULT '2011-11-11 00:00:00', -- CONSTANT
date_col date DEFAULT (CURRENT_DATE + INTERVAL '2 YEAR'), -- EXPRESSION
i INT DEFAULT -1, -- CONSTANT
c VARCHAR(10) DEFAULT '', -- CONSTANT
price DOUBLE precision DEFAULT 0.00, -- CONSTANT
-- literal defaults
i2 INT DEFAULT 0, -- CONSTANT
i3 INT DEFAULT 3, -- CONSTANT
pi_val FLOAT DEFAULT 3.14, -- CONSTANT
--
f FLOAT DEFAULT 3.14, -- CONSTANT
d DATE DEFAULT (CURRENT_DATE + INTERVAL '1 YEAR'), -- EXPRESSION
p POINT DEFAULT (Point(0,0)), -- EXPRESSION
j JSON DEFAULT '{"a":"b"}' -- CONSTANT
)
Similar part in MySQL
See https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html
Column with default expression have DEFAULT_GENERATED in Extra column in Information Schema
https://dev.mysql.com/doc/refman/8.0/en/information-schema-columns-table.html
Yo can get that info in adbin field from attdef in the pg_catalog
If you have for example:
create table table1(
w_def_expr text default current_user,
w_def_cons text default 'hi',
wo_def text
);
You can get that info with this query:
select adrelid, tab.relname, attname, pg_get_expr(adbin, 0) expr,
split_part(substr(adbin,2),' ',1) expr_type
from pg_attribute col
inner join pg_class tab on col.attrelid = tab.oid
inner join pg_attrdef def on col.attrelid = def.adrelid and col.attnum = def.adnum
where tab.relname='table1';
Getting something like:
The way of getting info in adbin is not standar and can be change in the future.
I didn't find a way to do it better.
Here you can se an answer from 2017 saying that is not possible

Why can't I insert a null value into a notNull column which has a default value? [duplicate]

I just migrated my app from mysql to postgres but when I try to insert a record in a specific table I get violates not-null constraint error:
ERROR: null value in column "id" violates not-null constraint DETAIL: Failing row contains (null, 1, 1, null, null, null, 2016-03-09 09:24:12.841891, 2012-12-31 23:00:00, 2012-12-31 23:00:00, null, null, f, null, f, XYZAssignment, null, null, null, null).
********** Error **********
ERROR: null value in column "id" violates not-null constraint
SQL state: 23502
Detail: Failing row contains (null, 1, 1, null, null, null, 2016-03-09 09:24:12.841891, 2012-12-31 23:00:00, 2012-12-31 23:00:00, null, null, f, null, f, XYZAssignment, null, null, null, null).
When I try to create the record using factory_girl:
#assignment = FactoryGirl.create(:assignment)
It builds this sql query:
INSERT INTO assignments(
id, account_id, l_id, viewed_at, accepted_at, declined_at,
expires_at, created_at, updated_at, decline_reason, decline_reason_text,
promotion, c_checked_at, forwardable, type, f_promo,
c_check_successful, c_check_api_result, c_check_human_result)
VALUES (null, 1, 1, null, null, null, '2016-03-09 09:24:12.841891', '2012-12-31 23:00:00', '2012-12-31 23:00:00', null, null, 'f', null, 'f', 'XYZAssignment', null, null, null, null);
This is the assignment factory:
FactoryGirl.define do
factory :assignment do
expires_at 24.hours.from_now
account
lead
end
end
this is the table description:
CREATE TABLE assignments(
id serial NOT NULL, account_id integer NOT NULL, l_id integer NOT NULL, viewed_at timestamp without time zone, accepted_at timestamp without time zone, declined_at timestamp without time zone, expires_at timestamp without time zone, created_at timestamp without time zone, updated_at timestamp without time zone, decline_reason character varying(16), decline_reason_text character varying(256), promotion boolean NOT NULL DEFAULT false, c_checked_at timestamp without time zone, forwardable boolean DEFAULT true, type character varying(64), f_promo boolean, c_check_successful boolean, c_check_api_result character varying(32), c_check_human_result character varying(32), CONSTRAINT assignments_pkey PRIMARY KEY (id)
) WITH (
OIDS=FALSE
);
Looks its not able to auto increment the id, any idea?
You have to skip id in the INSERT operation:
INSERT INTO assignments(account_id, l_id, ...)
VALUES
(1, 1, ...)
The id will automatically get the next sequence number, since it is an auto-increment field.

Query execution time increased dramatically without type cast

The query in this state takes more than 5 minutes to execute. If I remove any of the ::DATE conversions (see comment in code) the execution time goes < 500 ms.
For example, if I change gf.created::DATE to gf.created the performance is dramatically increased. Same happens if I change gtg.created::DATE to gtg.created.
Why is there a huge difference when using both ::DATE conversions if each shows great performance on its own?
SELECT gtg6.tipo_ganado, COUNT(gtg6.tipo_ganado) animales
FROM agroapp.ganado g
INNER JOIN (SELECT gf5.ganado_id, gf5.fundo_id
FROM agroapp.ganado_fundo gf5
INNER JOIN (SELECT MAX(gf3.ganado_fundo_id) ganado_fundo_id
FROM agroapp.ganado_fundo gf3
INNER JOIN (SELECT gf.ganado_id, MAX(gf.created) created
FROM agroapp.ganado_fundo gf
WHERE gf.isactive = 'Y'
-- HERE CHANGING gf.created::DATE TO gf.created
AND gf.created::DATE <= '20181030'::DATE
GROUP BY gf.ganado_id) gf2 ON (gf2.ganado_id = gf3.ganado_id AND gf2.created = gf3.created)
WHERE gf3.isactive = 'Y'
GROUP BY gf3.ganado_id) gf4 ON gf4.ganado_fundo_id = gf5.ganado_fundo_id
) gf6 ON gf6.ganado_id = g.ganado_id
INNER JOIN (SELECT gtg5.ganado_id, gtg5.tipo_ganado
FROM agroapp.ganado_tipo_ganado gtg5
INNER JOIN (SELECT MAX(gtg3.ganado_tipo_ganado_id) ganado_tipo_ganado_id
FROM agroapp.ganado_tipo_ganado gtg3
INNER JOIN (SELECT gtg.ganado_id, MAX(gtg.created) created
FROM agroapp.ganado_tipo_ganado gtg
WHERE gtg.isactive = 'Y'
-- OR HERE CHANGING gtg.created::DATE TO gtg.created
AND gtg.created::DATE <= '20181030'::DATE
GROUP BY gtg.ganado_id) gtg2 ON (gtg2.ganado_id = gtg3.ganado_id AND gtg2.created = gtg3.created)
WHERE gtg3.isactive = 'Y'
GROUP BY gtg3.ganado_id) gtg4 ON gtg4.ganado_tipo_ganado_id = gtg5.ganado_tipo_ganado_id
) gtg6 ON gtg6.ganado_id = g.ganado_id
WHERE g.organizacion_id = 21
GROUP BY gtg6.tipo_ganado
ORDER BY gtg6.tipo_ganado;
Table definitions
All 3 tables have around 50000 rows:
CREATE TABLE agroapp.ganado_fundo
(
ganado_fundo_id serial NOT NULL,
organizacion_id integer NOT NULL,
isactive character(1) NOT NULL DEFAULT 'Y'::bpchar,
created timestamp without time zone NOT NULL DEFAULT now(),
createdby numeric(10,0) NOT NULL,
updated timestamp without time zone NOT NULL DEFAULT now(),
updatedby numeric(10,0) NOT NULL,
fundo_id integer NOT NULL,
ganado_id integer NOT NULL,
CONSTRAINT ganado_fundo_pk PRIMARY KEY (ganado_fundo_id),
CONSTRAINT ganado_fk FOREIGN KEY (ganado_id)
REFERENCES agroapp.ganado (ganado_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
CREATE TABLE agroapp.ganado_tipo_ganado
(
ganado_tipo_ganado_id serial NOT NULL,
organizacion_id integer NOT NULL,
isactive character(1) NOT NULL DEFAULT 'Y'::bpchar,
created timestamp without time zone NOT NULL DEFAULT now(),
createdby numeric(10,0) NOT NULL,
updated timestamp without time zone NOT NULL DEFAULT now(),
updatedby numeric(10,0) NOT NULL,
tipo_ganado character varying(80) NOT NULL,
ganado_id integer NOT NULL,
CONSTRAINT ganado_tipo_ganado_pk PRIMARY KEY (ganado_tipo_ganado_id),
CONSTRAINT ganado_fk FOREIGN KEY (ganado_id)
REFERENCES agroapp.ganado (ganado_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
CREATE TABLE agroapp.ganado
(
ganado_id serial NOT NULL,
organizacion_id integer NOT NULL,
isactive character(1) NOT NULL DEFAULT 'Y'::bpchar,
created timestamp without time zone NOT NULL DEFAULT now(),
createdby numeric(10,0) NOT NULL,
updated timestamp without time zone NOT NULL DEFAULT now(),
updatedby numeric(10,0) NOT NULL,
fecha_nacimiento timestamp without time zone NOT NULL,
tipo_ganado character varying(80) NOT NULL,
diio_id integer NOT NULL,
fundo_id integer NOT NULL,
raza_id integer NOT NULL,
estado_reproductivo character varying(80) NOT NULL,
estado_leche character varying(80),
CONSTRAINT ganado_pk PRIMARY KEY (ganado_id),
CONSTRAINT diio_fk FOREIGN KEY (diio_id)
REFERENCES agroapp.diio (diio_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fundo_fk FOREIGN KEY (fundo_id)
REFERENCES agroapp.fundo (fundo_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT raza_fk FOREIGN KEY (raza_id)
REFERENCES agroapp.raza (raza_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Most probably because the forced cast voids the option to use an index on the column agroapp.ganado_fundo.created
Guessing (for lack of information) that gf.created is of type timestamp with time zone (or timestamp), replace
AND gf.created::DATE <= '20181030'::DATE
with:
AND gf.created < '2018-10-31'::timestamp -- match the data type of the column!
to achieve the same result, but with index support.
If you operate with timestamtptz, be aware of implications on the date: it depends on the current time zone. Details:
Ignoring time zones altogether in Rails and PostgreSQL

Postgres error "ERROR: INSERT has more target columns than expressions"

I have the following tables :
CREATE TABLE public.participant_audit
(
participant_audit_id bigint NOT NULL DEFAULT nextval('participant_audit_participant_audit_id_seq'::regclass),
participant_id bigint,
shared_asset_id bigint NOT NULL,
asset_role_type character varying(200) NOT NULL,
user_external_ref_uuid uuid NOT NULL,
user_first_name character varying(200) NOT NULL,
user_last_name character varying(200) NOT NULL,
user_email_address character varying(200) NOT NULL,
deleted_timestamp timestamp(0) with time zone,
row_updated_timestamp timestamp(6) with time zone NOT NULL,
row_created_timestamp timestamp(6) with time zone NOT NULL,
row_created_by_db_user oid NOT NULL,
row_updated_by_db_user oid NOT NULL,
created_by_client uuid,
updated_by_client uuid,
CONSTRAINT participant_audit_pkey PRIMARY KEY (participant_audit_id)
)
WITH (
OIDS=FALSE
);
CREATE TABLE public.participant
(
participant_id bigint NOT NULL DEFAULT nextval('participant_participant_id_seq'::regclass),
shared_asset_id bigint NOT NULL,
asset_role_type_id bigint NOT NULL,
user_external_ref_uuid uuid NOT NULL,
user_first_name character varying(200) NOT NULL,
user_last_name character varying(200) NOT NULL,
user_email_address character varying(200) NOT NULL,
deleted_timestamp timestamp(0) with time zone,
row_updated_timestamp timestamp(6) with time zone NOT NULL,
row_created_timestamp timestamp(6) with time zone NOT NULL,
row_created_by_db_user oid NOT NULL,
row_updated_by_db_user oid NOT NULL,
created_by_client uuid,
updated_by_client uuid,
CONSTRAINT participant_pkey PRIMARY KEY (participant_id),
CONSTRAINT participant_asset_role_type_id_fkey FOREIGN KEY (asset_role_type_id)
REFERENCES public.asset_role_type (asset_role_type_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT participant_shared_asset_id_fkey FOREIGN KEY (shared_asset_id)
REFERENCES public.shared_asset (shared_asset_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
And the following TRIGGER FUNCTION:
-- DROP FUNCTION public.participant_audit();
CREATE OR REPLACE FUNCTION public.participant_audit()
RETURNS trigger AS
$BODY$
BEGIN
insert into participant_audit
(participant_audit_id, participant_id , shared_asset_id , asset_role_type , user_external_ref_uuid,
user_first_name , user_last_name , user_email_address , deleted_timestamp, row_updated_timestamp,
row_created_timestamp , row_created_by_db_user , row_updated_by_db_user , created_by_client,
updated_by_client
)
select NEW.* ;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER
COST 100;
When I execute the following INSERT
INSERT INTO participant (shared_asset_id,asset_role_type_id,
user_external_ref_uuid,user_first_name,user_last_name,
user_email_address,row_created_by_db_user,
row_updated_by_db_user,created_by_client,updated_by_client)
VALUES (1, 1, 'c9d140ad-b0da-4a9d-a898-8719000c7b7b'::uuid , 'john', 'simpson', 'js#gmail.com', 1::oid,1::oid, '53ed670d-f680-4e81-b53d-59b3d487633f'::uuid, '53ed670d-f680-4e81-b53d-59b3d487633f'::uuid);
I get the following error:
ERROR: INSERT has more target columns than expressions LINE 2:
...user , row_updated_by_db_user , created_by_client,updated_by...
^ QUERY: insert into public.participant_audit
(participant_audit_id, participant_id , shared_asset_id , asset_role_type ,
user_external_ref_uuid,user_first_name , user_last_name ,
user_email_address , deleted_timestamp,
row_updated_timestamp,row_created_timestamp , row_created_by_db_user ,
row_updated_by_db_user , created_by_client,updated_by_client)
select NEW.* CONTEXT: PL/pgSQL function participant_audit() line 3 at SQL statement
********** Error **********
ERROR: INSERT has more target columns than expressions SQL state:
42601 Context: PL/pgSQL function participant_audit() line 3 at SQL
statement
How can I fix this issue ??
The problem is in your trigger. Count the columns that you are trying to insert into the audit table here.
insert into participant_audit
(participant_audit_id, participant_id , shared_asset_id , asset_role_type , user_external_ref_uuid,
user_first_name , user_last_name , user_email_address , deleted_timestamp, row_updated_timestamp,
row_created_timestamp , row_created_by_db_user , row_updated_by_db_user , created_by_client,
updated_by_client
)
select NEW.* ;
That's quite a few more than what's contained in NEW because your insert statement has only 10 columns in it. I believe some of your columns maybe taking NULL values. Pass nulls explicitly in the SELECT part of your statement inside the trigger.

ERROR: syntax error at or near "SET"

UPDATE m_price
SET pricelist=15.159, pricestd=14.3184
WHERE m_product_id = 1000332
ERROR: syntax error at or near "SET" i am getting this error in POSTGRESQL.
table structure
CREATE TABLE m_price (
m_pricelist_version_id numeric(10,0) NOT NULL,
m_product_id numeric(10,0) NOT NULL,
ad_client_id numeric(10,0) NOT NULL,
ad_org_id numeric(10,0) NOT NULL,
isactive character(1) DEFAULT 'Y'::bpchar NOT NULL,
created timestamp without time zone DEFAULT now() NOT NULL,
createdby numeric(10,0) NOT NULL,
updated timestamp without time zone DEFAULT now() NOT NULL,
updatedby numeric(10,0) NOT NULL,
pricelist numeric DEFAULT 0 NOT NULL,
pricestd numeric DEFAULT 0 NOT NULL,
pricelimit numeric DEFAULT 0 NOT NULL,
CONSTRAINT m_productprice_isactive_check CHECK ((isactive = ANY (ARRAY['Y'::bpchar, 'N'::bpchar])))
);
Try
UPDATE m_price
SET (pricelist,pricestd)=(15.159,14.3184)
WHERE m_product_id = 1000332