postgresql insert with integer PK nextval - postgresql

So, I have the following table definition with the id as integer PK using a nextval seq as shown below.
'''
CREATE TABLE public.fi_raisedalarms
(
id integer NOT NULL DEFAULT nextval('fi_raisedalarms_id_seq'::regclass),
equipid integer,
alid integer,
isset boolean,
tstamp timestamp without time zone,
create_uid integer,
create_date timestamp without time zone,
write_uid integer,
write_date timestamp without time zone,
CONSTRAINT fi_raisedalarms_pkey PRIMARY KEY (id),
CONSTRAINT fi_raisedalarms_alid_fkey FOREIGN KEY (alid)
REFERENCES public.fi_alarms (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE SET NULL,
CONSTRAINT fi_raisedalarms_create_uid_fkey FOREIGN KEY (create_uid)
REFERENCES public.res_users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE SET NULL,
CONSTRAINT fi_raisedalarms_write_uid_fkey FOREIGN KEY (write_uid)
REFERENCES public.res_users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE SET NULL
)
'''
I then want to use a function as shown below to insert omitting the id since it should pull the default value but I am getting the following error. Not sure where to go from here....
'''
CREATE OR REPLACE FUNCTION public.setequipmentalarm(
equipmentid integer,
alarmid integer,
isset boolean,
tstamp timestamp without time zone)
RETURNS integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
var integer;
BEGIN
INSERT INTO fi_raisedalarms VALUES(equipmentid, alarmid, isset, tstamp) RETURNING equipmentid into var;
RETURN var;
END;
$BODY$;
ALTER FUNCTION public.setequipmentalarm(integer, integer, boolean, timestamp without time zone)
OWNER TO postgres;
'''
ERROR: column "alid" is of type integer but expression is of type boolean
LINE 1: ...INTO fi_raisedalarms VALUES(equipmentid, alarmid, isset, tst...
^
HINT: You will need to rewrite or cast the expression.
QUERY: INSERT INTO fi_raisedalarms VALUES(equipmentid, alarmid, isset, tstamp) RETURNING equipmentid
CONTEXT: PL/pgSQL function setequipmentalarm(integer,integer,boolean,timestamp without time zone) line 5 at SQL statement
SQL state: 42804

You need to specify column names if you are not setting a value for each table column:
INSERT INTO fi_raisedalarms(equipid, alid , isset, tstamp) VALUES ...
Alternatively, you can insert DEFAULT in place of the column to explicitly choose the default value. But specifying the columns is preferable.

Related

Postgres SQL Table Partitioning by Range Timestamp not Unique key Collision

I have an issue when trying to modify and existing PostgreSQL (version 13.3) table to support partitioning it gets stuck when inserting the new data from the old table because the inserted timestamp in some cases may not be unique, so it fails on execution.
The partition forces me to create the primary to be the range (timestamp) value. You can see the new table definition below:
CREATE TABLE "UserFavorites_master" (
"Id" int4 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"UserId" int4 NOT NULL,
"CardId" int4 NOT NULL,
"CreationDate" timestamp NOT NULL,
CONSTRAINT "PK_UserFavorites_CreationDate" PRIMARY KEY ("CreationDate")
) partition by range ("CreationDate");
The original table didn't have a constraint on timestamp to either be unique or a primary key nor would we particularly want that but that seems to be a requirement of partitioning. Looking for alternatives or good ideas to solve the issue.
You can see the full code below:
alter table "UserFavorites" rename to "UserFavorites_old";
CREATE TABLE "UserFavorites_master" (
"Id" int4 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"UserId" int4 NOT NULL,
"CardId" int4 NOT NULL,
"CreationDate" timestamp NOT NULL,
CONSTRAINT "PK_UserFavorites_CreationDate" PRIMARY KEY ("CreationDate")
) partition by range ("CreationDate");
-- Frome Reference: https://stackoverflow.com/a/53600145/1190540
create or replace function createPartitionIfNotExists(forDate timestamp) returns void
as $body$
declare yearStart date := date_trunc('year', forDate);
declare yearEndExclusive date := yearStart + interval '1 year';
declare tableName text := 'UserFavorites_Partition_' || to_char(forDate, 'YYYY');
begin
if to_regclass(tableName) is null then
execute format('create table %I partition of "UserFavorites_master" for values from (%L) to (%L)', tableName, yearStart, yearEndExclusive);
-- Unfortunatelly Postgres forces us to define index for each table individually:
--execute format('create unique index on %I (%I)', tableName, 'UserId'::text);
end if;
end;
$body$ language plpgsql;
do
$$
declare rec record;
begin
loop
for rec in 2015..2030 loop
-- ... and create a partition for them
perform createPartitionIfNotExists(to_date(rec::varchar,'yyyy'));
end loop;
end
$$;
create or replace view "UserFavorites" as select * from "UserFavorites_master";
insert into "UserFavorites" ("Id", "UserId", "CardId", "CreationDate") select * from "UserFavorites_old";
It fails on the Last line with the following error:
SQL Error [23505]: ERROR: duplicate key value violates unique constraint "UserFavorites_Partition_2020_pkey"
Detail: Key ("CreationDate")=(2020-11-02 09:38:54.997) already exists.
ERROR: duplicate key value violates unique constraint "UserFavorites_Partition_2020_pkey"
Detail: Key ("CreationDate")=(2020-11-02 09:38:54.997) already exists.
ERROR: duplicate key value violates unique constraint "UserFavorites_Partition_2020_pkey"
Detail: Key ("CreationDate")=(2020-11-02 09:38:54.997) already exists.
No, partitioning doesn't force you to create a primary key. Just omit that line, and your example should work.
However, you definitely always should have a primary key on your tables. Otherwise, you can end up with identical rows, which is a major headache in a relational database. You might have to clean up your data.
#Laurenz Albe is correct, it seems I also have the ability to specify multiple keys though it may affect performance as referenced here Multiple Keys Performance, even indexing the creation date of the partition seemed to make the performance worse.
You can see a reference to multiple keys below, you mileage may vary.
CREATE TABLE "UserFavorites_master" (
"Id" int4 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"UserId" int4 NOT NULL,
"CardId" int4 NOT NULL,
"CreationDate" timestamp NOT NULL,
CONSTRAINT "PK_UserFavorites" PRIMARY KEY ("Id", "CreationDate")
) partition by range ("CreationDate");

You might need to add explicit type casts

I am trying to create function which returns last inserted id, but its showing error while function call:
Will you please help me to identify my error.
ERROR: function public.insert_voucher(integer, unknown, unknown, integer, integer, unknown) does not exist
LINE 1: select public.insert_voucher(1, 'P', '20180909', 1, 1, 'txt');
Function:
CREATE OR REPLACE FUNCTION public.insert_voucher(
in_orgid smallint
,in_transtype character
,in_date character
,in_partnerid smallint
,in_quantity smallint
,in_remarks character varying)
RETURNS integer AS
$BODY$
BEGIN
insert into
public.transaction_header(
org_id
,trans_type
,fiscal_year
,date
,partner_id
,quantity
,remarks
,create_by
,create_ts)
values (
in_orgid
,in_transtype
,1819
,in_date
,in_partnerid
,in_quantity
,in_remarks
,1
,now())
returning trans_header_id;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 1;
Table Schema:
trans_header_id integer NOT NULL DEFAULT nextval('transaction_header_trans_header_id_seq'::regclass)
,org_id smallint NOT NULL
,trans_type character(1) NOT NULL DEFAULT 'P'::bpchar
,fiscal_year smallint NOT NULL DEFAULT '1819'::smallint
,date date NOT NULL
,partner_id smallint NOT NULL
,quantity smallint NOT NULL
,remarks character varying(100)
,create_by smallint NOT NULL
,create_ts timestamp without time zone NOT NULL DEFAULT now()
,update_by smallint
,update_ts timestamp without time zone
,CONSTRAINT transaction_header_pk PRIMARY KEY (trans_header_id)
,CONSTRAINT create_by FOREIGN KEY (create_by)
REFERENCES public.app_user (user_id) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION
,CONSTRAINT org_id FOREIGN KEY (org_id)
REFERENCES public.organization (org_id) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION
,CONSTRAINT partner_id FOREIGN KEY (partner_id)
REFERENCES public.partners (partner_id) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION
,CONSTRAINT update_by FOREIGN KEY (update_by)
REFERENCES public.app_user (user_id) MATCH FULL
ON UPDATE NO ACTION ON DELETE NO ACTION
,CONSTRAINT org_fy_transtype_transno UNIQUE (org_id, trans_type, fiscal_year)
With reference #muistooshort and #stickybit, I am updating previous function.
Hope it will give more clarity and returns desired result.
CREATE OR REPLACE FUNCTION public.insert_voucher(
IN in_orgid smallint
,IN in_transtype character
,IN in_date date
,IN in_partnerid smallint
,IN in_quantity smallint
,IN in_remarks character varying
,OUT out_id smallint)
RETURNS smallint AS
$BODY$
BEGIN
insert into
public.transaction_header(
org_id
,trans_type
,fiscal_year
,date
,partner_id
,quantity
,remarks
,create_by
,create_ts)
values (
in_orgid
,in_transtype
,1819
,in_date
,in_partnerid
,in_quantity
,in_remarks
,1
,now())
RETURNING trans_header_id
INTO out_id;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 1;
The smallints are the problem. A cast from integer to smallint can mean losing some information. The engine won't do implicit casts, where information can get lost. Therefore it considers public.insert_voucher(integer, unknown, unknown, integer, integer, unknown) as not an option.
If you explicitly cast the numbers to smallint, the call should work.
SELECT public.insert_voucher(1::smallint, 'P', '20180909', 1::smallint, 1::smallint, 'txt');
There are some other things like why you pass a date as a string and '1819'::smallint (why the string first?). And some other problem may arise once the call works. But that's beyond the current question.

Function to insert data into different tables

I have three tables in PostgreSQL:
CREATE TABLE organization (id int, name text, parent_id int);
CREATE TABLE staff (id int, name text, family text, organization_id int);
CREATE TABLE clock(id int, staff_id int, Date date, Time time);
I need a function that gets all the fields of these tables as inputs (8 on total) and then inserts these inputs into appropriate fields of the tables
Here is my code:
CREATE FUNCTION insert_into_tables(org_name character varying(50), org_PID int, person_name character varying(50),_family character varying(50), org_id int, staff_id int,_date date, _time time without time zone)
RETURNS void AS $$
BEGIN
INSERT INTO "Org".organisation("Name", "PID")
VALUES ($1, $2);
INSERT INTO "Org".staff("Name", "Family", "Organization_id")
VALUES ($3, $4, $5);
INSERT INTO "Org"."Clock"("Staff_Id", "Date", "Time")
VALUES ($6, $7, $8);
END;
$$ LANGUAGE plpgsql;
select * from insert_into_tables('SASAD',9,'mamad','Imani',2,2,1397-10-22,'08:26:47')
But no data is inserted. I get the error:
ERROR: function insert_into_tables(unknown, integer, unknown, unknown, integer, integer, integer, unknown) does not exist
LINE 17: select * from insert_into_tables('SASAD',9,'mamad','Imani',2... ^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
Where did i go wrong?
That's because the 2nd last parameter is declared as date, not int. You forgot the single quotes:
select * from insert_into_tables('SASAD',9,'mamad','Imani',2,2,'1397-10-22','08:26:47');
Without single quotes, this is interpreted as subtraction between 3 integer constants, resulting in an integer: 1397-10-22 = 1365.
Also fix your identifiers: double-quoting preserves upper-case letters, so "Name" is distinct from name etc. See:
Are PostgreSQL column names case-sensitive?

Access database, Sql query , Error "Syntax error in DROP TABLE or DROP INDEX."

This is the query , running this in C#.
n getting above error
"DROP TABLE IF EXISTS `NATIONAL_ID_ISSUANCE_CENTER`;
CREATE TABLE `NATIONAL_ID_ISSUANCE_CENTER` (
`ID` INTEGER NOT NULL AUTO_INCREMENT,
`NAME` VARCHAR(100),
`APPLICATION_ID` INTEGER,
`STATUS` INTEGER,
`CREATED_BY` INTEGER,
`UPDATED_BY` INTEGER,
`CREATED_DATE` DATETIME,
`UPDATED_DATE` DATETIME,
`THIRD_PARTY_ID` INTEGER,
`PROVINCE_ID` INTEGER,
INDEX (`APPLICATION_ID`),
PRIMARY KEY (`ID`),
INDEX (`PROVINCE_ID`),
INDEX (`THIRD_PARTY_ID`)
)"
You can't put an IF statement inside Drop and Create statements. Anytime you want to drop a table that you're not sure exists, use the following:
IF(OBJECT_ID('[Database].[Schema].[TableName]') is not null)
BEGIN
DROP TABLE [Database].[Schema].[TableName];
END;
Please note you should replace [Database], [Schema], and [TableName] with the appropriate database, schema, and table names, respectively.

How to create a pageable function in PostgreSQL

I have two tables: event and location
CREATE TABLE location
(
location_id bigint NOT NULL,
version bigint NOT NULL,
active boolean NOT NULL,
created timestamp without time zone NOT NULL,
latitude double precision NOT NULL,
longitude double precision NOT NULL,
updated timestamp without time zone,
CONSTRAINT location_pkey PRIMARY KEY (location_id)
)
CREATE TABLE event
(
event_id bigint NOT NULL,
version bigint NOT NULL,
active boolean NOT NULL,
created timestamp without time zone NOT NULL,
end_date date,
entry_fee numeric(19,2),
location_id bigint NOT NULL,
organizer_id bigint NOT NULL,
start_date date NOT NULL,
timetable_id bigint,
updated timestamp without time zone,
CONSTRAINT event_pkey PRIMARY KEY (event_id),
CONSTRAINT fk_organizer FOREIGN KEY (organizer_id)
REFERENCES "user" (user_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_timetable FOREIGN KEY (timetable_id)
REFERENCES timetable (timetable_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_location FOREIGN KEY (location_id)
REFERENCES location (location_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Other tables are of lesser to no importance so they will not be shown (unless explicitly asked).
And for those tables, using cube and earthdistance pgsql extensions I've created the following function for finding all event_ids within a certain radius of a certain point.
CREATE OR REPLACE FUNCTION eventidswithinradius(
lat double precision,
lng double precision,
radius double precision)
RETURNS SETOF bigint AS
$BODY$
BEGIN
RETURN QUERY SELECT event.event_id
FROM event
INNER JOIN location ON location.location_id = event.location_id
WHERE earth_box( ll_to_earth(lat, lng), radius) #> ll_to_earth(location.latitude, location.longitude);
END;
$BODY$
And this works as expected. Now I wish to make it pageable, and am stuck on how to get all the necessary values (the table with paged contents and total count).
So far I've created this:
CREATE OR REPLACE FUNCTION pagedeventidswithinradius(
IN lat double precision,
IN lng double precision,
IN radius double precision,
IN page_size integer,
IN page_offset integer)
RETURNS TABLE( total_size integer , event_id bigint ) AS
$BODY$
DECLARE total integer;
BEGIN
SELECT COUNT(location.*) INTO total FROM location WHERE earth_box( ll_to_earth(lat, lng), radius) #> ll_to_earth(location.latitude, location.longitude);
RETURN QUERY SELECT total, event.event_id as event_id
FROM event
INNER JOIN location ON location.location_id = event.location_id
WHERE earth_box( ll_to_earth(lat, lng), radius) #> ll_to_earth(location.latitude, location.longitude)
ORDER BY event_id
LIMIT page_size OFFSET page_offset;
END;
$BODY$
Here count is called only once and stored in a variable since I assumed that if I placed COUNT into the return query itself it would be called for each row.
This kind of works, but it is difficult to parse on the back-end since the result is in the form of (count, event_id), also count is needlessly repeated over all result rows. I was hoping I could simply add total as an OUT param and have the function return the table and fill the OUT variable with total count, however it seems this is not allowed. I can always have the count be a separate function but I was wondering if there is a better way to approach this issue?
No, there isn't really a better option. You want two different types of quantities so you need two queries. You can improve upon your function, however:
CREATE FUNCTION eventidswithinradius(lat float8, long float8, radius float8) RETURNS SETOF bigint AS $BODY$
SELECT event.event_id
FROM event
JOIN location l USING (location_id)
WHERE earth_box(ll_to_earth(lat, lng), radius) #> ll_to_earth(l.latitude, l.longitude);
$BODY$ LANGUAGE sql STRICT;
As a LANGUAGE sql function it is more efficient than as a PL/pgSQL function, plus you can do your paging on the outside:
SELECT *
FROM eventidswithinradius(121.056, 14.582, 3000)
LIMIT 15 OFFSET 1;
Internally the query planner will resolve the function call to its underlying query and apply the paging directly to that level.
Get the total with the obvious:
SELECT count(id)
FROM eventidswithinradius(121.056, 14.582, 3000);