Postgresql, bulk insert if row with some value not exists - postgresql

I want to do bulk insert only if there is no matching email
I tried this, but I don't know how to add a where clause.
INSERT INTO user (
'pk',
'email'
)
SELECT UNNEST(ARRAY[10, 11, 12, 13, 14, 15, 16]),
UNNEST(ARRAY['example1#example.com', 'example2#example.com', .........])
WHERE something.......

Sample 1:
with tbl(a,b) as (
select
UNNEST(ARRAY[10, 11, 12]),
UNNEST(ARRAY['example3#example.com', 'example4#example.com', 'example5#example.com'])
)
insert into user (pk, email)
select a, b from tbl
left join user on tbl.b = user.email
where user.email is null
Sample 2:
with tbl(a,b) as (
select
UNNEST(ARRAY[10, 11, 12]),
UNNEST(ARRAY['example3#example.com', 'example4#example.com', 'example5#example.com'])
)
insert into user (pk, email)
select a, b from tbl
where not exists (select 1 from user where user.email = tbl.b)

Related

Grouping user id columns together with string_agg on PostgreSQL 13

This is my emails table
create table emails (
id bigint not null primary key generated by default as identity,
name text not null
);
And contacts table:
create table contacts (
id bigint not null primary key generated by default as identity,
email_id bigint not null,
user_id bigint not null,
full_name text not null,
ordering int not null
);
As you can see I have user_id field here. There can be multiple same user ID's on my result so i want to join them using comma ,
Insert some data to the tables:
insert into emails (name)
values
('dennis1'),
('dennis2');
insert into contacts (id, email_id, user_id, full_name, ordering)
values
(5, 1, 1, 'dennis1', 9),
(6, 2, 1, 'dennis1', 5),
(7, 2, 1, 'dennis1', 1),
(8, 1, 3, 'john', 2),
(9, 2, 4, 'dennis7', 1),
(10, 2, 4, 'dennis7', 1);
My query is:
select em.name,
c.user_ids
from emails em
join (
select email_id, string_agg(user_id::text, ',' order by ordering desc) as user_ids
from contacts
group by email_id
) c on c.email_id = em.id
order by em.name;
Actual Result
name user_ids
dennis1 1,3
dennis2 1,1,4,4
Expected Result
name user_ids
dennis1 1,3
dennis2 1,4
On my real-world data, I get same user id like 50 times. Instead it should appear 1 time only. In example above, you see user 1 and 4 appears 2 times for dennis2 user.
How can I unique them?
Demo: https://dbfiddle.uk/?rdbms=postgres_13&fiddle=2e957b52eb46742f3ddea27ec36effb1
P.S: I tried to add user_id it to group by but this time I get duplicate rows...
demo:db<>fiddle
SELECT
name,
string_agg(user_id::text, ',' order by ordering desc)
FROM (
SELECT DISTINCT ON (em.id, c.user_id)
*
FROM emails em
JOIN contacts c ON c.email_id = em.id
) s
GROUP BY name
Join the tables
DISTINCT ON email and the user_id, so for every email record, there is no equal users
Aggregate

Using A couple of Temp variables in PostgreSQL

Trying to get this working. I realize that select into creates a table, not a variable so I'm not sure how to do this:
select state.state_id into stateId from state where state.name = 'Illinois'
INSERT INTO public.city(
name,
state_id)
VALUES ('Chicago', stateId);
select public.city.city_id into chicago from public.city where public.city.name = 'Chicago'
INSERT INTO public.location (
city_id,
state_id,
country_id,
name)
VALUES (
chicago,
9,
185,
'Chicago, IL, USA'
);
No need for variables here, you can query as you're inserting. Just be sure that your query returns only the one row you want.
INSERT INTO public.city (name, state_id)
SELECT 'Chicago', state.state_id
FROM state
WHERE state.name = 'Illinois';
INSERT INTO public.location (city_id, state_id, country_id, name)
SELECT public.city.city_id, 9, 185, 'Chicago, IL, USA'
FROM public.city
WHERE public.city.name = 'Chicago';

Populatie IN list with a subquery

Preamble:
I'm working in an environment (Knowage) that let me only do SELECTs.
$P{something} is the call of a parameter replaced by knowage when running the query on the MSSQL engine
Whay I have to do:
I have to use a parameter to 'filter' the data with an entry that let take out all, currently I can only get filtered data, i cannot take out 'all'
My query looks like this:
select ClusterDRG, t.N as N_cur, u.N as N_pas, t.SubStabilimento
from(
--- prendo i primi 10 DRG del periodo in corso confrontando i valori degli stessi DRG del periodo precedente (indipendentemente dal fatto che fossero o meno tra i primi 10)
SELECT count(ProgrSdo) as N
,ClusterDRG, TipologiaDRG as tipodrg
, ROW_NUMBER() OVER(PARTITION BY anno,tipologiaDRG ORDER BY count(progrsdo) desC) as Ordine
, case when [Anno]=$P{lista_anni} then 'Attuale'
when [Anno]=$P{lista_anni}-1 then 'Precedente'
else cast([Anno] as varchar (4)) end as Anno
FROM
MY_TABLE
where left(Mese,2) <=$P{lista_mesi}
and ANNO = $P{lista_anni} -- l'anno corrente
and SubStabilimento = $P{lista_stabilimenti} -- here is my issue
and codicepresidio=111111
group by ClusterDRG, TipologiaDRG, anno, SubStabilimento
) t
left join
(
SELECT count(ProgrSdo) as N
,ClusterDRG, TipologiaDRG as tipodrg
, case when [Anno]=$P{lista_anni} then 'Attuale'
when [Anno]=$P{lista_anni}-1 then 'Precedente'
else cast([Anno] as varchar (4)) end as Anno
, ROW_NUMBER() OVER(PARTITION BY anno,tipologiaDRG ORDER BY count(progrsdo) desC) as Ordine
FROM MY_TABLE
where left(Mese,2) <= $P{lista_mesi}
and ANNO = $P{lista_anni}-1 -- l'anno passato
and codicepresidio=111111
and SubStabilimento = $P{lista_stabilimenti} -- here is my issue
group by ClusterDRG, TipologiaDRG, anno, SubStabilimento
)u on t.ClusterDRG=u.ClusterDRG and t.TipoDRG=u.TipoDRG and t.SubStabilimento=u.SubStabilimento
my parameters got these values:
$P{lista_mesi} := 5
$P{lista_anni} := 2018
$P{lista_stabilimenti} := **here is my issue**
I want to use $P{lista_stabilimenti} to filter by single entity (and at doing that now it works) or all if a specific value is set.
so If I have $P{lista_stabilimenti} := 'stab1'
I get
clust1, 123, 122, stab1
clust2, 789, 456, stab1
and If I have $P{lista_stabilimenti} := 'ALL' (This is the behaviour I'm trying to make)
I get
clust1, 123, 122, stab1
clust2, 789, 456, stab1
clust1, 321, 221, stab2
clust2, 987, 654, stab2
clust5, 963, 258, stab3
I tryed populating the and SubStabilimento = $P{lista_stabilimenti}
as
[...]
and SubStabilimento in ( case when exists ( select SubStabilimento from Knowage_L15_COLLEGATA where SubStabilimento = $P{lista_stabilimenti}) then $P{lista_stabilimenti}
when $P{lista_stabilimenti} like 'TUTTI' then (
select STUFF(
(
SELECT ',' + SubStabilimento
FROM MY_TABLE v
where CodicePresidio = '111111'
group by SubStabilimento
FOR XML PATH('')
),
1, 1, '')
)
end)
[...]
With this I get a string like stab1,stab2,stab3 but I'm not able to feed it to the IN clause
A pizza made my brain to grind again, my solution is like this:
[...]
and SubStabilimento LIKE ( select case when exists (
select SubStabilimento
from MY_TABLE
where SubStabilimento = $P{lista_stabilimenti})
then $P{lista_stabilimenti}
when $P{lista_stabilimenti} like 'TUTTI'
then '%' end as SubStab )
[...]

Postgres very hard dynamic select statement with COALESCE

Having a table and data like this
CREATE TABLE solicitations
(
id SERIAL PRIMARY KEY,
name text
);
CREATE TABLE donations
(
id SERIAL PRIMARY KEY,
solicitation_id integer REFERENCES solicitations, -- can be null
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
amount bigint NOT NULL DEFAULT 0
);
INSERT INTO solicitations (name) VALUES
('solicitation1'), ('solicitation2');
INSERT INTO donations (created_at, solicitation_id, amount) VALUES
('2018-06-26', null, 10), ('2018-06-26', 1, 20), ('2018-06-26', 2, 30),
('2018-06-27', null, 10), ('2018-06-27', 1, 20),
('2018-06-28', null, 10), ('2018-06-28', 1, 20), ('2018-06-28', 2, 30);
How to make solicitation id's dynamic in following select statement using only postgres???
SELECT
"created_at"
-- make dynamic this begins
, COALESCE("no_solicitation", 0) AS "no_solicitation"
, COALESCE("1", 0) AS "1"
, COALESCE("2", 0) AS "2"
-- make dynamic this ends
FROM crosstab(
$source_sql$
SELECT
created_at::date as row_id
, COALESCE(solicitation_id::text, 'no_solicitation') as category
, SUM(amount) as value
FROM donations
GROUP BY row_id, category
ORDER BY row_id, category
$source_sql$
, $category_sql$
-- parametrize with ids from here begins
SELECT unnest('{no_solicitation}'::text[] || ARRAY(SELECT DISTINCT id::text FROM solicitations ORDER BY id))
-- parametrize with ids from here ends
$category_sql$
) AS ct (
"created_at" date
-- make dynamic this begins
, "no_solicitation" bigint
, "1" bigint
, "2" bigint
-- make dynamic this ends
)
The select should return data like this
created_at no_solicitation 1 2
____________________________________
2018-06-26 10 20 30
2018-06-27 10 20 0
2018-06-28 10 20 30
The solicitation ids that should parametrize select are the same as in
SELECT unnest('{no_solicitation}'::text[] || ARRAY(SELECT DISTINCT id::text FROM solicitations ORDER BY id))
One can fiddle the code here
I decided to use json, which is much simpler then crosstab
WITH
all_solicitation_ids AS (
SELECT
unnest('{no_solicitation}'::text[] ||
ARRAY(SELECT DISTINCT id::text FROM solicitations ORDER BY id))
AS col
)
, all_days AS (
SELECT
-- TODO: compute days ad hoc, from min created_at day of donations to max created_at day of donations
generate_series('2018-06-26', '2018-06-28', '1 day'::interval)::date
AS col
)
, all_days_and_all_solicitation_ids AS (
SELECT
all_days.col AS created_at
, all_solicitation_ids.col AS solicitation_id
FROM all_days, all_solicitation_ids
ORDER BY all_days.col, all_solicitation_ids.col
)
, donations_ AS (
SELECT
created_at::date as created_at
, COALESCE(solicitation_id::text, 'no_solicitation') as solicitation_id
, SUM(amount) as amount
FROM donations
GROUP BY created_at, solicitation_id
ORDER BY created_at, solicitation_id
)
, donations__ AS (
SELECT
all_days_and_all_solicitation_ids.created_at
, all_days_and_all_solicitation_ids.solicitation_id
, COALESCE(donations_.amount, 0) AS amount
FROM all_days_and_all_solicitation_ids
LEFT JOIN donations_
ON all_days_and_all_solicitation_ids.created_at = donations_.created_at
AND all_days_and_all_solicitation_ids.solicitation_id = donations_.solicitation_id
)
SELECT
jsonb_object_agg(solicitation_id, amount) ||
jsonb_object_agg('date', created_at)
AS data
FROM donations__
GROUP BY created_at
which results
data
______________________________________________________________
{"1": 20, "2": 30, "date": "2018-06-28", "no_solicitation": 10}
{"1": 20, "2": 30, "date": "2018-06-26", "no_solicitation": 10}
{"1": 20, "2": 0, "date": "2018-06-27", "no_solicitation": 10}
Thought its not the same that I requested.
It returns only data column, instead of date, no_solicitation, 1, 2, ...., to do so I need to use json_to_record, but I dont know how to produce its as argument dynamically

How can I parse query result to string in postgres?

I prepared sql fiddle: http://sqlfiddle.com/#!15/62e65/2
And schema is here:
CREATE TABLE products
("id" int, "name" varchar(5))
;
INSERT INTO products
("id", "name")
VALUES
(1, 'Car'),
(2, 'Phone')
;
CREATE TABLE operations
("id" int, "product_id" int, "status" varchar(7), "type" varchar(8))
;
INSERT INTO operations
("id", "product_id", "status", "type")
VALUES
(1, 1, 'pending', 'invoice'),
(2, 1, 'done', 'delivery'),
(3, 2, 'done', 'delivery'),
(3, 2, 'done', 'invoice')
;
I know that the data schema could be better, but I don't have possibility to refactor it now - I am just adding new view.
Note about schema: product has always 2 operations: invoicing and delivery.
What I want to achieve is to get such result:
name status
car pending
phone done
Where product status is a string returned after checking both product operations.
Rule is that product status is done only when both operations are done, otherwise its pending.
How to write such query in postgres?
SELECT p.name, CASE WHEN status='done' THEN 'done' else 'pending' END
FROM (
SELECT p.id, p.name, string_agg(distinct o.status, '') as status
FROM products p JOIN operations o ON o.product_id = p.id
GROUP BY p.id, p.name
) sub
Fisrtly we concatenate distinct values in one string (string_agg) and then in master query we check: if ne string is single 'done' that meand that all operations were 'done'. Otherwise one or both operation are 'pending'.
Since your foreign key is products.id <--> operations.product_id we must GROUP BY products.id.
You could create two subqueries, one for orders and one for deliveries, join them, and then use a case expression to check the status on both:
SELECT invoice.name,
CASE WHEN invoice.status = 'done' and delivery.status = 'done' THEN 'done'
ELSE 'pending'
END AS status
FROM (SELECT p.name, p.id, o.status
FROM products p
JOIN operations o ON o.type = 'invoice' AND
o.product_id = p.id) invoice
JOIN (SELECT p.name, p.id, o.status
FROM products p
JOIN operations o ON o.type = 'delivery' AND
o.product_id = p.id) delivery
ON invoice.id = delivery.id
select
p.name,
case
when count(case when status <> 'done' then 1 else null end) = 0 then 'done'
else 'pending'
end status
from products p
inner join operations o on p.id = o.product_id
group by p.name, p.id;