SQL Query JOIN and Aggregate - sql

New to SQL and have been googling to no avail. Here is the schema:
"users"
Column | Type |
---------------------+--------------------------+
id | text |
name | text |
title | text |
org_id | text |
type. | text |
"organizations"
Column | Type |
--------------------------------+--------------------------+
id | text |
name | text |
"posts"
Column | Type |
-------------------+--------------------------+
id | text |
title | text |
content | jsonb[] |
owner_id | text |
org_id | text |
is_public | boolean |
My goal is to, in one table, show how many private topics, admins, and standard users each organization has, like this:
Org | Users | Admins | Private Posts
----------+-------+-------+---------------
Org1 | 56 | 10 | 22
Org2 | 111 | 10 | 34
Right now, I only get this:
Org | Count | Type | Private Posts
----------+-------+-------+---------------
Org1 | 10 | admin | 22
Org2 | 111 | user | 34
Org1 | 56 | user | 22
Org2 | 10 | admin | 34
Using:
SELECT t1.id as "Org", t1.cnt as "Count", t1.type as "Type", t2.cnt as "Private Posts" from
(SELECT COUNT(u.type) as "cnt", u.type as "type", o.id FROM "users" AS u JOIN
"organizations" AS o ON o.id=u.org_id GROUP BY u.type, o.id) as t1 join
(SELECT COUNT(org_id) as "cnt", org_id from posts WHERE is_public = False group
by org_id) as t2 on t2.org_id = t1.id;
I basically tried to join the users and organizations and count based on organization and user type (t1), then counted the public posts in posts (t2), and tried to join t1 and t2 based on the organization id. Any help is appreciated.

Consider a concept called conditional aggregation that pivots results to needed wide formatted columns based on conditional logic. Postgres maintains the useful FILTER for this type of query but can also use CASE statements as shared with other RDBMS's:
SELECT o.id AS "Org",
COUNT(*) FILTER(WHERE u.type = 'user') AS "Users",
COUNT(*) FILTER(WHERE u.type = 'admin') AS "Admins",
COUNT(*) FILTER(WHERE is_public = False) AS "Private Posts"
FROM users u
JOIN organizations o
ON o.id = u.org_id
JOIN posts p
ON o.id = p.org_id
GROUP BY o.id;

Related

How to generate multiple rows based on a single record when join a table?

I have two tables User and Post. User has many posts, so user_id is the foreign key.
User:
id | name | base
---+------+-------
1 | User1| 5
Post:
id | content | min | max | user_id
---+---------+------+-----+--------
1 | Test | 8 | 10 | 1
2 | Test2 | 10 | 15 | 1
There could also have other table to join(Which is not show in the example). In general, I can joins the posts and other tables:
SELECT users.id, users.content, posts.min, posts.max, posts.content, ...
FROM users
JOIN table_1
ON condition_1
JOIN table_2
ON condition_2
JOIN posts
ON users.id = posts.user_id
Now I want to get users, but join posts and extend the posts based on user.base, which will seperate id=1 post into two records. So finally I want to get 3 records:
user.id | user.name | user.base | post.id | post.content | post.min | post.max | post.user_id
--------+-----------+-----------+---------+--------------+----------+----------+----------
1 | User1 | 5 | 1 | Test | 5 | 8 | 1
1 | User1 | 5 | 1 | Test | 8 | 10 | 1
1 | User1 | 5 | 2 | Test2 | 10 | 15 | 1
Is it possible to get the result via SQL?
I guess below query would do the job if i understood it correctly ,
(select u.id as uid,u.name,u.base,p.id,p.content, base as min,min as max,user_id
from users u,posts p where u.id = p.user_id
and min = (select min(min) from posts p1 where p1.user_id = u.id))
union
select u.id as uid,u.name,u.base,p.id,p.content,min, max,user_id from users
u,posts p where u.id = p.user_id
order by uid,min
dbfiddle example with whole solution : https://dbfiddle.uk/?rdbms=postgres_13&fiddle=592d8eb5803c11b2a30cac388344a402
UPDATE : To join the results with other tables,use the above query as inline view and join with tables with conditions as desired.
Lets assume another table user_addr that has address details for all users and user_id is the foreign key that references user.id
select uid,name,base,pid,content,min,max,magic_table.user_id,addr_typ,addrline1,addrline2,city,postcode,prov,country
from
((select u.id as uid,u.name,u.base,p.id as pid,p.content, base as min,min as max,user_id
from users u,posts p where u.id = p.user_id
order by min asc
fetch first 1 rows only)
union
select u.id as uid,u.name,u.base,p.id as pid,p.content,min, max,user_id from users
u,posts p where u.id = p.user_id) magic_table, user_addr
where magic_table.user_id = user_addr.user_id
Output :
dbfiddle link with solution : https://dbfiddle.uk/?rdbms=postgres_13&fiddle=ea697bbf1502857834b461edd8ccf447

Postgres query IN query

Is it possible in Postgres to determine if at least one result of query 1 is inside query 2 results?
For example:
SELECT * FROM items
WHERE
(SELECT id FROM users) IN (SELECT user_id FROM user_items WHERE item_id = 1)
I know that this query can be a nonsense, I'm just asking how to do that check in the where clause. In my real query (more complex), I'm getting:
(Postgrex.Error) ERROR 21000 (cardinality_violation): more than one row returned by a subquery used as an expression
if there is more than one result from query1 (query1 IN query2)
EDIT
select user_id
from notification_token n
join notification_folder f on n.user_id = f.user_id
where ((SELECT tag_id FROM notification_folder_tag WHERE notification_folder_id = f.id) IN (SELECT tag_id FROM event_tag WHERE event_id = 1))
tables:
notification_token
| user_id | notification_token |
--------------------------------------------------
| 1 | token1 |
| 2 | token2 |
| 3 | token3 |
notification_folder
| user_id | data |
--------------------------------------------------
| 1 | "useless string" |
notification_folder_tag
| notification_folder_id | tag_id |
--------------------------------------------------
| 1 | 1 |
| 1 | 2 |
| 2 | 5 |
event_tag
| event_id | tag_id |
--------------------------------------------------
| 1 | 1 |
| 2 | 2 |
| 3 | 8 |
The result that I want is user_id 1 from notification_token.
"Where" should be true because at least one tag_id from the left side of the IN (result 1,2) is contained in the right side of the IN (result 1).
Anyways i get error when the left side of the IN is composed by more than one entry. It works properly with just one entry
Try this
SELECT * FROM items
WHERE
EXISTS (SELECT id FROM users)
IN
(SELECT user_id FROM user_items WHERE item_id = 1);
If this doesn't work, go for relational database queries.
You seem to want items anyone who ordered item_1 has also ordered. If this interpretation is correct, then here is one way to write the query:
select distinct i.*
from items i join
user_items ui
on ui.item_id = i.item_id
where ui.user_id in (select ui2.user_id
from user_items ui2
where ui2.item_id = 1
);

JOIN the table if records exist

is it possible if i want to do INNER JOIN only if the record exist on the 2nd table if not then dont join?
this is my table
User table
+--------+--------------+
| id | name |
+--------+--------------+
| 1 | John |
+--------+--------------+
| 2 | Josh |
+--------+--------------+
House table
+--------+-------------+--------------+
| id | owner_id | house_no |
+--------+-------------+--------------+
| 1 | 1 | 991 |
+--------+-------------+--------------+
this is my INNER JOIN query
SELECT h.owner_id, u.name, h.house_no FROM user u
INNER JOIN house h on u.id = h.owner_id
WHERE u.id = :id
it will return this result if id = 1
+--------+--------------+--------------+
| id | name | house_no |
+--------+--------------+--------------+
| 1 | John | 991 |
+--------+--------------+--------------+
but if i run with id = 2 no result returned.
what i want to do right now is it still return the result even when no data exist for id = 2 in table house
Use a left outer join instead.
SELECT u.id, u.name, h.house_no FROM user u
LEFT OUTER JOIN house h on u.id = h.owner_id
WHERE u.id = :id
The resulting record will be:
+--------+--------------+--------------+
| id | name | house_no |
+--------+--------------+--------------+
| 2 | Josh | null |
+--------+--------------+--------------+

SQL query using GROUP_CONCAT() function

I have two tables:
1.Activity-- which stores all the activities from a user
2.User-- which stores all the registered users
I want to fetch all the posts and the likes to all posts in one query. A row is a post or like is decided by the value of type column.
Table Name - Activity
| actId | parentId | type | postedby |
---------------------------------------------------
| 100 | NULL | post | 100 |
| 200 | 100 | like | 200 |
| 300 | 100 | like | 300 |
| 400 | 100 | like | 400 |
| 500 | NULL | post | 100 |
| 600 | 500 | like | 600 |
Table Name - User
| userId | name |
-------------------
| 100 | Amit |
| 200 | Alok |
| 300 | Arjun |
| 400 | Arpeet |
| 600 | Amaan |
The output should be
| actId | name | LikedBy |
------------------------------------------
| 100 | Amit | Alok, Arjun, Arpeet|
| 500 | Amit | Amaan |
NOTE - I don't want this to be achieved using FOR XML PATH since it is not supported in SQLite which I am working on.
My query attempt was
SELECT a.id, u.name,
(select group_concat(u.name) from activity a, user u where a.id = u.id and a.type = 'like'
group by a.parent) as likedby
FROM activity a, user u
WHERE a.id = u.id and a.type = 'post'
The result I got was
| Id | name | LikedBy |
---------------------------------------
| 100 | Amit | Alok, Arjun, Arpeet|
You can see that the other row was missing from result.
Try this:
select t.id4id Id, n1.Name Name, group_concat(n2.name) Groups
from (select t1.actid id4id, t1.postedby id4name, t2.postedby id4groups
from Activity t1 join Activity t2 on t1.actid = t2.parentid) t join "user" n1 on t.id4name = n1.userid join "User" n2 on t.id4groups = n2.userid
group by id4id
Notes: 1. I assume that a post will always have a null value in parent
You do not need a subquery for this. You simply need two joins in order to get the values for the original name and the liked name:
select a.parentid, u.name, group_concat(ul.name separator ', ') as likedby
from activity a join
user ul
on a.postedby = ul.userid join
user u
on a.parentid = u.userid
where a.type = 'like'
group by a.parentid, u.name;

Select all Users related to Company

First of all, sorry for the meaningless title. Couldn't think of anything better. Please, take a look at my following tables:
User
+-----+-------+---------------+
| id | name | email |
+-----+-------+---------------+
| 1 | NameA | namea#srv.com |
| 2 | NameB | nameb#srv.com |
| 3 | NameC | namec#srv.com |
+-----+-------+---------------+
Department
+-----+---------+-------+---------+
| id | company | name | manager |
+-----+---------+-------+---------+
| 1 | 1 | DeptA | 1 |
| 2 | 1 | DeptB | 2 |
+-----+---------+-------+---------+
Company
+-----+------+-------+
| id | name | owner |
+-----+------+-------+
| 1 | Buzz | 3 |
+-----+-------+------+
I need to find all users related to a Company. Something like:
+---------+------------+
| user_id | company_id |
+---------+------------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
+---------+------------+
How can I do that? I read about the different kind of joins (inner, outer, full, etc), but I couldn't figure out how to handle that "Department" table in the middle of everything.
I would do this with two queries:
select Department.manager as user_id, Company.id
from Company
join Department on Department.company=Company.id
union
select Company.owner as user_id, Company.id
from Company
select User.id, User.name, User.email, CASE WHEN User.id = Company.Owner THEN 'YES' ELSE 'NO' END as CompanyOwner
from User
inner join Department on User.DepartmentID = Department.id
inner join Company on Company.id = Department.company
I think you're missing an entitiy relationship between Users and Departments, perhaps a departmentID on the User Table. I think that might be the part you're missing.
Typically, you would have some way to link Users to the Department they work in. Either with a DeptID column added onto the User table. In this example, the owner's department is represented as null, while the department of managers is populated, but which way you do this is up to you.
User
+-----+-------+---------------+------+
| id | name | email | dept |
+-----+-------+---------------+------+
| 1 | NameA | namea#srv.com | 1 |
| 2 | NameB | nameb#srv.com | 2 |
| 3 | NameC | namec#srv.com | +++ |
| 4 | NameD | named#srv.com | 2 |
| 5 | NameE | namee#srv.com | 2 |
+-----+-------+---------------+------+
So to get the list of all company 1 users where the department is populated on the User table, this becomes simple
SELECT u.id as user_id,
d.company as company_id
FROM User u
INNER JOIN Department d ON u.dept=d.id
WHERE d.company=1
UNION
SELECT owner as user_id,
id as company_id
FROM Company
If you chose not to populate manager's department in the User table, you would have to append a UNION for that part as well, similar in construct to the first part directly above.
Or you might instead have an association table to show which employees work for which department. This might be needed if a person could work in more than one department. (The managers and owners might not need to be in this table, since they are already accounted for. Again, up to you. But if you do include them, then you wont need the UNIONs, so it will keep things simpler. You might even put the owner in an "OWNERSHIP" department.)
UserDept
+--------+--------+
| UserID | DeptID |
+--------+--------+
| 4 | 2 |
| 5 | 2 |
+--------+--------+
SELECT u.id as user_id,
d.company as company_id
FROM UserDept u
INNER JOIN Department d ON u.dept=d.id
WHERE d.company=1
(Add UNIONs on as needed, if you did not include owners / managers in the UserDept table.)
But in case you need a deeper example, let's suppose you also needed the name from the User table:
SELECT u.id as user_id,
d.company as company_id,
u.name as user_name
FROM User u
INNER JOIN UserDept x ON u.id=x.user
INNER JOIN Department d ON x.dept=d.id
WHERE d.company=1