Postgres - jsonb : Update key in column with value taken from another table - postgresql

I am using postgres 9.5. I have a profile table, which lists the names:
public.profiles:
id | first_name | last_name
--- --------------- ---------------------
1 Jason Bourne
2 Jhonny Quest
I have an invoices table:
public.invoices:
invoice_id | billing_address | profile_id
------------------ ----------------------------- ---------------------
1 { 2
"address_line1": "445 Mount
Eden Road",
"city":"Mount Eden",
"country": "Auckland"
}
I want to update the billing_address column of the invoices table with the first_name and last_name from the profile table, like :
public.invoices:
invoice_id | billing_address | profile_id
------------------ ----------------------------- ---------------------
1 {
"name" : "Jhonny Quest" 2
"address_line1": "445 Mount
Eden Road",
"city":"Mount Eden",
"country": "Auckland"
}
To do so, I have tried using jsonb_set:
UPDATE invoices AS i SET billing_address = jsonb_set(billing_address,'{name}', SELECT t::jsonb FROM (SELECT CONCAT (p.first_name,p.middle_name, p.last_name) FROM profiles p WHERE p.id = i.profile_id)t )
It throws an error at SELECT. TBH I am not even sure if any of that statement is legal. Looking for any guidance.

Click: demo:db<>fiddle
UPDATE invoices i
SET billing_address = s.new_billing_address
FROM (
SELECT
i.invoice_id,
jsonb_set(
billing_address,
'{name}'::text[],
to_jsonb(concat_ws(' ', first_name, last_name))
) AS new_billing_address
FROM
invoices i
JOIN profiles p ON i.profile_id = p.id
) s
WHERE s.invoice_id = i.invoice_id;
Creating the SELECT with joining the second table; Afterwards you are able to create the new JSON object out of the name parts using to_jsonb() and the concat operator || (or concat_ws(), of course, as mentioned in the comments).

Related

Postgres: Query for list of ids in a mapping table and create If they don't exist

Assume we have the following table whose purpose is to autogenerate a numeric id for distinct (name, location) tuples:
CREATE TABLE mapping
(
id bigserial PRIMARY KEY,
name text NOT NULL,
location text NOT NULL,
);
CREATE UNIQUE INDEX idx_name_loc on mapping(name location)
What is the most efficient way to query for a set of (name, location) tuples and autocreate any mappings that don't already exist, with all mappings (including the ones we created) being returned to the user.
My naive implementation would be something like:
SELECT id, name, location
FROM mappings
WHERE (name, location) IN ((name_1, location_1)...(name_n, location_n))
do something with the results in a programming language of may choice to work out which results are missing.
INSERT
INTO mappings (name, location)
VALUES (missing_name_1, missing_loc_1), ... (missing_name_2, missing_loc_2)
ON CONFLICT DO NOTHING
This gets the job done but I get the feeling there's probably something that can a) be done in pure sql and b) is more efficient.
You can use DISTINCT to get all possible values for the two columns, and CROSS JOIN to get their Carthesian product.
LEFT JOIN with the original table to get the actual records (if any):
CREATE TABLE mapping
( id bigserial PRIMARY KEY
, name text NOT NULL
, location text NOT NULL
, UNIQUE (name, location)
);
INSERT INTO mapping(name, location) VALUES ('Alice', 'kitchen'), ('Bob', 'bedroom' );
SELECT * FROM mapping;
SELECT n.name, l.location, m.id
FROM (SELECT DISTINCT name from mapping) n
CROSS JOIN (SELECT DISTINCT location from mapping) l
LEFT JOIN mapping m ON m.name = n.name AND m.location = l.location
;
Results:
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 2
id | name | location
----+-------+----------
1 | Alice | kitchen
2 | Bob | bedroom
(2 rows)
name | location | id
-------+----------+----
Alice | kitchen | 1
Alice | bedroom |
Bob | kitchen |
Bob | bedroom | 2
(4 rows)
And if you want to physically INSERT the missing combinations:
INSERT INTO mapping(name, location)
SELECT n.name, l.location
FROM (SELECT DISTINCT name from mapping) n
CROSS JOIN (SELECT DISTINCT location from mapping) l
WHERE NOT EXISTS(
SELECT *
FROM mapping m
WHERE m.name = n.name AND m.location = l.location
)
;
SELECT * FROM mapping;
INSERT 0 2
id | name | location
----+-------+----------
1 | Alice | kitchen
2 | Bob | bedroom
3 | Alice | bedroom
4 | Bob | kitchen
(4 rows)

How to get id of the row which was selected by aggregate function? [duplicate]

This question already has answers here:
Select first row in each GROUP BY group?
(20 answers)
Closed 4 years ago.
I have next data:
id | name | amount | datefrom
---------------------------
3 | a | 8 | 2018-01-01
4 | a | 3 | 2018-01-15 10:00
5 | b | 1 | 2018-02-20
I can group result with the next query:
select name, max(amount) from table group by name
But I need the id of selected row too. Thus I have tried:
select max(id), name, max(amount) from table group by name
And as it was expected it returns:
id | name | amount
-----------
4 | a | 8
5 | b | 1
But I need the id to have 3 for the amount of 8:
id | name | amount
-----------
3 | a | 8
5 | b | 1
Is this possible?
PS. This is required for billing task. At some day 2018-01-15 configuration of a was changed and user consumes some resource 10h with the amount of 8 and rests the day 14h -- 3. I need to count such a day by the maximum value. Thus row with id = 4 is just ignored for 2018-01-15 day. (for next day 2018-01-16 I will bill the amount of 3)
So I take for billing the row:
3 | a | 8 | 2018-01-01
And if something is wrong with it. I must report that row with id == 3 is wrong.
But when I used aggregation function the information about id is lost.
Would be awesome if this is possible:
select current(id), name, max(amount) from table group by name
select aggregated_row(id), name, max(amount) from table group by name
Here agg_row refer to the row which was selected by aggregation function max
UPD
I resolve the task as:
SELECT
(
SELECT id FROM t2
WHERE id = ANY ( ARRAY_AGG( tf.id ) ) AND amount = MAX( tf.amount )
) id,
name,
MAX(amount) ma,
SUM( ratio )
FROM t2 tf
GROUP BY name
UPD
It would be much better to use window functions
There are at least 3 ways, see below:
CREATE TEMP TABLE test (
id integer, name text, amount numeric, datefrom timestamptz
);
COPY test FROM STDIN (FORMAT csv);
3,a,8,2018-01-01
4,a,3,2018-01-15 10:00
5,b,1,2018-02-20
6,b,1,2019-01-01
\.
Method 1. using DISTINCT ON (PostgreSQL-specific)
SELECT DISTINCT ON (name)
id, name, amount
FROM test
ORDER BY name, amount DESC, datefrom ASC;
Method 2. using window functions
SELECT id, name, amount FROM (
SELECT *, row_number() OVER (
PARTITION BY name
ORDER BY amount DESC, datefrom ASC) AS __rn
FROM test) AS x
WHERE x.__rn = 1;
Method 3. using corelated subquery
SELECT id, name, amount FROM test
WHERE id = (
SELECT id FROM test AS t2
WHERE t2.name = test.name
ORDER BY amount DESC, datefrom ASC
LIMIT 1
);
demo: db<>fiddle
You need DISTINCT ON which filters the first row per group.
SELECT DISTINCT ON (name)
*
FROM table
ORDER BY name, amount DESC
You need a nested inner join. Try this -
SELECT id, T2.name, T2.amount
FROM TABLE T
INNER JOIN (SELECT name, MAX(amount) amount
FROM TABLE
GROUP BY name) T2
ON T.amount = T2.amount

PostgreSQL - How to display a corresponding string on every entry in string_agg()?

I have 2 tables:
Employee
ID Name
1 John
2 Ben
3 Adam
Employer
ID Name
1 James
2 Rob
3 Paul
I want to string_agg() and concatenate the two tables in one record as a single column. Now I wanted another column than will determine that if that string is from "Employee" table, it will display "Employee" and "Employer" if the data comes from the "Employer" table.
Here's my code for displaying the table:
SELECT string_agg(e.Name, CHR(10)) || CHR(10) || string_agg(er.Name, CHR(10)), PERSON_STATUS
FROM Employee e, Employer er
Here's my expected output:
ID Name PERSON_STATUS
1 John Employee
Ben Employee
Adam Employee
James Employer
Rob Employer
Paul Employer
NOTE: I know this can be done by adding another column in the table but that's not the case of this scenario. This is just an example to illustrate my problem.
Based on your sample, I'd say that you need UNION ALL rather than an aggregate:
SELECT id, name, 'Employee'::text AS person_status
FROM employee
UNION ALL
SELECT id, name, 'Employer'::text
from employer;
SELECT 1 AS id, STRING_AGG(name, E'\r\n') AS name, STRING_AGG(person_status, E'\r\n') AS person_status
FROM (
SELECT name, 'Employee' AS person_status
FROM employee
UNION ALL
SELECT name, 'Employer'
FROM employer
) data
Returns:
Ok, so first we merge our 2 tables into 3 columns. We can select arbitrary values this way.
select
"ID", -- Double quotes are necesary for capitalised aliases
"Name",
'Employee' as "PERSON_STATUS"
from
employee
union
select
"ID",
"Name",
'Employer'
from
employer
We then subquery this and perform our string operations as required.
select
string_agg(concat(people."Name", ' ', people."PERSON_STATUS"), chr(10))
from
(
select
"ID",
"Name",
'Employee' as "PERSON_STATUS"
from
employee
union
select
"ID",
"Name",
'Employer'
from
employer
) as people

Update Count column in Postgresql

I have a single table laid out as such:
id | name | count
1 | John |
2 | Jim |
3 | John |
4 | Tim |
I need to fill out the count column such that the result is the number of times the specific name shows up in the column name.
The result should be:
id | name | count
1 | John | 2
2 | Jim | 1
3 | John | 2
4 | Tim | 1
I can get the count of occurrences of unique names easily using:
SELECT COUNT(name)
FROM table
GROUP BY name
But that doesn't fit into an UPDATE statement due to it returning multiple rows.
I can also get it narrowed down to a single row by doing this:
SELECT COUNT(name)
FROM table
WHERE name = 'John'
GROUP BY name
But that doesn't allow me to fill out the entire column, just the 'John' rows.
you can do that with a common table expression:
with counted as (
select name, count(*) as name_count
from the_table
group by name
)
update the_table
set "count" = c.name_count
from counted c
where c.name = the_table.name;
Another (slower) option would be to use a co-related sub-query:
update the_table
set "count" = (select count(*)
from the_table t2
where t2.name = the_table.name);
But in general it is a bad idea to store values that can easily be calculated on the fly:
select id,
name,
count(*) over (partition by name) as name_count
from the_table;
Another method : Using a derived table
UPDATE tb
SET count = t.count
FROM (
SELECT count(NAME)
,NAME
FROM tb
GROUP BY 2
) t
WHERE t.NAME = tb.NAME

Get column value from another table in PostgreSQL

I have two tables:
User and Order
A Order belongs to a User, in a way that the Order table has a column called user_id. So, for example, a row in Order table would look like:
id: 4
description: "pair of shoes"
price: 18.40
user_id: 1
And a User row would look like:
id: 1
email: "myemail#gmail.com"
name: "John"
How would I go if I want to get all the Orders, but instead of getting the user id, get the email?
A simple JOIN will do the trick
SELECT o.id,
o.description,
o.price,
u.email
FROM "order" o JOIN "user" u
ON o.user_id = u.id
Sample output:
| ID | DESCRIPTION | PRICE | EMAIL |
--------------------------------------------------
| 4 | pair of shoes | 18.4 | myemail#gmail.com |
Here is SQLFiddle demo
Further reading A Visual Explanation of SQL Joins