Condition on count of associated records in SQL - sql

I have the following tables (with given columns):
houses (id)
users (id, house_id, active)
custom_values (name, house_id, type)
I want to get all the (distinct) houses and the count of associated users that:
have at least 1 associated custom_value which name column contains the string 'red' (case insensitive) AND the custom_value column type value is 'mandatory'.
have at least 100 associated users which status column is 'active'
How can I run this query in PostgreSQL?
Right now I have this query (which was answered in Get records where associated records name contain a string AND associated record count is bigger than threshold), but I don't know how to select the count of users too (:
select h.*
from houses
where
exists (
select 1
from custom_values cv
where cv.house_id = h.house_id and cv.type = 'mandatory' and lower(cv.name) = 'red'
)
and (
select count(*)
from users u
where u.house_id = h.house_id and u.status = 'active'
) >= 100

You can turn the subquery to a lateral join:
select h.*, u.no_users
from houses h
cross join lateral (
select count(*) no_users
from users u
where u.house_id = h.house_id and u.status = 'active'
) u
where
u.cnt >= 100
and exists (
select 1
from custom_values cv
where cv.house_id = h.house_id and cv.type = 'mandatory' and lower(cv.name) = 'red'
)

Related

How to select users that exist in one group and only that group - oracle

I am trying to get a list of users that only exist in a single group and no other groups. The users can be in multiple groups.
The group I want to get users in has an ID of 20064212 and belongs to the acc_id 200640
There are three tables involved:
Users
USER_PK_ID,
ACC_ID,
Active,
Deleted
USER_GROUP_USER
GROUP_ID, USER_PK_ID
USER_GROUP
ID,
ACC_ID,
DESCRIPTION,
ACTIVE
I can get all the users in the group with the fallowing query but it does not exclude users that exist in other groups also.
SELECT DISTINCT (U.USER_PK_ID)
FROM USER_GROUP_USER U
JOIN USERS US ON US.USER_PK_ID = U.USER_PK_ID
WHERE GROUP_ID = 20064212
AND US.acc_id = 200640
AND US.DELETED = 'N'
I have tried various queries but they always seem to return users that also exist in other groups
SELECT DISTINCT (U.USER_PK_ID)
FROM USER_GROUP_USER U
JOIN USERS US ON US.USER_PK_ID = U.USER_PK_ID
WHERE GROUP_ID = 20064212
AND US.acc_id = 200640
AND US.DELETED = 'N'
AND GROUP_ID NOT IN (
SELECT ID
FROM USER_GROUP
WHERE acc_id = 200640
AND ID != 20064212)
Hoping I parsed your text correctly; this SQL returns users with ACC_ID= 200640 and DELETE='N' and that are member of group with ID 20064212, but not member of any other group.
select u.user_pk_id
from users u
join user_group_user ugu on (u.user_pk_id = ugu.user_pk_id)
join user_group ug on ugu.group_id=ug.id
where ugu.group_id=20064212
and u.acc_id=200640
and u.deleted='N'
and not exists (
select null
from user_group_user ugu2
where ugu2.group_id != ugu.group_id
and ugu.user_pk_id = ugu2.user_pk_id
);

Which query is more performant using groupBy in subquery?

I have next task:
I have table columns and I have table tasks - One Column has Many Tasks, Task belongsTo one Column.
First query^
SELECT id,
name,
color,
created_at,
CASE
WHEN jt.tc IS NULL THEN 0
ELSE jt.tc
END
FROM columns AS c1
LEFT JOIN
(SELECT count(*) AS tc,
column_id
FROM tasks AS t
GROUP BY column_id) AS jt ON c1.id=jt.column_id
WHERE board_id = 'some id here';
In that case in jt table will be grouped all records from tasks table and with big amount of data in tasks it will not work at all (very slow slow speed)
Second query:
SELECT id,
name,
color,
created_at,
CASE
WHEN jt.tc IS NULL THEN 0
ELSE jt.tc
END
FROM columns AS c1
LEFT JOIN
(SELECT count(*) AS tc,
column_id
FROM tasks AS t
LEFT JOIN columns c ON t.column_id = c.id
WHERE c.board_id = 'some id here'
GROUP BY column_id) AS jt ON c1.id=jt.column_id
WHERE board_id = 'some id here';
In that case within jt table will be only those columns which i need, so where clause will cut selection a lot.
Am i right?

How to create cases for multiple purposes and just showing the count of unique IDs for multiple cases

I wrote a CTE which helps determine a flag based off a certain client's ID and what kind of client they are. I am looking to test the counts of the Flags, and the counts are totaling out amazingly! However, I am looking to add additional columns to the very last section of my code to show the amount of IDs who have belonged in all 3 cases, or both PPP and R, PPP and RR, RR and PPP, or RR and R. Is there a way I could do this? I know SUM won't work. I'm thinking using CASE or an IF, however I am a novice to SQL and am unsure what to do.
WITH ids AS (
SELECT DISTINCT LOWER(r.entry_id) AS ID
FROM id_user AS r
UNION
SELECT DISTINCT LOWER(identifiervalue) AS ID
FROM account AS a
)
,PPP as (
SELECT DISTINCT
LOWER(accountid) as "ID"
FROM ppp
WHERE (date >= '2022-11-21')
)
,R as (
SELECT DISTINCT
LOWER(account_id) as "ID"
FROM user
)
, RR as (
SELECT DISTINCT
LOWER(id) AS "ID"
FROM program_member
)
, Joint as (
SELECT
r.ID
,CASE WHEN p.ID IS NULL THEN 0 ELSE 1 END AS "PPP Flag"
,CASE WHEN r.ID IS NULL THEN 0 ELSE 1 END AS "R Flag"
,CASE WHEN rr.raid IS NULL THEN 0 ELSE 1 END AS "RR Flag"
FROM ids i
LEFT JOIN PPP ppp ON i.RAID = ppp.RAID
LEFT JOIN R r ON i.RAID = r.RAID
LEFT JOIN RR rr on i.RAID = rr.RAID
----TESTING COUNTS
SELECT
COUNT(ID) AS "ID Count"
,sum("PPP Flag") AS "PPP Users"
,sum("R") AS "R Accounts"
,sum("RR Flag") AS "RR Users"
FROM Joint
if all you are trying to do is get distinct counts, and how many are within each of those given flag categories, dont try to repeat then join. They are either in the PPP, User or Program_Member table. Just get that
select
UnionCnt.IDAndRaidCnt,
P.PPPCnt,
R.RCnt,
RR.RRCnt
from
-- this outer query returns only 1 record
( select
count(*) IDAndRaidCnt
from
-- this inner query returns all distinct based on the UNION result
(SELECT DISTINCT
LOWER(r.entry_id) ID
FROM
id_user r
UNION
SELECT DISTINCT
LOWER(identifiervalue) ID
FROM
account a)
) UnionCnt
JOIN
(SELECT
count( DISTINCT LOWER(accountid)) PPPCnt
FROM
ppp
WHERE
date >= '2022-11-21' ) P
-- above will only return a single record anyhow
on 1=1
JOIN
(SELECT
count( DISTINCT LOWER(account_id) RCnt
FROM
user ) R
-- also returns single row with total qualifying distinct ID count
on 1=1
JOIN
(SELECT
count( DISTINCT LOWER(id)) RRCnt
FROM
program_member ) RR
-- same here, single record
on 1=1

How to use array in a sql join and check if all of the array elements satisfy a condition?

I have two tables, activity and contacts in postgresql.
An activity can have multiple contacts in array form, like this
contact Id = {23,54,34}.
I want to delete a activity only if all the contact Ids of that activity are deleted in contacts table and keep the activity if at least one one contact id is still not deleted.
Deleted At is column in contacts table to check for deleted contacts. I don't want to use NOT IN.
Activity table
id contact Id
-------------------
16 {60,61,23}
15 {}
5 {59}
6 {}
You can use simple EXISTS predicate testing contacts table with activity.contacts array:
create table activity (
id int primary key,
contacts int[]
)
create table contacts (
id int primary key,
name varchar(10),
deleted boolean
)
insert into activity
select 16 as id, '{1,2,3}'::int[] as contacts union all
select 15, null union all
select 5, '{4}' union all
select 6, '{6, 5}'
insert into contacts
select 1 as id, 'q' as name, false as deleted union all
select 2, 'w', false union all
select 3, 'e', true union all
select 4, 'r', false union all
select 5, 't', true union all
select 6, 'y', true
delete
from activity a
where not exists (
select null
from contacts c
where not(c.deleted)
and c.id = any(a.contacts)
)
2 rows affected
db<>fiddle here
Use UNNEST to convert the nested-array to rows, then do an anti-join to look for broken references:
(An anti-join is when you perform a x LEFT OUTER JOIN y with a WHERE y.KeyColumn IS NULL - this gives you the opposite of an INNER JOIN with the same join criteria).
WITH unnested AS (
SELECT
Id AS ActivityId,
UNNEST( ContactId ) AS ContactIdFromArray
FROM
crmActivity
)
SELECT
u.ActivityId,
u.ContactIdFromArray AS Missing_ContactId_in_Activity
FROM
unnested AS u
LEFT OUTER JOIN contacts AS c ON
c.ContactId = u.ContactIdFromArray
WHERE
c.ContactId IS NULL
ORDER BY
u.ActivityId
I want to delete a activity only if all the contact Ids of that activity are deleted in contacts table and keep the activity if at least one one contact id is still not deleted.
This can be done with a DELETE FROM using WHERE crmActivity.Id IN with a CTE that generates the correct set of bad crmActivity.Id values, via a GROUP BY with the above query:
WITH unnested AS (
SELECT
Id AS ActivityId,
UNNEST( ContactId ) AS ContactIdFK
FROM
crmActivity
)
WITH brokenContacts AS (
SELECT
u.ActivityId,
u.ContactIdFK,
c.ContactId AS ContactIdPK
FROM
unnested AS u
LEFT OUTER JOIN contacts AS c ON
c.ContactId = u.ContactIdFromArray
)
WITH counts AS (
SELECT
ActivityId,
COUNT(*) AS ContactIdCount,
COUNT( CASE WHEN ContactIdPK IS NULL THEN 1 END ) AS BadContactIdCount
FROM
brokenContacts
GROUP BY
ActivityId
)
WITH badActivityIds AS (
SELECT
ActivityId
FROM
counts
WHERE
BadContactIdCount = ContactIdCount
AND
ContactIdCount > 0
)
DELETE FROM
crmActivity
WHERE
ActivityId IN ( SELECT ActivityId FROM badActivityIds );

Count for all values of enum in PostgreSQL

I have a table called users, which has the following columns:
id: INT NOT NULL
face: face_type
face_type is an ENUM type that has the following values: 'square', 'round' and 'triangle'.
And I have another table called houses, which has the following columns:
id: INT NOT NULL
user_id: INT NOT NULL
Now, I want to get all the houses grouped by the different type of face types. So, what I have so far is this:
SELECT users.face_type, COUNT(*)
FROM users
LEFT JOIN houses ON houses.user_id = users.id
GROUP BY users.face_type
The problem is that I also want to get rows for face_type which none of the users have, as well as a result for NULL face_type. So, for example, if I have the following data:
users (id, face_type)
1, 'round'
2, 'triangle'
houses (id, user_id)
1, 1
2, 1
3, 2
I would expect the result to be:
face_type, count
'round' 2
'triangle' 1
'square' 0
null 0
I know how to get all the potential values of the face_type ENUM, by doing :
SELECT unnest(enum_range(NULL::face_type)) AS face_types;
But I don't know how to use that to count all potential face types in the aggregate, as well as also calculating for NULL face types.
You can use LEFT JOIN:
SELECT ft.face_type, COUNT(h.user_id)
FROM (SELECT unnest(enum_range(NULL::face_type)) AS face_types
) ft LEFT JOIN
users u
ON u.face_type = ft.face_type LEFT JOIN
houses h
ON h.user_id = u.id
GROUP BY ft.face_type;
To get NULL, just use UNION ALL:
SELECT ft.face_type, COUNT(h.user_id)
FROM (SELECT unnest(enum_range(NULL::face_type)) AS face_types
UNION ALL
SELECT NULL
) ft LEFT JOIN
users u
ON u.face_type = ft.face_type LEFT JOIN
houses h
ON h.user_id = u.id
GROUP BY ft.face_type;
Of course, the = will not every match. If that is possible, then you want to change the JOIN condition to u.face_type is not distinct from ft.face_type.
to COUNT(houses.*)
SELECT face_type.type, COUNT(houses.*)
FROM (SELECT unnest(enum_range(NULL::face_type))) AS face_type(type)
FULL JOIN users ON users.face_type=face_type.type
LEFT JOIN houses ON houses.user_id = users.id
GROUP BY face_type.type
A LEFT JOIN starting from the ENUM and going to users and houses will allow you to recover totals for each enumerated value. To also display the NULL face types, you can use a UNION query.
SELECT
ft.face_type,
COUNT(ho.user_id) as cnt
FROM
(SELECT unnest(enum_range(NULL::face_type)) AS face_types) ft
LEFT JOIN users us ON us.face_type = ft.fact_type
LEFT JOIN houses ho ON ho.user_id = us.id
GROUP BY ft.face_type
UNION
SELECT
null,
COUNT(ho.user_id)
FROM houses ho
INNER JOIN users us ON ho.user_id = us.id AND us.face_type IS NULL
ORDER BY cnt desc