get rows if all foreign rows are inactive - sql

I have the following two tables. An "Account" can can many "Ads"
An account can have many ads.
I want to come up with a query that returns: any account that does not have any ads with the column active set to true.
If the account does not have any ads, then the query should not return it. And if only one of the ads related to that account has an ad with the column active set to true, then that should not be returned as well.
CREATE TABLE public.account
(
id bigint NOT NULL DEFAULT nextval('account_id_seq'::regclass),
password text COLLATE pg_catalog."default",
insert_time timestamp with time zone DEFAULT now(),
email_account_id bigint,
phone_number text COLLATE pg_catalog."default",
active boolean NOT NULL DEFAULT true,
CONSTRAINT account_pkey PRIMARY KEY (id),
CONSTRAINT account_email_account_id_fkey FOREIGN KEY (email_account_id)
REFERENCES public.email_account (id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE
)
CREATE TABLE public.ad
(
id bigint NOT NULL DEFAULT nextval('model_id_seq'::regclass),
name text COLLATE pg_catalog."default" NOT NULL,
insert_time timestamp with time zone DEFAULT now(),
ad_url text COLLATE pg_catalog."default",
account_id bigint NOT NULL,
active boolean NOT NULL DEFAULT true,
CONSTRAINT ad_pkey PRIMARY KEY (id),
CONSTRAINT ad_account_id_fkey FOREIGN KEY (account_id)
REFERENCES public.account (id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE
)

Below query will give result for only those account who does not have any ads with the column active set to true
SELECT * FROM public.account a
LEFT JOIN public.ad ad ON (a.id = ad.account_id)
WHERE a.active = true AND ad.account_id IS NULL;

using not exists():
select *
from public.account a
where not exists (
select 1
from public.ad d
where a.id = d.account_id
and d.active
)

select a.id
from public.account a
join public.ad ad on ad.account_id = a.id
group by a.id
having bool_or(ad.active) = false
If any related ad has active = true - bool_or(ad.active) will return true and the having condition will fail. So you will get accounts that have at least one related ad, and no of those ads is active.
Add more columns in the SELECT and GROUP BY clauses if needed.

Related

Postgresql join with Condition

I have three table as follows.
CREATE TABLE users (
id uuid NOT NULL PRIMARY KEY,
password character varying(128) NOT NULL,
username character varying(15) NOT NULL,
email character varying(100) NULL,
gender character varying(1) NOT NULL
);
CREATE TABLE followers (
id bigserial NOT NULL PRIMARY KEY,
followed_at timestamp with time zone NOT NULL,
follower_id uuid REFERENCES users(id),
following_id uuid REFERENCES users(id)
);
CREATE TABLE profile_picture (
id uuid NOT NULL PRIMARY KEY ,
profile_pic character varying(100) NOT NULL,
owner_id uuid NOT NULL REFERENCES users(id),
is_active boolean NOT NULL
);
I want the query for selecting all the follower with the fields : id, username and active_profile_pic.
is_active will be true only for one profile pic of the user but he can upload as many profile photo as he want.
I have tried the without profile_pic, which is not the wanted result.
select users.id , username from users inner join followers on follower_id = users.id where followers.following_id = user_id;
I want the query for selecting all the followers with id, username and active_profile_pic who are following the user with given user_id.
User may have no profile pic and can have more than one so only the active_profile pic should be returned.
Query will have to contain the follower with no profile picture too.
Tried this, but it is not returning follower which have no profile picture. I want to return that too.
SELECT u.id, username, p.profile_pic FROM followers INNER JOIN users AS u ON u.id = follower_id
LEFT OUTER JOIN profile_picture AS p ON u.id = p.owner_id
where followers.following_id = '' and p.is_active = true
In the above query user_id signifies variable , you can specify id of the user there.
Please visit DBFIDDLE
Please suggest me the right query.
You have profile_picture.is_active test in WHERE condition, therefore your result will display only users with profile picture set. If you want all users, regardless of the profile picture, you should include profile_picture.is_active column in query results and delete the condition p.is_active = true from WHERE statement.
With the help of #random_user,
This works for me.
select id , username, profile_pic from (SELECT u.id, username, is_active, profile_pic FROM users as u Full Outer JOIN followers ON u.id = follower_id
Full Outer JOIN profile_picture AS p ON u.id = p.owner_id
where followers.following_id = '2582f93d-68c3-48e2-98ba-a401402c7b62') as profile_table where (is_active = true) or (is_active is null)
May be someone can suggest, better answer, but this works for me.
Check it at DBFIDDLE

Complicated Join Query, Join 3 tables with multiple group bys

I have 3 tables:
Tweets:
CREATE TABLE tweets (
text_content VARCHAR(280) not null,
username VARCHAR(50) not null,
timestamp TIMESTAMP not null DEFAULT current_timestamp,
id UUID not null DEFAULT uuid_generate_v4(),
CONSTRAINT tweets_pk PRIMARY KEY (id)
);
Likes:
CREATE TABLE likes (
username VARCHAR(50) not null,
timestamp TIMESTAMP not null default current_timestamp,
post_id UUID not null,
CONSTRAINT likes_pk PRIMARY KEY (username, post_id),
CONSTRAINT likes_post_id_fk FOREIGN KEY (post_id) REFERENCES tweets(id)
);
And Retweets
CREATE TABLE retweets (
username VARCHAR(50) not null,
timestamp TIMESTAMP not null default current_timestamp,
post_id UUID not null,
CONSTRAINT retweets_pk PRIMARY KEY (username, post_id),
CONSTRAINT retweets_post_id_fk FOREIGN KEY (post_id) REFERENCES tweets(id)
);
I need a query, that would select all tweets, along with the amount of likes and retweets they have.
I did manage to write a working query, but I think I over-complicated it, and would love to hear simpler solutions!
You want to aggregate before joining. Assuming the join key is post_id:
select t.*, l.likes, r.retweets
from tweets t left join
(select post_id, count(*) as likes
from likes
group by post_id
) l
on l.post_id = t.id left join
(select post_id, count(*) as retweets
from retweets
group by post_id
) r
on r.post_id = t.id;

Role basesd access control recursive SQL query

I made the following role based access database schema which is able to hold roles, operations and types. A role can perform a specific operation on a type. Connections to users or types is not important here because this will be application specific. Every of these three tables can have as many parents as they want.
At the moment I'm struggling with a query which outputs every possible combination from the role_operation_type table.
Every role should inherit every permission on a types from the ancestors which can be more than one. In my opinion I need three nested recursive with queries for that or is there any faster way to achieve that?
My intention is to put that query in a view and select the needed values when a user requests an operation on a type.
Here is the database schema:
CREATE TABLE IF NOT EXISTS `role` (
`id` INTEGER PRIMARY KEY,
`name` VARCHAR NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS `role_role` (
`role_id` INTEGER NOT NULL REFERENCES `role`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
`parent_role_id` INTEGER NOT NULL REFERENCES `role`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT role_uq UNIQUE (`role_id`, `parent_role_id`),
CONSTRAINT role_chk CHECK(`role_id` != `parent_role_id`)
);
CREATE TABLE IF NOT EXISTS `operation` (
`id` INTEGER PRIMARY KEY,
`name` VARCHAR NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS `operation_operation` (
`operation_id` INTEGER NOT NULL REFERENCES `operation`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
`parent_operation_id` INTEGER NOT NULL REFERENCES `operation`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT operation_uq UNIQUE (`operation_id`, `parent_operation_id`),
CONSTRAINT operation_chk CHECK(`operation_id` != `parent_operation_id`)
);
CREATE TABLE IF NOT EXISTS `type` (
`id` INTEGER PRIMARY KEY,
`name` VARCHAR NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS `type_type` (
`type_id` INTEGER NOT NULL REFERENCES `type`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
`parent_type_id` INTEGER NOT NULL REFERENCES `type`(`id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT type_uq UNIQUE (`type_id`, `parent_type_id`),
CONSTRAINT type_chk CHECK(`type_id` != `parent_type_id`)
);
CREATE TABLE IF NOT EXISTS `role_operation_type` (
`role_id` INTEGER NOT NULL REFERENCES `role`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`operation_id` INTEGER NOT NULL REFERENCES `operation`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`type_id` INTEGER NOT NULL REFERENCES `type`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT role_id_operation_id_type_id_uq UNIQUE (`role_id`, `operation_id`, `type_id`)
);
CREATE VIEW IF NOT EXISTS role_role_recursive_view AS
WITH RECURSIVE p(role_id, r, parent_role_id) AS (
SELECT ROLE.id, ROLE.id, role_role.parent_role_id
FROM ROLE
INNER JOIN role_role ON ROLE.id = role_role.role_id
UNION
SELECT p.r, p.role_id, role_role.parent_role_id
FROM p
INNER JOIN role_role ON p.parent_role_id = role_role.role_id
WHERE p.r != role_role.parent_role_id
)
SELECT p.role_id, p.parent_role_id FROM p ORDER BY role_id;
CREATE VIEW IF NOT EXISTS operation_operation_recursive_view AS
WITH RECURSIVE o(operation_id, o, parent_operation_id) AS (
SELECT operation.id, operation.id, operation_operation.parent_operation_id
FROM operation
INNER JOIN operation_operation ON operation.id = operation_operation.operation_id
UNION
SELECT o.o, o.operation_id, operation_operation.parent_operation_id
FROM o
INNER JOIN operation_operation ON o.parent_operation_id = operation_operation.operation_id
WHERE o.o != operation_operation.parent_operation_id
)
SELECT o.operation_id, o.parent_operation_id FROM o ORDER BY operation_id;
CREATE VIEW IF NOT EXISTS type_type_recursive_view AS
WITH RECURSIVE t(type_id, t, parent_type_id) AS (
SELECT TYPE.id, TYPE.id, type_type.parent_type_id
FROM TYPE
INNER JOIN type_type ON TYPE.id = type_type.type_id
UNION
SELECT t.t, t.type_id, type_type.parent_type_id
FROM t
INNER JOIN type_type ON t.parent_type_id = type_type.type_id
WHERE t.t != type_type.parent_type_id
)
SELECT t.type_id, t.parent_type_id FROM t ORDER BY type_id;
Now I will answer to my own question with a solution which works quite well. This is a recursive query and generates every possible combination. This is a recursive query which might be slow when the inheritance level becomes deeper.
CREATE VIEW IF NOT EXISTS role_operation_type_recursive_view AS
WITH RECURSIVE T(role_id, operation_id, type_id) AS (
WITH RECURSIVE O(role_id, operation_id, type_id) AS (
WITH RECURSIVE R(role_id, operation_id, type_id) AS (
SELECT role_id, operation_id,type_id
FROM role_operation_type
UNION
SELECT role_role_recursive_view.role_id, R.operation_id, R.type_id
FROM R
INNER JOIN role_role_recursive_view ON R.role_id = role_role_recursive_view.parent_role_id
)
SELECT * FROM R
UNION
SELECT O.role_id,operation_operation_recursive_view.parent_operation_id ,O.type_id
FROM O
INNER JOIN operation_operation_recursive_view ON O.operation_id = operation_operation_recursive_view.operation_id
)
SELECT * FROM O
UNION
SELECT T.role_id, T.operation_id, type_type_recursive_view.type_id
FROM T
INNER JOIN type_type_recursive_view ON T.type_id = type_type_recursive_view.parent_type_id
)
SELECT * FROM T;

Get rows that no foreign keys point to

I have two tables
CREATE TABLE public.city_url
(
id bigint NOT NULL DEFAULT nextval('city_url_id_seq'::regclass),
url text,
city text,
state text,
country text,
common_name text,
CONSTRAINT city_url_pkey PRIMARY KEY (id)
)
and
CREATE TABLE public.email_account
(
id bigint NOT NULL DEFAULT nextval('email_accounts_id_seq'::regclass),
email text,
password text,
total_replied integer DEFAULT 0,
last_accessed timestamp with time zone,
enabled boolean NOT NULL DEFAULT true,
deleted boolean NOT NULL DEFAULT false,
city_url_id bigint,
CONSTRAINT email_accounts_pkey PRIMARY KEY (id),
CONSTRAINT email_account_city_url_id_fkey FOREIGN KEY (city_url_id)
REFERENCES public.city_url (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
I want to come up with a query that fetches rows in the city_url only if there is no row in the email_account pointing to it with the city_url_id column.
NOT EXISTS comes to mind:
select c.*
from city_url c
where not exists (select 1
from email_account ea
where ea.city_url_id = c.id
);
There's also this option:
SELECT city_url.*
FROM city_url
LEFT JOIN email_account ON email_account.city_url_id = city_url.id
WHERE email_account.id IS NULL
A NOT EXISTS is absolutely the answer to the "... if there is no row ...".
Nonetheless it would be preferable to accomplish this by selecting then difference quantity.
Which is in principle:
SELECT a.*
FROM table1 a
LEFT JOIN table2 b
ON a.[columnX] = b.[columnY]
WHERE b.[columnY] IS NULL
Using the tablenames here, this would be:
SELECT c.*
FROM city_url c
LEFT JOIN email_account e
ON c.id = e.city_url
WHERE e.city_url IS NULL
I believe NOT IN could be used here as well, although this might be less performant on large datasets:
SELECT *
FROM city_url
WHERE id NOT IN (
SELECT city_url_id FROM email_account
)

Using PostgreSQL with clause show count from two tables

I have four tables advertisement,sales_enquiry,sales_estimate and sales_leadsource. Database used is PostgreSQL. I want to get the count of sales_enquiry and count of sales_estimate for an advertisement, advertisement and sales_enquiry has the id of sales_leadsource as foreign key, sales_estimate table has the id of sales_enquiry as foreign key. I have tried using the following query but result is not getting correctly.
Table schema is
TABLE sales_leadsource
(
leadsource_id serial NOT NULL,
leadsource character varying(250),
CONSTRAINT sales_leadsource_pkey PRIMARY KEY (leadsource_id)
)
TABLE advertisement
(
id serial NOT NULL,
ad_source_name character varying(200),
amount numeric(18,2),
duration character varying(50),
leadsource_id integer,
CONSTRAINT advertisement_pkey PRIMARY KEY (id),
CONSTRAINT advertisement_leadsource_id_fkey FOREIGN KEY (leadsource_id)
REFERENCES sales_leadsource (leadsource_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE RESTRICT
)
TABLE sales_enquiry
(
enquiry_id serial NOT NULL,
leadsource_id integer,
customer_name character varying(200),
advertisement_id integer,
CONSTRAINT sales_enquiry_pkey PRIMARY KEY (enquiry_id),
CONSTRAINT sales_enquiry_advertisement_id_fkey FOREIGN KEY (advertisement_id)
REFERENCES advertisement (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE RESTRICT
)
TABLE sales_estimate
(
ref_no integer NOT NULL,
enquiry_id integer,
property_id integer,
sqft_amount numeric,
CONSTRAINT sales_estimate_pkey PRIMARY KEY (ref_no),
CONSTRAINT sales_estimate_enquiry_id_fkey FOREIGN KEY (enquiry_id)
REFERENCES sales_enquiry (enquiry_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE RESTRICT
)
Query is as follows
with campaign as(
select se.leadsource_id,se.advertisement_id,count(*) as leads_count from sales_enquiry se group by se.advertisement_id,se.leadsource_id
),
converted as(
select se.leadsource_id,se.advertisement_id,count(*) as converted_count from sales_enquiry se
left join sales_estimate sm on se.enquiry_id=sm.enquiry_id
where sm.status_id=1 and sm.application_status=1 or sm.application_status=2
group by se.advertisement_id,se.leadsource_id
)
select a.id,
coalesce(a.ad_source_name,'None'),
a.amount,
a.duration,
c.leads_count,
c.leadsource_id,
sl.leadsource,
cv.converted_count
From advertisement a
right join campaign c on a.id=c.advertisement_id
left join converted cv on c.advertisement_id=cv.advertisement_id
left join advertisement_status ads on a.status_id=ads.status_id
left join sales_leadsource sl on c.leadsource_id=sl.leadsource_id;
Please help. Thanks in advance.