I want to perform a textsearch on a table containing posts belonging to topics within groups. Depending on the privacysettings for these groups I need to run a subquery to check if the requesting user is a member of the groups containg search matches.
Databasescheme:
Table: posts
Columns: id, group_id, title, text
Table: groups
Columns: group_id, privacy
Table: group_memberships
Columns: group_id, is_member
The privacy column in the group table contains an integervalue.
1 = public, anyone can access the data
2 = system, anyone can access the data
3 = private, only members can access the data
What the query should do:
1. Find some matches in the post table
2. Check the group privacy in the groups table -> a value HIGHER THAN 2 requires a membership check
3. Do a membership check on the group_memberships table if required
I really don't know how do handle this.
It looks like mysql supports two ways? IF statements and case expressions?
What would be a correct way for this?
PS: The subquery for membership checking should be optional and only firing if needed.
Something "like" this..
Pseudocode:
SELECT p.id, p.title, p.text
FROM posts p
INNER JOIN groups g
ON g.group_id = p.group_id
AND p.title is not null
WHERE EXISTS(
CASE
WHEN g.privacy < 2 THEN ''everything is ok. Nothing more needed''
ELSE (''Membership check needed'')
END
)
EDIT:
Can someone confirm that this is a/the right way?
SELECT p.id, p.channel_id, p.title, g.name
FROM posts p
INNER JOIN user_groups g
ON g.id = p.channel_id
AND p.title is not null
WHERE g.privacy < 2 OR (SELECT count(*) FROM user_groups_memberships WHERE uid = 1 AND channel_id = p.channel_id AND rank IS NOT NULL AND is_banned IS NULL) = 1
GROUP BY p.parent_id
Okay this might not be the best answer, but this solves the above problem without using IF or CASE expressions.
select
p.id,
p.group_id,
p.title,
p.text
from
posts p,
groups g
where
p.group_id = g.group_id
and
(
g.privacy<3
or
( g.privacy => 3 and
(select is_member from group_memberships gm where gm.group_id = g.group_id) = 1)
);
Assuming here that is_member = 1 means that id is a member and 0 means that id isn't.
Related
Im newbie please help me on my project. I have 2 tables below.
Table user:
Table Likes
I want to count all likes per story_id and check if the given user has like the story else it will return null.
as of now this is my query and output, given user id = 1.
SELECT COUNT(*) , sl.story_id, u.id as user
FROM stories_likes sl
LEFT JOIN users u ON sl.user_id = u.id AND sl.user_id = 1
GROUP BY sl.story_id
My output:
But what i want to get output is:
Given: user_id = 1
Given user_id = 4
Sorry for the construction of my question i dont know how. Thanks in advance
If the query should display the result for a one particular user_id only then try the following query:
select count(*),
story_id,
(case find_in_set(1, Group_concat(user_id separator ',')) >0 then 1
else NULL
end
)as user_id
from Stories_Likes
group by story_id;
In above query, you can put user_id manually after when, or you can set a variable with particular Id and use it in the query.
For i.e., if you want to check for user_id=4, then put 4 after when and then.
Click here for the Updated Demo
Hope it helps!
The users table is left joined to the query, so it may have null values. Instead, you should use the user_id column from the stories_likes table:
SELECT COUNT(*) , sl.story_id, sl.user_id as user
-- Here --------------------------^
FROM stories_likes sl
LEFT JOIN users u ON sl.user_id = u.id AND sl.user_id = 1
GROUP BY sl.story_id
Got the answer. for those who has the same problem and needed this here's the answer.
SELECT COUNT(*) , sl.story_id, t.user_id
FROM stories_likes sl
LEFT JOIN users u ON sl.user_id = u.id
LEFT JOIN (SELECT * FROM stories_likes WHERE user_id = 4) t ON
sl.story_id = t.story_id
GROUP BY sl.story_id
Where the given user is declared on temporary table t
I want to join another table if a column in current table equals to a specific value. ( ENUM type )
for example, assume a status column which has 4 types of ( Public, OnlyMe, Friend, Group )
I want to join table groups only when status = Group
Something like this.
This is not mysql syntax.
SELECT * FROM posts WHERE ( status = Public OR status = OnlyMe OR status = Friend )
OR ( IF(status=Group) JOIN groups )
How can I do that in mysql?
Something like this (I made up several column names for your tables):
SELECT p.id, p.status, p.text, g.name
FROM posts p
LEFT OUTER JOIN groups g
ON (p.status = 'Group' AND p.groupId = g.id);
Using MySQL, I'd like to list all users that don't have the document "liaison". It could means Users that does not have any document at all, or users that have documents, but not "liaison" in these ones.
How can I do using MySQL Query ? I can't make it work!
Here's the (simple) model
Users (id, name)
Documents (id, user_id, name, path)
The NOT EXISTS is a workable solution. As an alternative, sometimes, with large sets, an "anti JOIN" operation can give better performance:
SELECT u.*
FROM Users u
LEFT
JOIN (SELECT d.user_id
FROM Documents d
WHERE d.name = 'liaison'
) l
ON l.user_id = u.id
WHERE l.user_id IS NULL
The inline view aliased as l returns us a list of user_id that have document named 'liaison'; that result set gets outer joined to the Users table, and then we exclude any rows where we found a match (the test of l.user_id IS NULL).
This returns a resultset equivalent to your query with the NOT EXISTS predicate.
Another alternative is to use a query with a NOT IN predicate. Note that we need to guarantee that the subquery does not return a NULL, so the general approach is to include an IS NOT NULL predicate on the column being returned by the subquery.
SELECT u.*
FROM Users u
WHERE u.id NOT IN
( SELECT d.user_id
FROM Documents d
WHERE d.user_id IS NOT NULL
AND d.name = 'liaison'
)
I'd write the NOT EXISTS query like this:
SELECT u.*
FROM Users u
WHERE NOT EXISTS
( SELECT 1
FROM Documents d
WHERE d.name = 'liaison'
AND d.user_id = u.id
)
My personal preference is to use a literal 1 in the SELECT list of that correlated subquery; it reminds me that the query is just looking for the existence of 1 row.)
Again, I usually find that the "anti-join" pattern gives the best performance with large sets. (You'd need to look at the EXPLAIN output for each statement, and measure the performance of each to determine which will work best in your situation.)
The correct query you are looking for is:
SELECT
*
FROM
Users
WHERE
id NOT IN (
SELECT
user_id
FROM
Documents
WHERE
name = "liaison"
)
This will achieve the exact result you are looking for. If a specific user has no documents, it will be listed. If it has many documents, and one of those is 'liaison', it won't be listed.
If you want to search for 'liaison' in your document's name, replace name = "liaison" for name LIKE "%liaison%".
It basically says: Select all users such as there are no documents with name "liaison" pointing to it.
So, I finally came up with this solution that seems to work good :
SELECT * FROM users u WHERE id NOT IN (SELECT DISTINCT user_id FROM user_documents WHERE name = 'LIAISON') ORDER BY c.lastname, c.firstname
SELECT users.*
FROM users left join Documents
on users.id = Documents.user_id
and documents.name='LIAISON'
WHERE documents.user_id is null
select * from Users where not exists (select id from Documents where Users.id = Documents.id and Documents.name = 'liaison')
Try :
SELECT DISTINCT u.*
FROM users u LEFT JOIN documents d ON d.user_id = u.id
WHERE d.id IS NULL OR d.name NOT LIKE '%liaison%'
Remove percent signs if "liaison" is the exact name of the document.
I usually go with the join approach but in this case I am a bit confused. I am not even sure that it is possible at all. I wonder if the following query can be converted to a left join query instead of the multiple select in used:
select
users.id, users.first_name, users.last_name, users.description, users.email
from users
where id in (
select assigned.id_user from assigned where id_project in (
select assigned.id_project from assigned where id_user = 1
)
)
or id in (
select projects.id_user from projects where projects.id in (
select assigned.id_project from assigned where id_user = 1
)
)
This query returns the correct result set. However, I guess the repetition of the query that selects assigned.id_project is a waste.
You could start with the project assignments of user 1 a1. Then find all assignments of other people to those projects a2, and the user in the project table p. The users you are looking for are then in either a2 or p. I added distinct to remove users who can be reached in both ways.
select distinct u.*
from assigned a1
left join
assigned a2
on a1.id_project = a2.id_project
left join
project p
on a1.id_project = p.id
join user u
on u.id = a2.id_user
or u.id = p.id_user
where a1.id_user = 1
Since both subqueries have a condition where assigned.id_user = 1, I start with that query. Let's call that assignment(s) the 'leading assignment'.
Then join the rest, using left joins for the 'optional' tables.
Use an inner join on user that matches either users of assignments linked to the leading assignment or users of projects linked to the leading project.
I use distinct, because I assumen you'd want each user once, event if they have an assignment and a project (or multiple projects).
select distinct
u.id, u.first_name, u.last_name, u.description, u.email
from
assigned a
left join assigned ap on ap.id_project = a.id_project
left join projects p on p.id = a.id_project
inner join users u on u.id = ap.id_user or u.id = p.id_user
where
a.id_user = 1
Here's an alternative way to get rid of the repetition:
SELECT
users.id,
users.first_name,
users.last_name,
users.description,
users.email
FROM users
WHERE id IN (
SELECT up.id_user
FROM (
SELECT id_user, id_project FROM assigned
UNION ALL
SELECT id_user, id FROM projects
) up
INNER JOIN assigned a
ON a.id_project = up.id_project
WHERE a.id_user = 1
)
;
That is, the assigned table's pairs of id_user, id_project are UNIONed with those of projects. The resulting set is then joined with the user_id = 1 projects to obtain the list of all users who share the projects with the ID 1 user. And now it only remains to retrieve the details for those users, which in this case is done in the same way as in your query, i.e. using an IN clause.
I'm sorry to say that I don't have MySQL to thoroughly test the performance of this query and so cannot be quite sure if it is in any way better or worse than your original query or than the one suggested both by #GolezTrol and by #Andomar. Generally I tend to agree with #GolezTrol's comment that a query with simple (semi- or whatever-) joins and repetitive parts might turn out more efficient than an equivalent sophisticated query that doesn't have repetitions. In the end, however, it is testing that must reveal the final answer for you.
I have a query that produces a result like this:
The data is sorted by date DESC, then by time DESC, but also the common 'markers_name' elements are chunked together. (They're only chunked together for each date).
To do this I get the list of MAX(time) values for every combination of (markers_id, date) in conditions, then join that list to the row set I am getting from the present query, and use the MAX(time) values for sorting:
SELECT
m.name AS markers_name,
c.time AS conditions_time,
c.date AS conditions_date,
s.name AS station_name
FROM markers m
INNER JOIN conditions c ON c.markers_id = m.id
INNER JOIN station s ON c.station_id = s.id
INNER JOIN (
SELECT
markers_id,
date,
MAX(time) AS time
FROM conditions
GROUP BY
markers_id,
date
) mx ON c.markers_id = mx.markers_id AND c.date = mx.date
ORDER BY
c.date DESC,
mx.time DESC,
m.name DESC,
c.time DESC
Now I need to restrict user access to some of the rows.
The conditions table has a 'private' column. If 'private' is set to 1 only some people are allowed to see the query row. The people that can see it include the person that created the conditions report and that person's contacts. The conditions table has a 'user_id', which contains the id of the person that created the conditions report. The contacts are obtained from a 'contacts' table which has two fields: 'user_id' and 'friend_id'. I have the user_id of the person requesting the information.
To restate another way, I need to do something like this:
Do the query above, but also check to see if c.private is set to one.
If c.private is set to one use c.user_id to get that user's 'friend_id's from the contacts table.
If the user_id of the person requesting the information matches c.user_id or any of c.user_id's friends return the query row. If not, don't return that row or return something that indicates that the row is private (I could have a markers_name with the name "Private" in the database, for example.
If anyone has any ideas I would love to hear them. I struggled with this all day yesterday. Thank you.
Possibly just adding a filter like this would be enough:
…
WHERE c.Private <> 1
OR c.user_id = #user_id
OR c.user_id IN (
SELECT friend_id
FROM contacts WHERE user_id = #user_id
)
where #user_id is the ID of the person requesting the info.
This would prohibit the private rows from appearing in the output where inappropriate.
If, however, you'd rather like them to be there but be marked as Private (for being dealt with later in the client or something like that), you could use that filter as a column in your SELECT clause:
SELECT
NOT (
c.Private <> 1
OR c.user_id = #user_id
OR c.user_id IN (
SELECT friend_id
FROM contacts WHERE user_id = #user_id
)
) AS IsPrivate,
…
FROM …
I don't have similar tables to play around with, so I'm writing this for you to try out, alright? Here is the WHERE clause for you to add to your query:
WHERE
-- all non-1 valued `private` columns are public, right? remove if not
c.private <> 1
-- all private but #user_id is entitled to view
OR (
c.private = 1
AND (
(c.user_id = #user_id) -- requestor is creator
OR (
EXISTS (
SELECT ct.friend_id
FROM contacts ct
WHERE ct.user_id = c.user_id
AND friend_id = #user_id
) -- requestor is a friend of the creator
)
)
)
Again, I wasn't able to test this.. I'll leave that up to you for now.. so please let me know how it goes. :)