query a jsonb array in a select of another query - postgresql

I have a user table with a column favorites that is a jsonb
favorites:
[
{
"id_doc:": 9,
"type": "post"
},
{
"id_doc": 10,
"type": "post"
}
]
And I have another table posts where I want to make a query by id and this id must be in the fields id_doc in the favorites user
select * from posts where id in (select favorites -> id_doc from users )
This is the schema
CREATE TABLE dev.users
(
id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
firstname character varying COLLATE pg_catalog."default" NOT NULL,
lastname character varying COLLATE pg_catalog."default" NOT NULL,
email character varying COLLATE pg_catalog."default" NOT NULL,
password character varying COLLATE pg_catalog."default" NOT NULL,
favorites jsonb[],
CONSTRAINT users_pkey PRIMARY KEY (id),
CONSTRAINT email_key UNIQUE (email)
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE dev.users
OWNER to postgres;
CREATE TABLE dev.posts
(
id integer NOT NULL DEFAULT nextval('dev.posts_id_seq'::regclass),
title character varying COLLATE pg_catalog."default" NOT NULL,
userid integer NOT NULL,
description character varying COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT posts_pkey PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE dev.posts
OWNER to postgres;
How can I do this?
Thank you

There are other ways to accomplish this, but I prefer using CTEs for clarity. Please let me know in the comments if you have questions about what this does.
with elements as (
select jsonb_array_elements(favorites) as favitem
from users
), fav_ids as (
select distinct (favitem->>'id_doc')::int as id_doc
from elements
)
select p.*
from posts p
join fav_ids f on f.id_doc = p.id
;
Update
Since the column is defined as jsonb[] rather than json, we need to unnest() instead of jsonb_array_elements():
with elements as (
select unnest(favorites) as favitem
from users
), fav_ids as (
select distinct (favitem->>'id_doc')::int as id_doc
from elements
)
select p.*
from posts p
join fav_ids f on f.id_doc = p.id
;

Related

pg_restore into a different table structure?

I have two different databases containing a table called feed_items
Lets call them source and destination.
I want to copy some 250k rows from the source feed_items table to the destination feed_items table
Structure of source feed_items table
CREATE TABLE IF NOT EXISTS public.feed_items
(
feed_item_id uuid NOT NULL,
pubdate timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
link character varying COLLATE pg_catalog."default" NOT NULL,
guid character varying COLLATE pg_catalog."default",
title text COLLATE pg_catalog."default" NOT NULL,
summary text COLLATE pg_catalog."default",
content text COLLATE pg_catalog."default",
author character varying(63) COLLATE pg_catalog."default",
feed_id integer NOT NULL,
tags character varying(127)[] COLLATE pg_catalog."default" DEFAULT '{}'::character varying[],
title_vector tsvector,
summary_vector tsvector,
CONSTRAINT feed_items_pkey PRIMARY KEY (feed_item_id),
CONSTRAINT fkey_feed_item_feed FOREIGN KEY (feed_id)
REFERENCES public.feeds (feed_id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.feed_items
OWNER to ch_v3_root;
CREATE INDEX IF NOT EXISTS idx_feed_items_pubdate_feed_item_id_desc
ON public.feed_items USING btree
(pubdate DESC NULLS FIRST, feed_item_id DESC NULLS FIRST)
TABLESPACE pg_default;
CREATE INDEX IF NOT EXISTS idx_summary_vector
ON public.feed_items USING gin
(summary_vector)
TABLESPACE pg_default;
CREATE INDEX IF NOT EXISTS idx_tags_array
ON public.feed_items USING gin
(tags COLLATE pg_catalog."default")
TABLESPACE pg_default;
CREATE INDEX IF NOT EXISTS idx_title_vector
ON public.feed_items USING gin
(title_vector)
TABLESPACE pg_default;
CREATE TRIGGER on_insert_feed_items
AFTER INSERT
ON public.feed_items
FOR EACH ROW
EXECUTE FUNCTION public.notify_change_feed_items();
CREATE TRIGGER on_update_feed_items
AFTER UPDATE
ON public.feed_items
FOR EACH ROW
WHEN (new.title <> old.title OR new.summary <> old.summary OR new.content <> old.content)
EXECUTE FUNCTION public.notify_change_feed_items();
CREATE TRIGGER trigger_update_summary_vector
BEFORE INSERT OR UPDATE
ON public.feed_items
FOR EACH ROW
EXECUTE FUNCTION tsvector_update_trigger('summary_vector', 'pg_catalog.english', 'summary');
CREATE TRIGGER trigger_update_title_vector
BEFORE INSERT OR UPDATE
ON public.feed_items
FOR EACH ROW
EXECUTE FUNCTION tsvector_update_trigger('title_vector', 'pg_catalog.english', 'title');
Structure of the destination feed_items table
CREATE TABLE IF NOT EXISTS public.feed_items
(
id uuid NOT NULL,
author character varying(255) COLLATE pg_catalog."default",
content text COLLATE pg_catalog."default",
guid character varying(2047) COLLATE pg_catalog."default",
link character varying(2047) COLLATE pg_catalog."default" NOT NULL,
pubdate timestamp with time zone NOT NULL DEFAULT now(),
searchable tsvector GENERATED ALWAYS AS (((setweight(to_tsvector('english'::regconfig, COALESCE(title, (''::character varying)::text)), 'A'::"char") || setweight(to_tsvector('english'::regconfig, COALESCE(summary, ''::text)), 'B'::"char")) || setweight(to_tsvector('english'::regconfig, COALESCE(content, (''::character varying)::text)), 'C'::"char"))) STORED,
summary text COLLATE pg_catalog."default",
tags character varying(255)[] COLLATE pg_catalog."default" NOT NULL DEFAULT (ARRAY[]::character varying[])::character varying(255)[],
title text COLLATE pg_catalog."default" NOT NULL,
feed integer NOT NULL,
CONSTRAINT feed_items_pkey PRIMARY KEY (id),
CONSTRAINT feed_items_link_key UNIQUE (link),
CONSTRAINT feed_items_feed_fkey FOREIGN KEY (feed)
REFERENCES public.feeds (id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.feed_items
OWNER to ch_api_user;
CREATE INDEX IF NOT EXISTS feed_items_pubdate_id
ON public.feed_items USING btree
(pubdate DESC NULLS FIRST, id DESC NULLS FIRST)
TABLESPACE pg_default;
CREATE INDEX IF NOT EXISTS feed_items_searchable
ON public.feed_items USING gin
(searchable)
TABLESPACE pg_default;
CREATE INDEX IF NOT EXISTS feed_items_tags
ON public.feed_items USING gin
(tags COLLATE pg_catalog."default")
TABLESPACE pg_default;
The names of a few columns have changed, the order of columns has changed and the table is assigned to a different database user. How do I pg_restore from source to destination?

TypeORM: many to many: Get all teams from userID

I have two table: User and Team.
They are in a many-to-many relation.
I would like to get all teams of a specific user.
Seams really simple but I can't find the answer anywhere..
Do you know how can I do ?
On the many-to-many relation must be 3rd table. This table contained user_id and team_id connections.
For example:
CREATE TABLE user (
id serial4 NOT NULL,
first_name varchar(200) NOT NULL,
last_name varchar(200) NULL,
CONSTRAINT user_pk PRIMARY KEY (id)
);
CREATE TABLE team (
id serial4 NOT NULL,
team_name varchar NOT NULL,
team_about text NULL,
CONSTRAINT team_pk PRIMARY KEY (id)
);
CREATE TABLE user_team (
id serial4 NOT NULL,
user_id int4 NOT NULL,
team_id int4 NOT NULL,
CONSTRAINT user_team_pk PRIMARY KEY (id)
);
-- filter and select team by user_id
select t.* from examples.team t
inner join examples.user_team usrt on usrt.team_id = t.id
where usrt.user_id = 2;

postgresql: constaint to have unique values across 2 tables

I have 2 tables:
CREATE TABLE public."user"
(
id integer NOT NULL DEFAULT nextval('user_id_seq'::regclass),
username character varying(256) COLLATE pg_catalog."default",
CONSTRAINT user_pkey PRIMARY KEY (id)
)
CREATE TABLE public.user_tenant
(
user_id integer NOT NULL,
tenant_id integer NOT NULL,
CONSTRAINT user_fk FOREIGN KEY (user_id)
REFERENCES public."user" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
)
I need to have unique values (user.username, user_tenant.tenant_id). How can I declare such a constraint?
I would make the username unique, just like the tenant that is in another table. When that is done, you can put a primary key on the user_id and tenant_id:
CREATE TABLE public."user"
(
id integer NOT NULL DEFAULT nextval('user_id_seq'::regclass),
username character varying(256) COLLATE pg_catalog."default" unique,
CONSTRAINT user_pkey PRIMARY KEY (id)
);
CREATE TABLE public.user_tenant
(
user_id integer NOT NULL,
tenant_id integer NOT NULL,
CONSTRAINT user_fk FOREIGN KEY (user_id)
REFERENCES public."user" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT user_tenant_pk PRIMARY KEY (user_id, tenant_id)
);
By the way, don't use reserved names like "user" for table names.
You can create a function which can check for uniqueness across multiple tables (example here: Postgres unique combination constraint across tables) but it looks like you may need to the structure of your tables or follow Frank Heikens' answer.
EDIT:
CREATE TABLE public."user"
(
id SERIAL,
username character varying(256) COLLATE pg_catalog."default",
PRIMARY KEY (id)
);
CREATE TABLE public.user_tenant
(
user_id integer NOT NULL,
tenant_id integer NOT NULL,
CONSTRAINT user_fk FOREIGN KEY (user_id)
REFERENCES public."user" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
CREATE OR REPLACE FUNCTION public.check_user_tenant(user_id integer, tenant_id integer)
RETURNS boolean AS
$$
DECLARE b_result boolean;
BEGIN
SELECT (COUNT(*) = 0) INTO b_result
FROM public.user u
JOIN public.user_tenant ut ON ut.user_id IN (SELECT id
FROM public.user i_u
WHERE i_u.username = u.username)
WHERE u.id = $1 AND ut.tenant_id = $2;
RETURN b_result;
END
$$ LANGUAGE 'plpgsql';
ALTER TABLE public.user_tenant
ADD CONSTRAINT check_filename CHECK
(public.check_user_tenant(user_id, tenant_id));
-- Testing:
insert into public."user" (username) VALUES ('foo');
insert into public.user_tenant (user_id, tenant_id) VALUES (1,3);
insert into public."user" (username) VALUES ('foo');
-- Violates constraint:
insert into public.user_tenant (user_id, tenant_id) VALUES (2,3);

PostgreSQL - How to merge data from multiple rows using join from other tables

I am using postgresql 9.6 and want to merge data from multiple rows into one. I'm using 4 tables(the table format and data cannot be changed)
Tables are-
"id" int4 DEFAULT nextval('product_a_seq'::regclass) NOT NULL,
"name" varchar(255) COLLATE "default",
"organization_id" int4,
"description" text COLLATE "default",
"image" varchar(255) COLLATE "default" DEFAULT 'https://abcd.com'::character varying,
"deleted" bool,
"price" float4,
"currency_id" int4,
CONSTRAINT "product_pkey" PRIMARY KEY ("id")
)
CREATE TABLE "public"."product_asset" (
"id" int4 DEFAULT nextval('product_asset_a_seq'::regclass) NOT NULL,
"product_id" int4,
"asset_type" varchar(255) COLLATE "default",
"asset_url" text COLLATE "default",
"asset_value" text COLLATE "default",
"asset_name" text COLLATE "default",
CONSTRAINT "product_asset_pkey" PRIMARY KEY ("id")
)
CREATE TABLE "public"."user" (
"id" int4 DEFAULT nextval('istar_user_a_seq'::regclass) NOT NULL,
"mobile" varchar COLLATE "default",
"name" varchar(255) COLLATE "default",
"organization_id" int4,
CONSTRAINT "istar_user_pkey" PRIMARY KEY ("id")
)
CREATE TABLE "public"."pipeline_product" (
"pipeline_id" int4 NOT NULL,
"product_id" int4 NOT NULL,
CONSTRAINT "uq_pipeline_product_pipeline_id_product_id" UNIQUE ("pipeline_id", "product_id")
)
For each product there can be multiple assets and pipelines
I want all data grouped according to product.id, here's my initial attempt
SELECT
product. ID AS pid,
product. NAME AS pname,
product.price,
product.currency_id,
product.description,
product_asset.asset_type,
product_asset.asset_url,
product_asset.asset_name,
product_asset.asset_value,
product_asset. ID AS asset_id,
String_agg ( pipeline_product.pipeline_id :: TEXT, ',' ) AS process_id
FROM
product
LEFT JOIN product_asset ON product_asset.product_id = product. ID
AND product_asset.is_active = TRUE
LEFT JOIN pipeline_product ON pipeline_product.product_id = product. ID
WHERE
organization_id IN (
SELECT
"user" .organization_id
FROM
"user"
WHERE
"user" . ID = 218915
)
AND deleted = FALSE
GROUP BY
pid,
product_asset. ID,
product_asset.asset_type,
product_asset.asset_name,
product_asset.asset_url,
product_asset.asset_value
and i get output as:
I need only one entry for the product.id and everything else should be in a single column,
I am using string_agg() function. What am I missing?
I see three issues in your query:
product_asset.asset_url is not unique, use max(product_asset.asset_url)
product_asset.ID is not unique, so you cannot group it. Use min(product_asset.ID) or max(product_asset. ID)
String_agg(pipeline_product.pipeline_id :: TEXT, ',') does not return always the same value, because they are not sorted, You should add ORDER BY pipeline_product.product_id, pipeline_product.pipeline_id.

How to create index on table which is partitioned?

How to create an index on a partitioned table in PostgreSQL 11.2?
My table is:
CREATE TABLE sometablename
(
column1 character varying(255) COLLATE pg_catalog."default" NOT NULL,
column2 integer NOT NULL,
column3 character varying(255) COLLATE pg_catalog."default" NOT NULL,
"timestamp" timestamp without time zone NOT NULL,
avg_val double precision,
max_val double precision,
min_val double precision,
p95_val double precision,
sample_count double precision,
sum_val double precision,
unit character varying(255) COLLATE pg_catalog."default",
user_id bigint NOT NULL,
CONSTRAINT testtable_pkey PRIMARY KEY (column1, column2, column3, "timestamp", user_id)
)
PARTITION BY HASH (user_id)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
CREATE UNIQUE INDEX testtable_unique_pkey
ON sometablename USING btree (column1 COLLATE pg_catalog."default", column2
COLLATE pg_catalog."default", "timestamp", user_id)
TABLESPACE pg_default;
As you can see testtable_unique_pkey is my index.
but when I run:
SELECT tablename, indexname, indexdef
FROM pg_indexes
WHERE tablename = 'sometablename'
I can't see my index.
I checked the explain analysis on my queries which is also not using the index.
The index for the base table is never really created, so it doesn't show up in pg_indexes:
CREATE TABLE base_table
(
column1 varchar(255) NOT NULL,
column2 integer NOT NULL,
user_id bigint NOT NULL
)
PARTITION BY HASH (user_id);
CREATE UNIQUE INDEX idx_one ON base_table (column1, column2, user_id);
So the following returns nothing:
select *
from pg_indexes
where tablename = 'base_table';
It is however stored in pg_class:
select i.relname as indexname, t.relname as tablename
from pg_class i
join pg_index idx on idx.indexrelid = i.oid
join pg_class t on t.oid = idx.indrelid
where i.relkind = 'I'
and t.relname = 'base_table';
returns:
indexname | tablename
----------+-----------
idx_one | base_table
But for each partition the index will show up in pg_indexes:
create table st_p1 partition of base_table for values with (modulus 4, remainder 0);
create table st_p2 partition of base_table for values with (modulus 4, remainder 1);
create table st_p3 partition of base_table for values with (modulus 4, remainder 2);
create table st_p4 partition of base_table for values with (modulus 4, remainder 3);
And then:
select tablename, indexname
from pg_indexes
where tablename in ('st_p1', 'st_p2', 'st_p3', 'st_p4');
returns:
tablename | indexname
----------+----------------------------------
st_p1 | st_p1_column1_column2_user_id_idx
st_p2 | st_p2_column1_column2_user_id_idx
st_p3 | st_p3_column1_column2_user_id_idx
st_p4 | st_p4_column1_column2_user_id_idx
Update 2020-06-26:
The fact that the index did not show up in pg_indexes was acknowledged as a bug by the Postgres team and was fixed in Postgres 12.
So the above explanation is only valid for Postgres 10 and 11. Starting with Postgres 12, the index on base_table will be shown in `pg_indexes.