Inserting array values - arrays

How do I write and execute a query which inserts array values using libpqxx?
INSERT INTO exampleTable(exampleArray[3]) VALUES('{1, 2, 3}');
This example code gives me:
ERROR: syntax error at or near "'"
What is wrong? In PostgreSQL documentation I found that:
CREATE TABLE sal_emp (
name text,
pay_by_quarter integer[],
schedule text[][]
);
...
INSERT INTO sal_emp
VALUES ('Bill',
'{10000, 10000, 10000, 10000}',
'{{"meeting", "lunch"}, {"training", "presentation"}}');

You should use a column name without an index to insert an array:
create table example(arr smallint[]);
insert into example(arr) values('{1, 2, 3}');
-- alternative syntax
-- insert into example(arr) values(array[1, 2, 3]);
select * from example;
arr
---------
{1,2,3}
(1 row)
Use the column name with an index to access a single element of the array:
select arr[2] as "arr[2]"
from example;
arr[2]
--------
2
(1 row)
update example set arr[2] = 10;
select * from example;
arr
----------
{1,10,3}
(1 row)
You can use arr[n] in INSERT but this has special meaning. With this syntax you can create an array with one element indexed from the given number:
delete from example;
insert into example(arr[3]) values (1);
select * from example;
arr
-----------
[3:3]={1}
(1 row)
As a result you have an array which lower bound is 3:
select arr[3] from example;
arr
-----
1
(1 row)

ref: https://ubiq.co/database-blog/how-to-insert-into-array-in-postgresql/
A. use ARRAY
insert into employees (id, name, phone_numbers)
values (1, ' John Doe', ARRAY ['9998765432','9991234567']);
// less nested quotes
B. use '{}'
insert into employees (id, name, phone_numbers)
values (2, ' Jim Doe', '{"9996587432","9891334567"}');
OR
insert into employees (id, name, phone_numbers)
values (2, ' Jim Doe', '{9996587432,9891334567}');
// seems inner quotes " not necessary,
// number or string depends on column type.

Related

Postgres how to use ANY instead of IN

I have a query but it is not working correctly,
Select *
from "Firms"
Where "Properties" IN ('{1,2}')
That's my postgres query,
"Properties" column is int array.
Only those containing these two values ​​are coming, but I want to fetch records containing any of the values, and I want to list by number of matching values ​​if possible.
Test case:
create table array_any(id integer, array_fld int[]);
insert into array_any values (1, ARRAY[1,2]), (2, ARRAY[2,3]), (3, ARRAY[3,4]);
select id, count(*) from array_any,
lateral unnest(array_fld) as s where s = ANY(ARRAY[1,2]) group by id order by id;
id | count
----+-------
1 | 2
2 | 1

MSSQL select nearest match (id) of specified condition

I have a table filled with some words of different length.
ID | WORD | LENGTH
1 | able | 4
2 | acid | 4
3 | about | 5
.....
method in C# is generating random number and I want to get word with nearest match of length and ID. Recently I am using this query
select top 1 word from vocabulary where length = 4 and id <= 3;
problem is that this way it always returns first occurrence of word with 4 letters. That's not what I need.
I can't use this:
select top 1 word from vocabulary where length = 4 and id <= 3;
because when the random number is close to the last id in the table, it could happen that there will be no other word with requested length. (e.g.
select top 1 word from vocabulary where length = 4 and id >= 2;
would not be able to find match.
Is there a way how to select 1 value that has nearest match in the requested direction? <= or >=
Thanks.
declare #vocabulary table (ID int, Word varchar(max), LENGTH int)
insert into #vocabulary(ID,Word,LENGTH)values(1,'able',4),(2,'acid',4),(3,'about',5)
declare #random int = rand() * 10
select #random
select top 1 word from #vocabulary where LENGTH = 4 order by ABS(ID - #random)
Result is the neareast id from random number
Not sure if it's crucial to retrieve the row based on an externally generated random number, but if you just want a random word of given length, you could do something like this...
Setup:
DROP TABLE IF EXISTS DICTIONARY;
CREATE TABLE DICTIONARY (
ID int,
WORD nvarchar(255),
LENGTH AS LEN(WORD),
CONSTRAINT DICTIONARY_PK PRIMARY KEY (ID),
);
CREATE INDEX DICTIONARY_I1 ON DICTIONARY (LENGTH) INCLUDE (WORD);
INSERT INTO DICTIONARY (ID, WORD) VALUES
(1, 'able'),
(2, 'acid'),
(3, 'about'),
(4, 'boss'),
(5, 'brain'),
(6, 'child'),
(7, 'computer'),
(8, 'hint'),
(9, 'human'),
(10, 'ichthyosaur'),
(11, 'mother'),
(12, 'otorhinolaryngologist');
Query for getting a random row of given length (4 in this example):
DECLARE #length int = 4;
SELECT TOP 1 * FROM DICTIONARY WHERE LENGTH = #length ORDER BY NEWID();
The query plan is nice, which may be important for a large table and/or frequent querying:

How to update table based on a json map?

If I have this table
CREATE TABLE tmp (
a integer,
b integer,
c text
);
INSERT INTO tmp (a, b, c) VALUES (1, 2, 'foo');
And this json:
{
"a": 4,
"c": "bar"
}
Where the keys map to the column names, and the values are the new values.
How can I update the tmp table without touching columns that aren't in the map?
I thought about constructing a dynamic string of SQL update statement that can be executed in pl/pgsql, but it seems the number of arguments that get passed to USING must be predetermined. But the actual number of arguments is determined by the number of keys in the map, which is dynamic, so this seems like a dead end.
I know I can update the table using multiple update statements as I loop over the keys, but the problem is that I have a trigger set up for the table that will revision the table (by inserting changed columns into another table), so the columns must be updated in a single update statement.
I wonder if it's possible to dynamically update a table with a json map?
Use coalesce(). Example table:
drop table if exists my_table;
create table my_table(id int primary key, a int, b text, c date);
insert into my_table values (1, 1, 'old text', '2017-01-01');
and query:
with jsondata(jdata) as (
values ('{"id": 1, "b": "new text"}'::jsonb)
)
update my_table set
a = coalesce((jdata->>'a')::int, a),
b = coalesce((jdata->>'b')::text, b),
c = coalesce((jdata->>'c')::date, c)
from jsondata
where id = (jdata->>'id')::int;
select * from my_table;
id | a | b | c
----+---+----------+------------
1 | 1 | new text | 2017-01-01
(1 row)

Matching tables on unordered sets (arrays) with optional subsets

I'm trying to match observations in two tables based on a set of characteristics.
Table1:
id1;set1
A;{1,2,3,4}
B;{6,9,4,5}
Table2:
id2;set2
F;{1,2,3,4}
G;{7,6,2,4}
H;{6,{1,9},4,5}
In theory sets are unordered and every element of the sets in table 1 should match an element in table 2, however, when the element is a subset, the element in table 1 should belong to the subset in table 2.
Result should be:
MATCH_TABLE:
id1;id2
A;F
B;H
"A" in table 1 matches "F" in table 2, trivially.
Match "B","H" is more complex. The second element of set1, "1" belongs to "{1,9}", the subset in "H", and that is way the all match.
How should I model this data in Postgresql and perform this match?
PS: Ignoring the case H, of sets with subsets, I had created set1 and set2 as arrays, ordered them, and INNER JOINed the tables on the array fields.
Install intarray extension in which you can find type query_int ideally fitted to your need.
create extension if not exists intarray;
create table table1 (id1 text, set1 int[]);
insert into table1 values
('A', '{1,2,3,4}'),
('B', '{6,9,4,5}');
create table table2 (id2 text, set2 query_int);
insert into table2 values
('F', '1 & 2 & 3 & 4'),
('G', '7 & 6 & 2 & 4'),
('H', '6 & (1 | 9) & 4 & 5');
select id1, id2
from table1
join table2
on set1 ## set2
id1 | id2
-----+-----
A | F
B | H
(2 rows)

Retrieving full hierarchy sorted by a column under PostgreSQL's Ltree module

I'm using PostgreSQL's Ltree module for storing hierarchical data. I'm looking to retrieve the full hierarchy sorted by a particular column.
Consider the following table:
votes | path | ...
-------+-------+-----
1 | 1 | ...
2 | 1.1 | ...
4 | 1.2 | ...
1 | 1.2.1 | ...
3 | 2 | ...
1 | 2.1 | ...
2 | 2.1.1 | ...
4 | 2.1.2 | ...
... | ... | ...
In my current implementation, I'd query the database with SELECT * FROM comments ORDER BY path, which would return the whole tree:
Node 1
-- Node 1.1
-- Node 1.2
---- Node 1.2.1
Node 2
-- Node 2.1
---- Node 2.1.1
---- Node 2.1.2
However, I want to sort by votes (not by id, which is what sorting by path amounts to). Each depth level needs to be independently sorted, with the correct tree structure kept intact. Something that would return the following:
Node 2
-- Node 2.1
---- Node 2.1.2
---- Node 2.1.1
Node 1
-- Node 1.2
---- Node 1.2.1
-- Node 1.1
Postgres' WITH RECURSIVE might be appropriate, but I'm not sure. Any ideas?
You were on the right track with WITH RECURSIVE.
Solution with recursive CTE
WITH RECURSIVE t AS (
SELECT t.votes
, t.path
, 1::int AS lvl
, to_char(t2.votes, 'FM0000000') AS sort
FROM tbl t
JOIN tbl t2 ON t2.path = subltree(t.path, 0, 1)
UNION ALL
SELECT t.votes
, t.path
, t.lvl + 1
, t.sort || to_char(t2.votes, 'FM0000000')
FROM t
JOIN tbl t2 ON t2.path = subltree(t.path, 0, t.lvl + 1)
WHERE nlevel(t.path) > t.lvl
)
SELECT votes, path, max(sort) AS sort
FROM t
GROUP BY 1, 2
ORDER BY max(sort), path;
Major points
The crucial part is to replace every level of the path with the value of votes. Thereby we assemble one column we can ORDER BY at the end. This is necessary, because the path has an unknown depth and we cannot order by an unknown number of expressions in static SQL.
In order to get a stable sort, I convert votes to a string with leading zeroes using to_char(). I use seven digits in the demo, which works for vote values below 10.000.000. Adjust according to your maximum vote count.
In the final SELECT I exclude all intermediary states to eliminate duplicates. Only the last step with max(sort) remains.
This works in standard SQL with a recursive CTE, but is not very efficient for large trees. A plpgsql function that recursively updates the sort path in a temporary table without creating temporary dupes might perform better.
Only works with the additional module ltree installed, which provides the functions subltree() and nlevel(), as well as the ltree data type.
My test setup, for review convenience:
CREATE TEMP TABLE tbl(votes int, path ltree);
INSERT INTO tbl VALUES
(1, '1')
, (2, '1.1')
, (4, '1.2')
, (1, '1.2.1')
, (3, '2')
, (1, '2.1')
, (2, '2.1.1')
, (4, '2.1.2')
, (1, '2.1.3')
, (2, '3')
, (17, '3.3')
, (99, '3.2')
, (10, '3.1.1')
, (2345, '3.1.2')
, (1, '3.1.3')
;
PL/pgSQL table function doing the same
Should be faster with huge trees.
CREATE OR REPLACE FUNCTION f_sorted_ltree()
RETURNS TABLE(votes int, path ltree)
LANGUAGE plpgsql VOLATILE AS
$func$
DECLARE
lvl integer := 0;
BEGIN
CREATE TEMP TABLE t ON COMMIT DROP AS
SELECT tbl.votes
, tbl.path
, ''::text AS sort
, nlevel(tbl.path) AS depth
FROM tbl;
-- CREATE INDEX t_path_idx ON t (path); -- beneficial for huge trees
-- CREATE INDEX t_path_idx ON t (depth);
LOOP
lvl := lvl + 1;
UPDATE t SET sort = t.sort || to_char(v.votes, 'FM0000000')
FROM (
SELECT t2.votes, t2.path
FROM t t2
WHERE t2.depth = lvl
) v
WHERE v.path = subltree(t.path, 0 ,lvl);
EXIT WHEN NOT FOUND;
END LOOP;
-- Return sorted rows
RETURN QUERY
SELECT t.votes, t.path
FROM t
ORDER BY t.sort;
END
$func$;
Call:
SELECT * FROM f_sorted_ltree();
Read in the manual about setting temp_buffers.
I would be interested which performs faster with your real life data.
create table comments (
id serial,
parent_id int,
msg text,
primary key (id)
);
insert into comments (id, parent_id, msg) values (1, null, 'msg 1');
insert into comments (id, parent_id, msg) values (2, null, 'msg 2');
insert into comments (id, parent_id, msg) values (3, 1, 'msg 1 / ans 1');
insert into comments (id, parent_id, msg) values (4, null, 'msg 3');
insert into comments (id, parent_id, msg) values (5, 2, 'msg 2 / ans 1');
insert into comments (id, parent_id, msg) values (6, 2, 'msg 2 / ans 2');
insert into comments (id, parent_id, msg) values (7, 2, 'msg 2 / ans 3');
desc
WITH RECURSIVE q AS
(
SELECT id, msg, 1 as level, ARRAY[id] as path
FROM comments c
WHERE parent_id is null
UNION ALL
SELECT sub.id, sub.msg, level + 1, path || sub.id
FROM q
JOIN comments sub
ON sub.parent_id = q.id
)
SELECT id, msg, level
FROM q
order by path || array_fill(100500, ARRAY[8 - level]) desc;
results in
4,"msg 3",1
2,"msg 2",1
7,"msg 2 / ans 3",2
6,"msg 2 / ans 2",2
5,"msg 2 / ans 1",2
1,"msg 1",1
3,"msg 1 / ans 1",2
asc
WITH RECURSIVE q AS
(
SELECT id, msg, 1 as level, ARRAY[id] as path
FROM comments c
WHERE parent_id is null
UNION ALL
SELECT sub.id, sub.msg, level + 1, path || sub.id
FROM q
JOIN comments sub
ON sub.parent_id = q.id
)
SELECT id, msg, level
FROM q
--order by path || array_fill(100500, ARRAY[8 - level]) desc;
order by path;
results in
1,"msg 1",1
3,"msg 1 / ans 1",2
2,"msg 2",1
5,"msg 2 / ans 1",2
6,"msg 2 / ans 2",2
7,"msg 2 / ans 3",2
4,"msg 3",1

Resources