how to preserve spaces in char?
I create 2 tables ,
create table test_1 (a int, b char(10)) ;
create table test_2 (a int, b varchar(255));
and insert one row into test_1
insert into test_1 values (1 ,' ');
insert into test_2 select * from test_1;
select a, length(b) from test_2;
and it returns
| a | length(b) |
| -------- | -------------- |
| 1 | 0 |
I expect bleow, like oracle does
| a | length(b) |
| -------- | -------------- |
| 1 | 10 |
is there any option can i try ?
There is no way to change this behavior.
Observe also the following:
CREATE TABLE test_1 (a int, b char(10));
INSERT INTO test_1 VALUES (1, 'x');
SELECT length(b) FROM test_1;
length
--------
1
(1 row)
SELECT 'y' || b FROM test_1;
?column?
----------
yx
(1 row)
All this is working as required by the SQL standard.
Do yourself a favor and never use char. If you need the value to always have 10 characters, user a check constraint or a trigger that pads values with blanks.
Related
I have an array column I want to unnest, split the element values, and copy into another table. For example:
id | col1
-----------------
1 | '{"a:1", "b:2"}'
I'd like to insert into a new table that looks like:
table1_id | col1 | col2
------------------------
1 | 'a' | 1
1 | 'b' | 2
You can issue an insert from this select:
select id as table1_id,
(string_to_array(ary, ':'))[1] as col1,
(string_to_array(ary, ':'))[2] as col2
from table1
cross join lateral unnest(col1) as u(ary);
db<>fiddle here
Let's say I have this function that returns a table that shows how many letters are in a person's name:
CREATE TABLE people (name varchar);
INSERT INTO people VALUES ('jill');
INSERT INTO people VALUES ('jimmy');
CREATE OR REPLACE FUNCTION letter_count(person people) RETURNS TABLE(letter varchar, count bigint) AS $$
SELECT letter, COUNT(*) count FROM regexp_split_to_table(person.name, '') letter GROUP BY letter
$$ LANGUAGE sql STABLE;
I would like to invoke the function on just the person with name = jill, and I expect a result like this, which is returned by manually invoking the query in the function (SELECT letter, COUNT(*) count FROM regexp_split_to_table('jill', '') letter GROUP BY letter;):
| letter | count |
| ------ | ----- |
| j | 1 |
| i | 1 |
| l | 2 |
If I try this query:
SELECT letter_count(people.*) FROM people WHERE people.name='jill';
I get this as a result:
| letter_count |
| ------------ |
| (i,1) |
| (l,2) |
| (j,1) |
I've tried a number of other queries (SELECT * FROM letter_count((SELECT * FROM people WHERE name='jill')); seemed promising), but with no luck.
Here is a DB fiddle to play to reproduce what I'm seeing: https://www.db-fiddle.com/f/nBqwyGknRHJeWL5sdoFhhJ/0
You put such a table function in the FROM clause like a table.
Usually a lateral join is the best way to do that:
SELECT l.*
FROM people
CROSS JOIN LATERAL letter_count(people) AS l
WHERE people.name = 'jill';
Consider following scenario in PostgreSQL (any version from 10+):
CREATE TABLE users(
id serial primary key,
name text not null unique,
last_seen timestamp
);
INSERT INTO users(name, last_seen)
VALUES ('Alice', '2019-05-01'),
('Bob', '2019-04-29'),
('Dorian', '2019-05-11');
CREATE TABLE inactive_users(
user_id int primary key references users(id),
last_seen timestamp not null);
INSERT INTO inactive_users(user_id, last_seen)
SELECT id as user_id, last_seen FROM users
WHERE users.last_seen < '2019-05-04'
ON CONFLICT (user_id) DO UPDATE SET last_seen = excluded.last_seen;
Now let's say that I want to insert the same values (execute last statement) multiple times, every now and then. In practice from the database point of view, on conflicting values 90% of the time last_seen column will be updated to the same value it already had. The values of the rows stay the same, so there's no reason to do I/O writes, right? But is this really the case, or will postgres perform corresponding updates even though the actual value didn't change?
In my case the destination table has dozens of millions of rows, but only few hundreds/thousands of them will be really changing on each of the insert calls.
Any UPDATE to a row will actually create a new row (marking the old row deleted/dirty), regardless of the before/after values:
[root#497ba0eaf137 /]# psql
psql (12.1)
Type "help" for help.
postgres=# create table foo (id int, name text);
CREATE TABLE
postgres=# insert into foo values (1,'a');
INSERT 0 1
postgres=# select ctid,* from foo;
ctid | id | name
-------+----+------
(0,1) | 1 | a
(1 row)
postgres=# update foo set name = 'a' where id = 1;
UPDATE 1
postgres=# select ctid,* from foo;
ctid | id | name
-------+----+------
(0,2) | 1 | a
(1 row)
postgres=# update foo set id = 1 where id = 1;
UPDATE 1
postgres=# select ctid,* from foo;
ctid | id | name
-------+----+------
(0,3) | 1 | a
(1 row)
postgres=# select * from pg_stat_user_tables where relname = 'foo';
-[ RECORD 1 ]-------+-------
relid | 16384
schemaname | public
relname | foo
seq_scan | 5
seq_tup_read | 5
idx_scan |
idx_tup_fetch |
n_tup_ins | 1
n_tup_upd | 2
n_tup_del | 0
n_tup_hot_upd | 2
n_live_tup | 1
n_dead_tup | 2
<...>
And according to your example:
postgres=# select ctid,* FROM inactive_users ;
ctid | user_id | last_seen
-------+---------+---------------------
(0,1) | 1 | 2019-05-01 00:00:00
(0,2) | 2 | 2019-04-29 00:00:00
(2 rows)
postgres=# INSERT INTO inactive_users(user_id, last_seen)
postgres-# SELECT id as user_id, last_seen FROM users
postgres-# WHERE users.last_seen < '2019-05-04'
postgres-# ON CONFLICT (user_id) DO UPDATE SET last_seen = excluded.last_seen;
INSERT 0 2
postgres=# select ctid,* FROM inactive_users ;
ctid | user_id | last_seen
-------+---------+---------------------
(0,3) | 1 | 2019-05-01 00:00:00
(0,4) | 2 | 2019-04-29 00:00:00
(2 rows)
Postgres does not do any data validation against the column values -- if you are looking to prevent unnecessary write activity, you will need to surgically craft your WHERE clauses.
Disclosure: I work for EnterpriseDB (EDB)
I need to create a custom sequence based on a specific column, added as a prefix. I know it is possible to customize the sequence as well as the nextval, but I'm not sure if it is possible to use the column of a specific table.
This is the structure of the table with the essential information:
create table tab
(
id serial not null
constraint tab_pkey primary key,
year varchar(4) not null,
seq varchar(20) not null
);
create sequence tab_id_seq as integer;
I would like to automatically populate the "seq" column, as happens for normal sequences, according to this format:
{year}_{sequence}
where {year}_ is the prefix, while {sequence} is a progressive that starts again from 1 every year.
DESIRED RESULT
|--------|----------|---------|
| id | year | seq |
|--------|----------|---------|
| 10 | 2019 | 2019_1 |
|--------|----------|---------|
| 11 | 2019 | 2019_2 |
|--------|----------|---------|
| 12 | 2019 | 2019_3 |
|--------|----------|---------|
| 13 | 2019 | 2019_4 |
|--------|----------|---------|
| 14 | 2020 | 2020_1 | <--- sequence restarting
|--------|----------|---------|
| 15 | 2020 | 2020_2 |
|--------|----------|---------|
| 16 | 2020 | 2020_3 |
|--------|----------|---------|
N.B. there is no direct relationship between the id column and {sequence} element
For the following test structure :
create table test
(
id serial primary key
, year_val int
, seq varchar (10)
);
create or replace function fn_test () returns trigger language plpgsql as $$
declare
res_name varchar;
begin
drop table if exists tmp_test;
create temporary table tmp_test as select * from test;
insert into tmp_test values (new.id, new.year_val);
with cte as
(
select *
, year_val::varchar||'_'||(count(*) over (partition by year_val order by id))::varchar as built_res_name
from tmp_test
)
select built_res_name into res_name
from cte
where id = new.id;
new.seq := res_name;
return new;
end;
$$;
CREATE TRIGGER tg_test BEFORE INSERT ON test
FOR EACH ROW EXECUTE FUNCTION fn_test();
insert into test (year_val)
values (2019),(2019),(2019),(2019),(2020),(2020),(2020);
In the end I found a solution by using multiple sequences (one per year), created dynamically when entering the record.
A trigger, before the insertion invoke a procedure that creates the sequence (if it does not exist) and assigns the value to the seq column (if not assigned).
WORKFLOW
record insertion
sequence creation 'tab_ {year} _seq_id' if it does not exist
if the column seq is empty the value nextval is assigned (tab_ {year} _seq_id)
test insertions and deletions to verify that the column is populated in the correct way
TABLE STRUCTURE
CREATE TABLE tab (
id serial not null constraint tab_pkey primary key,
year varchar(4) not null,
seq varchar(20)
);
FUNCTION
CREATE FUNCTION tab_sequence_trigger_function() RETURNS trigger AS $$
BEGIN
IF NEW.seq IS NULL OR NEW.seq = '''' THEN
EXECUTE ('CREATE SEQUENCE IF NOT EXISTS tab_' || NEW.year || '_id_seq AS INTEGER');
NEW.seq = NEW.year || '_' || nextval('tab_' || NEW.year || '_id_seq');
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
TRIGGER
CREATE TRIGGER tab_sequence_trigger
BEFORE INSERT ON tab
FOR EACH ROW
EXECUTE PROCEDURE tab_sequence_trigger_function();
TEST
INSERT INTO tab (year) VALUES (2019);
INSERT INTO tab (year) VALUES (2019);
INSERT INTO tab (year) VALUES (2019);
INSERT INTO tab (year) VALUES (2019);
INSERT INTO tab (year) VALUES (2019);
DELETE FROM tab WHERE id=5;
INSERT INTO tab (year) VALUES (2019);
INSERT INTO tab (year) VALUES (2019);
INSERT INTO tab (year) VALUES (2020);
INSERT INTO tab (year) VALUES (2020);
INSERT INTO tab (year) VALUES (2021);
DELETE FROM tab WHERE id=8;
DELETE FROM tab WHERE id=9;
INSERT INTO tab (year) VALUES (2021);
INSERT INTO tab (year) VALUES (2020);
RESULT
SELECT * FROM tab;
----------------------
| id | year | seq |
----------------------
| 1 | 2019 | 2019_1 |
----------------------
| 2 | 2019 | 2019_2 |
----------------------
| 3 | 2019 | 2019_3 |
----------------------
| 4 | 2019 | 2019_4 |
----------------------
| 6 | 2019 | 2019_6 |
----------------------
| 7 | 2019 | 2019_7 |
----------------------
| 10 | 2021 | 2021_3 |
----------------------
| 11 | 2021 | 2021_4 |
----------------------
| 12 | 2020 | 2020_3 |
----------------------
how to return multiple row from multiple table in plpgsql, I tried to return rows from 2 tables with inner join?
this my code
CREATE OR REPLACE FUNCTION library.getallbookwithcategory()
RETURNS SETOF library.book AS
$BODY$
DECLARE
r library.book%rowtype;
BEGIN
FOR r IN select book.*,category.name from library.book left join library.category on category.id=book.category_id
WHERE book.id > 0 order by dateadded ASC
LOOP
-- can do some processing here
RETURN NEXT r;
END LOOP;
RETURN;
END
$BODY$
LANGUAGE plpgsql
I need return name from category
There are more ways how to do it. One:
postgres=# SELECT * FROM boo;
id | foo_id | a | b
----+--------+-----+------
1 | 1 | 100 | 1000
2 | 1 | 200 | 2000
3 | 2 | 300 | 4000
(3 rows)
postgres=# SELECT * FROM foo;
id | a | b
----+---+---
1 | 1 | 2
2 | 3 | 4
(2 rows)
CREATE OR REPLACE FUNCTION foo_boo()
RETURNS TABLE (foo_a int, foo_b int, boo_a int, boo_b int) AS $$
BEGIN
FOR foo_a, foo_b, boo_a, boo_b IN
SELECT f.a fa, f.b fb, b.a ba, b.b bb
FROM foo f
JOIN boo b
ON f.id = boo.foo_id
LOOP
RETURN NEXT;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
postgres=# SELECT * FROM foo_boo();
foo_a | foo_b | boo_a | boo_b
-------+-------+-------+-------
1 | 2 | 100 | 1000
1 | 2 | 200 | 2000
3 | 4 | 300 | 4000
(3 rows)