Insert based on select of hstore column - postgresql

In try to insert value from a hstore (postgreql) to a more generic table
In my car table, I have theses fields
id
fields (hstore)
My store table, I have theses fields
id
key
value
car_id
date
How to loop to my fields property in insert key, value to my store table.
Is there a way to do it with a select command?

Example data:
insert into car values
(1, 'brand=>ford, color=>yellow'),
(2, 'brand=>volvo, mileage=>50000, year=>2015');
Use the function each(hstore) to get pairs (key, value) of hstore column:
select id, key, value
from car, each(fields);
id | key | value
----+---------+--------
1 | brand | ford
1 | color | yellow
2 | year | 2015
2 | brand | volvo
2 | mileage | 50000
(5 rows)
The insert command may look like this:
insert into store (car_id, key, value)
select id, key, value
from car, each(fields);

Related

postgres: temporary column default that is unique and not nullable, without relying on a sequence?

Hi, I want to add a unique, non-nullable column to a table.
It
already has data. I would therefore like to instantly populate the
new column with unique values, eg 'ABC123', 'ABC124', 'ABC125', etc.
The data will eventually be wiped and
replaced with proper data, so i don't want to introduce a sequence
just to populate the default value.
Is it possible to generate a default value for the existing rows, based on something like rownumber()? I realise the use case is ridiculous but is it possible to achieve... if so how?
...
foo text not null unique default 'ABC'||rownumber()' -- or something similar?
...
can be applied generate_series?
select 'ABC' || generate_series(123,130)::text;
ABC123
ABC124
ABC125
ABC126
ABC127
ABC128
ABC129
ABC130
Variant 2 add column UNIQUE and not null
begin;
alter table test_table add column foo text not null default 'ABC';
with s as (select id,(row_number() over(order by id))::text t from test_table) update test_table set foo=foo || s.t from s where test_table.id=s.id;
alter table test_table add CONSTRAINT unique_foo1 UNIQUE(foo);
commit;
results
select * from test_table;
id | foo
----+------
1 | ABC1
2 | ABC2
3 | ABC3
4 | ABC4
5 | ABC5
6 | ABC6

Why is there a difference on UPDATE query results when a `UNIQUE INDEX` is involved?

I stumbled into Why would I get a duplicate key error when updating a row? so I tried a few things on https://extendsclass.com/postgresql-online.html.
Given the following schema:
create table scientist (id integer PRIMARY KEY, firstname varchar(100), lastname varchar(100));
insert into scientist (id, firstname, lastname) values (1, 'albert', 'einstein');
insert into scientist (id, firstname, lastname) values (2, 'isaac', 'newton');
insert into scientist (id, firstname, lastname) values (3, 'marie', 'curie');
select * from scientist;
CREATE UNIQUE INDEX fl_idx ON scientist(firstname, lastname);
when I run this query:
UPDATE scientist AS c SET
firstname = new_values.F,
lastname = new_values.L
FROM (
SELECT * FROM
UNNEST(
ARRAY[1, 1]::numeric[],
ARRAY['one', 'v']::text[],
ARRAY['three', 'f']::text[]
) AS T(
I,
F,
L
)
) AS new_values
WHERE c.id = new_values.I
RETURNING c.id, c.firstname, c.lastname;
I get back:
id firstname lastname
1 one three
whereas if I don't create the index (CREATE UNIQUE INDEX fl_idx ON scientist(firstname, lastname);) I get:
id firstname lastname
1 v f
So I am not sure why the UNIQUE INDEX affects the result and why there isn't a duplicate key value violates unique constraint exception when I change my UNNEST to (similar to what happens on the SO question I mentioned above) since the id is a PRIMARY KEY:
UNNEST(
ARRAY[1, 1]::numeric[],
ARRAY['one', 'one']::text[],
ARRAY['three', 'three']::text[]
)
The postgres version I run the above queries was:
PostgreSQL 11.11 (Debian 11.11-0+deb10u1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
From Update:
When using FROM you should ensure that the join produces at most one output row for each row to be modified. In other words, a target row shouldn't join to more than one row from the other table(s). If it does, then only one of the join rows will be used to update the target row, but which one will be used is not readily predictable.
In your case you have two matches for 1, so the choice is completely dependent in which order rows are read.
Here example when index is present for both runs and results are different:
db<>fiddle demo 1
db<>fiddle demo 2
Do you know why I don't get the "duplicate key value violates unique constraint" error?
There is no duplicate key neither on column id nor pair first_name/last_name after update is performed.
Scenario 1:
+-----+------------+----------+
| id | firstname | lastname |
+-----+------------+----------+
| 2 | isaac | newton |
| 3 | marie | curie |
| 1 | v | f |
+-----+------------+----------+
Scenario 2:
+-----+------------+----------+
| id | firstname | lastname |
+-----+------------+----------+
| 2 | isaac | newton |
| 3 | marie | curie |
| 1 | one | three |
+-----+------------+----------+
EDIT:
Using "UPSERT" and trying to insert/update row twice:
INSERT INTO scientist (id,firstname, lastname)
VALUES (1, 'one', 'three'), (1, 'v', 'f')
ON CONFLICT (id)
DO UPDATE SET firstname = excluded.firstname;
-- ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time

jsonb aggegation in postgres select queries

Given the following table:
# create table thing (id serial, tags jsonb);
# \d thing
Table "public.thing"
Column | Type | Modifiers
--------+---------+----------------------------------------------------
id | integer | not null default nextval('thing_id_seq'::regclass)
tags | jsonb |
...and the following data:
insert into thing (tags) values ('{"tag1": ["val1", "val2"], "tag2": ["t2val1"]}');
insert into thing (tags) values ('{"tag1": ["val3", "val1"], "tag2": ["t2val1"]}');
insert into thing (tags) values ('{"tag1": ["val2", "val1"], "tag2": ["t2val2"]}');
How can I aggregate the results of a query that equates to "show me the number of matching rows and the set of tag1 value that have a tag2 value of t2val1?
The closes I can get is:
# select count(*), json_agg(tags) from thing where tags->'tag2'?'t2val1';
count | json_agg
-------+--------------------------------------------------------------------------------------------------
2 | [{"tag1": ["val1", "val2"], "tag2": ["t2val1"]}, {"tag1": ["val3", "val1"], "tag2": ["t2val1"]}]
(1 row)
...but I really want:
count | tag1
-------+-------------------------
2 | ["val1", "val2", "val3"]
(1 row)

Complex TSQL MultiRow Insert with OutPut

I have a temp table as follows
DECLARE #InsertedRows TABLE (RevId INT, FooId INT)
I also have two other tables
Foo(FooId INT, MyData NVarchar(20))
Revisions(RevId INT, CreatedTimeStamp DATETIME)
For each row in Foo, I need to a) insert a row into Revisions and b) insert a row into #InsertedRows with the corresponding Id values from Foo and Revisions.
I've tried writing something using the Insert Output Select as follows:
INSERT INTO Revisions (CURRENT_TIMESTAMP)
OUTPUT Inserted.RevId, Foo.FooId INTO #InsertedRows
SELECT FooId From Foo
However, Foo.Id is not allowed in the Output column list. Also, the Id returned in the SELECT isn't inserted into the table, so that's another issue.
How can I resolve this?
You cannot reference the FROM table in an OUTPUT clause with an INSERT statement. You can only do this with a DELETE, UPDATE, or MERGE statement.
From the MSDN page on the OUTPUT clause (https://msdn.microsoft.com/en-us/library/ms177564.aspx)
from_table_name Is a column prefix that specifies a table included in
the FROM clause of a DELETE, UPDATE, or MERGE statement that is used
to specify the rows to update or delete.
You can use a MERGE statement to accomplish what you are asking.
In the below example, I changed the tables to be all variable tables so that this could be run as an independent query and I changed the ID columns to IDENTITY columns which increment differently to illustrate the relationship.
The ON clause (1=0) will always evaluate to NOT MATCHED. This means that all records in the USING statement will be used to insert into the target table. Additionally the FROM table in the USING statement will be available to use in the OUTPUT statement.
DECLARE #Foo TABLE (FooId INT IDENTITY(1,1), MyData NVarchar(20))
DECLARE #Revisions TABLE (RevId INT IDENTITY(100,10), CreatedTimeStamp DATETIME)
DECLARE #InsertedRows TABLE (RevId INT, FooId INT)
INSERT INTO #Foo VALUES ('FooData1'), ('FooData2'), ('FooData3')
MERGE #Revisions AS [Revisions]
USING (SELECT FooId FROM #Foo) AS [Foo]
ON (1=0)
WHEN NOT MATCHED THEN
INSERT (CreatedTimeStamp) VALUES (CURRENT_TIMESTAMP)
OUTPUT INSERTED.RevId, Foo.FooId INTO #InsertedRows;
SELECT * FROM #Foo
SELECT * FROM #Revisions
SELECT * FROM #InsertedRows
Table results from above query
#Foo table
+-------+----------+
| FooId | MyData |
+-------+----------+
| 1 | FooData1 |
| 2 | FooData2 |
| 3 | FooData3 |
+-------+----------+
#Revisions table
+-------+-------------------------+
| RevId | CreatedTimeStamp |
+-------+-------------------------+
| 100 | 2016-03-31 14:48:39.733 |
| 110 | 2016-03-31 14:48:39.733 |
| 120 | 2016-03-31 14:48:39.733 |
+-------+-------------------------+
#InsertedRows table
+-------+-------+
| RevId | FooId |
+-------+-------+
| 100 | 1 |
| 110 | 2 |
| 120 | 3 |
+-------+-------+

Migrate flat jsonb to hstore

I run postgres 9.4, and want to migrate column in my database table to hstore just to be able to make performance comparison.
My current column is key-value pair in jsonb, w/o nested structure.
Any tips how to approach this problem?
Example data:
create table jsons (id int, val jsonb);
insert into jsons values
(1, '{"age":22}'),
(2, '{"height":182}'),
(3, '{"age":30, "height":177}');
Split json objects to key, value pairs:
select id, (jsonb_each_text(val)).key, (jsonb_each_text(val)).value
from jsons
id | key | value
----+--------+-------
1 | age | 22
2 | height | 182
3 | age | 30
3 | height | 177
(4 rows)
Aggregate the pairs and convert them to hstore:
select id, hstore(array_agg(key), array_agg(value))
from (
select id, (jsonb_each_text(val)).key, (jsonb_each_text(val)).value
from jsons
) sub
group by 1
order by 1
id | hstore
----+------------------------------
1 | "age"=>"22"
2 | "height"=>"182"
3 | "age"=>"30", "height"=>"177"
(3 rows)
The same can be accomplished in a more elegant way using lateral join:
select id, hstore(array_agg(key), array_agg(value))
from jsons
cross join jsonb_each_text(val)
group by 1
order by 1;