PostgreSQL adding multiple results to an array - arrays

Hello Stack Overflow Users,
I'm trying to pull together a list of users and all of their permissions, but I want to aggregate the permissions so they they list in the single record of the user.
The users table is pretty simple, but the permissions are broken out individually.
To give you an example, there are 3 tables: users, user_access, page
Also, I'm stuck with PostgreSQL v8.4.
The data pretty much looks like the following:
users table
user_id,username
1,bob
2,cindy
3,jen
user_access table
id,user_id,page_id,allowed
1,1,5,true
2,1,7,true
3,1,8,false
4,2,4,true
5,2,5,true
6,2,7,false
7,3,1,true
8,3,5,false
page table
page_id,page_name
1,report_1
2,report_2
3,report_3
4,admin
5,users
6,options
7,addl_options
8,advanced
So far I have been able to create an array for the permissions, but I'm only grabbing a single page permission and displaying the result of whether it's allowed or not. I'm having a hard time figuring out how to collect the rest of the pages and what they're set to within the one cell for each user.
What I have so far:
select users.user_id, users.username,
(select array(select page.page_name || ':' || user_access.allowed)
where users.user_id = user_access.user_id and page.page_id = user_access.page_id) as permissions
from users
full join user_access on users.user_id = user_access.user_id
full join page on page.page_id = user_access.page_id;
But this only returns results like the following:
user_id,username,permissions
1,bob,{report_1:<null>}
1,bob,{report_2:<null>}
1,bob,{report_3:<null>}
1,bob,{admin:<null>}
1,bob,{users:true}
1,bob,{options:<null>}
1,bob,{addl_options:true}
1,bob,{advanced:false}
What I want to get back instead is all permissions per user record like this:
user_id,username,permissions (where permissions is an array of <page_name>:<allowed>)
1,bob,{report_1:<null>,report_2:<null>,report_3:<null>,admin:<null>,users:true,options:<null>,addl_options:true,advanced:false}
2,cindy,{report_1:<null>,report_2:<null>,report_3:<null>,admin:true,users:true,options:<null>,addl_options:false,advanced:<null>}
3,jen,{report_1:true,report_2:<null>,report_3:<null>,admin:<null>,users:false,options:<null>,addl_options:<null>,advanced:<null>}
I appreciate any advice you could assist with.
Thank you!

You need a cross join between users and page, then an outer join to the permissions. The result of that then needs to be grouped:
select u.user_id, array_agg(concat(p.page_name, ':', coalesce(ua.allowed::text, '<null>')) order by p.page_name)
from users u
cross join page p
left join user_access ua on ua.page_id = p.page_id and ua.user_id = u.user_id
group by u.user_id;
Online example: https://rextester.com/XYC99067
(In the online example I used string_agg() instead of array_agg() as rextester doesn't display arrays nicely)

Related

JOIN - Select columns/details with different criteria than COUNT

I have a chat system. There are 3 tables:
data_chats - holds the IDs of the chats themselves. This is where you mark a chat as deleted.
data_chat_parties - holds the member or team ID that is included in chat along with chat permissions, who they were invited by, etc
data_chat_messages - holds the actual messages of chats
With my query, I am trying to fetch the info from data_chat_parties related to the party requesting this information (ie currently logged in user), but also get the total number of chat parties in the chat.
SELECT
data_chats.id AS chat,
data_chats_parties.*,
COUNT(data_chats_parties.id) AS total_parties,
data_chats_messages.created AS last_message_created,
data_chats_messages.author AS last_message_author,
data_chats_messages.author_type AS last_message_author_type,
data_chats_messages.message AS last_message
FROM data_chats
LEFT JOIN data_chats_parties ON data_chats_parties.chat=data_chats.id
LEFT JOIN data_chats_messages ON data_chats_messages.chat=data_chats.id AND data_chats_messages.active=1
WHERE
data_chats.active=1 AND
data_chats_parties.member=1 AND
data_chats_parties.status >= 1
GROUP BY data_chats_parties.chat
ORDER BY last_message_created DESC
This all works fine, except that total_chat_parties always returns 1, presumably because it's only matching the record of data_chats_parties.member=1. How would I fetch the party record specific to this user but at the same time, fetch the total number of parties for this chat?
You should use a correlated query :
SELECT data_chats.id AS chat,
(SELECT COUNT(data_chats_parties.id) FROM data_chats_parties
WHERE data_chats_parties.chat = data_chats.id) AS total_parties,
data_chats_messages.created AS last_message_created,
data_chats_messages.author AS last_message_author,
data_chats_messages.author_type AS last_message_author_type,
data_chats_messages.message AS last_message
FROM data_chats
LEFT JOIN data_chats_messages
ON data_chats_messages.chat = data_chats.id
AND data_chats_messages.active = 1
AND data_chats_parties.member = 1
AND data_chats_parties.status >= 1
WHERE data_chats.active = 1
ORDER BY last_message_created DESC
Another thing is the conditions on the WHERE clause, you can filter the RIGHT table of a LEFT JOIN in the WHERE clause, those condition should only be specified in the ON clause.
You also group by a column from the RIGHT table - this is not suggested at all! Either use an inner join, or group by another field.
you may be able to use a subquery in the select statement to give you the desired count.
(select COUNT(data_chats_parties.id) from data_chats_parties where data_chats_parties.chat=data_chats.id) AS total_parties,
Also you can then remove the line
LEFT JOIN data_chats_parties ON data_chats_parties.chat=data_chats.id
Hopefully I've typed that all correctly =)

MYSQL - Randomly Selecting a row where two users are not following each other?

I am building an app that aims to show a user a randomly selected user from a list that he/she may like to follow where they are not following each other already.
I have 2 tables one table has a list of users and the second table has 2 columns that show a "following" relationship.
Table 1 user - user_id, instagram_id, join_date
Table 2 users_follow - instagram_id_1, instagram_id_2
I was just wondering if anyone could point me in the right direction on how to create the join and select a random row where the 2 users are not following each other? I have tried a left join but without any luck so far.
This is the code so far
SELECT *
FROM users
INNER JOIN `user_follows` ON users.instagram_id = user_follows.instagram_id
WHERE user_follows.instagram_id != 'insta123'
Thanks for the help.
I would suggest you make a sub query to get the list of your follower. Then user a LEFT JOIN and find those who are not follower by testing for NULL.
SELECT users.* from users
left join
(
SELECT
users_follows.instagram_id_2 as 'instagram_id'
from users
inner join users_follows on users.instagram_id=users_follows.instagram_id_1
where users.instagram_id = 'insta123'
) as followers on users.instagram_id = followers.instagram_id
where followers.instagram_id is null
and users.instagram_id != 'insta123' # you can't follow yourself
I've tested it with a replica of your structure and it works fine.

mySQL: How to join users with user_logs returning the latest log only?

Hi Guys i have 2 tables (user/logs kinda thing)
tk_assets (the users)
tk_sessions (the logs) <-- where each log contains both stamp_in and stamp_out epoch values (making up 1 session of the user.
User is considered logged in when its latest session does not have a stamp_out value (still logged in)
I simply need to retrieve all users in the database along its "LATEST SESSION"..
In plain english, this would be
"SELECT each user as well as its most recent session"
I can't seem to figure out the proper SQL statement for this..
SELECT
tk_assets.ass_id,
tk_sessions.stamp_in,
tk_sessions.stamp_out
FROM
tk_assets
LEFT JOIN tk_sessions ON tk_assets.ass_id = tk_sessions.ass_id
GROUP BY
tk_sessions.ass_id
ORDER BY
tk_sessions.stamp_in DESC
this is as far as i got..
Unless you need other information, MAX should help you out.
SELECT u.*, MAX(l.stamp_out) FROM user u
INNER JOIN logs l ON l.user_id = u.id
WHERE l.stamp_out = 0
GROUP BY u.id
If you need more that that, take a look at this question: MySQL JOIN the most recent row only?
Yoh your naming conventions are a bit rough
SELECT assets.`ass_id`, sessions.`stamp_in`, sessions.`stamp_out`, sessions.`ass_id`
FROM tk_assets assets
LEFT JOIN tk_sessions sessions
ON assets.`ass_id` = sessions.`ass_id`
WHERE assets.`ass_id` = sessions.`ass_id`
GROUP BY sessions.`ass_id`
ORDER BY sessions.`stamp_in` DESC

Creating a FRIEND RELATION in MySQL and PHP

I am creating a system to keep the relationships in my application. When someone says someone is stored in your database id, and his friend (in that order). And when to the other person accepts the request the id of his friend and the other id (in reverse order) is saved.
To verify that you two are friends have to see that there are these two rows.
The last problem is that when this is true, I have to retrieve the name of a friend from another table .. and do not know how to express it in PHP.
I've been testing, and should be something like this. Here is the code:
SELECT nombre FROM users
JOIN friends WHERE (id=usuari_o OR id=usuari_t) AND
(usuari_t='$id' OR usuari_o='$id')
FRIENDS TABLES STRUCTURE:
USERS TABLE STRUCTURE:
Something like this:
SELECT original.nombre as original_nombre, friends.usuari_o, target.nombre as target_nobmre, friends.usuari_t
FROM friends
LEFT JOIN users AS original ON original.id = friends.usuari_od
LEFT JOIN users AS target ON target.id = friends.usuari_t;
Try this -
SELECT users.nombre FROM users
LEFT JOIN friends WHERE
(users.id=friends.usuari_o OR users.id=friends.usuari_t) AND
(friends.usuari_two='$id' OR friends.usuari_one='$id')

User specified content sharing like G+

I'm building a site that requires sharing with either group(s) or individual user(s). I know for a fact that google does not use mysql, but i was wondering how i could replicate such feature on my site. On g+, one can:
Share a post with the "public" (everyone can see it).
Share a post with "all circles" (everyone in your circles can see it).
Share a post with both circles and individual users. E.g. post = "my first post" and is shared with family,friends, user 1(Joey tribbiani), user 2 (Ross geller) etc.
Conditions:
If a post is shared with a circle and a new user is added to the circle, then (s)he should be able to see all the previous posts shared with that circle.
If a user is removed from a circle. (s)he cannot see posts shared with that circle except posts (s)he has commented on.
Currently my database tables look like this.
Circle_category
Cat_id
Cat_name
user_id
Posts
post_id
user_id
post
is_public
all_circle
Post_to_circle
entry_id
post_id
cat_id
Post_to_user
entry_id
post_id
user_id
Post a user in family circle(which is in Circle_category with cat_id of 1 ) can see
They can see posts that are public.
They can see posts shared with all circles.
They can see posts shared with family circle.
They can see posts shared with them (Individual user).
SQL
SELECT p.* FROM posts p
JOIN Post_to_circle pc
ON p.post_id = pc.post_id
JOIN Post_to_user pu
ON p.post_id = pu.post_id
WHERE p.is_public = 1
OR all_circle = 1
OR pc.cat_id = $cat_id
OR pu.user_id = $user_id
Quetions:
Firstly, I've been able to get posts from case 1(see all public post), case 2 (Posts shared with all circles) but the other 2 cases do not work. I thought about it and saw that the main problem is that i specified the where clause to get posts where p.is_public = 1 which means it neglets rows where p.is_public = 0. How do i update the query so it shows posts covering all four cases and also covers the conditions we talked about at the beginning.
Secondly, is there a better way to structure my tables? i'm not sure i'm doing it the right way.
From a quick read trough, all i can say is:
you are using a join statement instead of a left join statement.
using join means:
keep all rows from the table used in from-clause that validate true for the condition specified in that join clause.
since you are using 2 statements, the first join throws away all the records that dont have the needed join, the second join throws away all the records that dont have the needed join in the second one, but it only uses records that matched the first join.
you should use left join instead. this keeps all rows from the first table. all rows that didnt have a match, get the values NULL for the columns specified in the joined table(s)
simple example:
users table:
user_id
name
user_posts
post_id
user_id
content
created
related queries:
select *
from users u
JOIN user_posts up on up.user_id = u.user_id and up.created > date_sub(curdate(), interval 1 day)
this will use all users and make match with each post that was created less then a day ago by that user.
if the user didnt have a post in the last day, he will NOT be in the resultset.
change this example to
select *
from users u
LEFT JOIN user_posts up on up.user_id = u.user_id and up.created > date_sub(curdate(), interval 1 day)
this will use all users and make a match with each post that was created less then a day ago by that user
if the user hasn't posted in the last day, he will STILL be in the resultset, but all the columns from the posts table will be NULL
the where filters all the rows you have left after the joins. (mysql will use where clauses before joining, if they can speed up the query).
altering your query:
make sure the clauses in where statement are wrapped between () for all the different cases. ALSO this is NOT the complete answer, as there is info missing (example user tables, circle relation tables, friend relations)
also the all_circles option confuses me, so it's missing from the query, but this should get you on the right track
SELECT p.* FROM posts p
left JOIN Post_to_circle pc
ON p.post_id = pc.post_id and /* define statement for valid circles for user you're trying to get the posts for */
left JOIN Post_to_user pu
ON p.post_id = pu.post_id and /* define statement for valid friends for user you're trying to get the posts for */
WHERE
/* 1 day old */
p.created > date_sub(curdate(), interval 1 day)
AND (
/* is public */
p.is_public = 1 OR
/* or to friends */
pu.id is not null OR
/* or to circles */
pc.id is not null
)
Also, i'm suspecting you'll need 2 subqueries, which is not the best thing to do, and my advise would be to find all correct ids for the friends, and all ids for the valid circles and then using an IN clause in each join statement (part thats in comment)

Resources