Join another table with condition - mysql

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);

Related

How to achiave this:MYSQL SELECT if true select a else select b?

I want to select user.* if chat.chat_type IS NULL, but if chat.chat_type = 1
I want to select group.*
SELECT
CASE
WHEN chat.chat_type IS NULL THEN user.*
WHEN chat.chat_type =1 THEN group.*
END,
x,y
FROM `chat` LEFT JOIN group ...
How I can achieve this?
The database design as described is not so good, because you have an ID in your chat table that can either be a user ID or a group ID. Thus you cannot have a constraint (foreign key) on this column, which makes it possible to put any value in there, i.e. even an ID that doesn't exist.
Anyway, with the design given, to get either user or group you outer join both tables on the conditions you already mentioned. Then use COALESCE to see whether you got a user or a group in the result row.
select c.from, coalesce(u.name, g.name) as to_name
from chat c
left join usr u on c.chat_type is null and c.to = u.user_id
left join grp g on c.chat_type = 1 and c.to = g.group_id
where c.chat_type is null or c.chat_type = 1;
You have to use Union:
SELECT user.* from user join ... where chat.chat_type is null
Union
SELECT `Group`.* from `Group` join .... where chat.chat_type =1
if user and Group have a different number of column, you have to use a Default where you have less columns.
Hint: because Group is a Keyword, you should rename your table

mysql subquery as if condition ( only if needed )

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.

Find unique values that do not exist in multiple columns and tables

A misconfigured manual import imported our entire AD into our help desk user database, creating a bunch of extraneous/duplicate accounts. Of course, no backup to restore from.
To facilitate the cleanup, I want to run a query that will find users not currently linked to any current or archived tickets. I have three tables, USER, HD_TICKET, and HD_ARCHIVE_TICKET. I want to compare the ID field in USER to the OWNER_ID and SUBMITTER_ID fields in the other two tables, returning the only the values in USER.ID that do not exist in any of the other four columns.
How can this be accomplished?
Do a left join for each relationship where the right table id is null:
select user.*
from user
left join hd_ticket on user.id = hd_ticket.owner_id
left join hd_ticket as hd_ticket2 on user.id = hd_ticket2.submitter_id
left join hd_archive_ticket on user.id = hd_archive_ticket.owner_id
left join hd_archive_ticket as hd_archive_ticket2 on user.id = hd_archive_ticket2.submitter_id
where hd_ticket.owner_id is null
and hd_ticket2.submitter_id is null
and hd_archive_ticket.owner_id is null
and hd_archive_ticket2.submitter_id is null
How about something like:
SELECT id
FROM user
WHERE id NOT IN
(
SELECT owner_id
FROM hd_ticket
UNION ALL
SELECT submitter_id
FROM hd_ticket
UNION ALL
SELECT owner_id
FROM hd_archive_ticket
UNION ALL
SELECT submitter_id
FROM hd_archive_ticket
)
If I understood you situation I would do this:
SELECT a.id FROM user a, hd_ticket b, hd_archive_ticket c WHERE a.id != b.id AND a.id != c.id
You would want to try something like below. Inner query where I am doing Inner join with other 2 tables, will return only those user id which exist in all 3 tables. Then in your outer query I am just filtering out those ID's returned by inner query; since your goal is to get only those USER ID which is not present in other tables.
select ID
FROM USER
WHERE ID NOT IN
(
select u.ID
from user u
inner join HD_TICKET h on u.ID = h.OWNER_ID
inner join HD_ARCHIVE_TICKET ha on u.ID = ha.SUBMITTER_ID
)

mysql aggregate functions in query with two joins gives unexpected results

Given the following (very simplified) mysql table structure:
products
id
product_categories
id
product_id
status (integer)
product_tags
id
product_id
some_other_numeric_value
I am trying to find every product that has an association to a certain product_tag, and that a relation to at least one category whichs status-attribute is 1.
I tried the following query:
SELECT *
FROM `product` p
JOIN `product_categories` pc
ON p.`product_id` = pc.`product_id`
JOIN `product_tags` pt
ON p.`product_id` = pt.`product_id`
WHERE pt.`some_value` = 'some comparison value'
GROUP BY p.`product_id`
HAVING SUM( pc.`status` ) > 0
ORDER BY SUM( pt.`some_other_numeric_value` ) DESC
Now my problem is: The SUM(pt.some_other_numeric_value) returns unexpected values.
I realized that if the product in question has more then one relation to the product_categories table, then every relation to the product_tags table is counted as many timed as there are relations to the product_categories table!
For example: If product with id=1 has a relation to product_categories with ids = 2, 3 and 4, and a relation with the product_tags with ids 5 and 6 - then if I insert a GROUP_CONCAT(pt.id), then it does give 5,6,5,6,5,6 instead of the expected 5,6.
At first I suspected it was a problem with the join type (left join, right join, inner join, and so on), so I tried every join type that I know of, but to no avail. I also tried to include more id-fields into the GROUP BY clause, but this didnĀ“t solve the problem either.
Can somebody explain to me what is actually going wrong here?
You join a "main" (product) table to two tables (tags and categories) via 1:n relationships, so this is expected, you are creating a mini cartesian product. For those products that have both more than one associated tags and more than one associated categories, multiple rows are created in the result set. If you Group By, you have wrong results in aggregate functions.
One way to avoid this is to remove one of the two joins, which is a valid startegy if you don't need results from that table. Say you don't need anything in the SELECT list from the product_categories table. Then you can use a semi-join (the EXISTS subquery)to that table:
SELECT p.*,
SUM( pt.`some_other_numeric_value` )
FROM `product` p
JOIN `product_tags` pt
ON p.`product_id` = pt.`product_id`
WHERE pt.`some_value` = 'some comparison value'
AND EXISTS
( SELECT *
FROM product_categories pc
WHERE pc.product_id = pc.product_id
AND pc.status = 1
)
GROUP BY p.`product_id`
ORDER BY SUM( pt.`some_other_numeric_value` ) DESC ;
Another way to circumvent this problem is - after the GROUP BY MainTable.pk - to use DISTINCT inside the COUNT() or GROUP_CONCAT() aggregate functions. This works but you can't use it with SUM(). So, it's not useful in your specific query.
A third option - which works always - is to first group by the two (or more) side tables and then join to the main table. Something like this in your case:
SELECT p.* ,
COALESCE(pt.sum_other_values, 0) AS sum_other_values
COALESCE(pt.cnt, 0) AS tags_count,
COALESCE(pc.cnt, 0) AS categories_count,
COALESCE(category_titles, '') AS category_titles
FROM `product` p
JOIN
( SELECT product_id
, COUNT(*) AS cnt
, GROUP_CONCAT(title) AS category_titles
FROM `product_categories` pc
WHERE status = 1
GROUP BY product_id
) AS pc
ON p.`product_id` = pc.`product_id`
JOIN
( SELECT product_id
, COUNT(*) AS cnt
, SUM(some_other_numeric_value) AS sum_other_values
FROM `product_tags` pt
WHERE some_value = 'some comparison value'
GROUP BY product_id
) AS pt
ON p.`product_id` = pt.`product_id`
ORDER BY sum_other_values DESC ;
The COALESCE() are not strictly needed there - just in case you chnage the inner joins to LEFT outer joins.
you cant order by a sum function
instead you could do it like that
SELECT * ,SUM( pt.`some_other_numeric_value` ) as sumvalues
FROM `product` p
JOIN `product_categories` pc
ON p.`product_id` = pc.`product_id`
JOIN `product_tags` pt
ON p.`product_id` = pt.`product_id`
WHERE pt.`some_value` = 'some comparison value'
GROUP BY p.`product_id`
HAVING SUM( pc.`status` ) > 0
ORDER BY sumvalues DESC

Join mysql tables twice on 2 columns = 1 column

I have a database that contains messages. The messages are stored in one table, the user information is stored in another. In the message table, there is an author_id column which represents the user_id of the author from the user table, there are all the message columns, and there is a to_address which represents a concatenation of "u_" + user_id from the user table. Is there any that I can join these two tables, so that it display the username instead of ID in BOTH the author_id AND to_address.
I've tried
SELECT username, ..., username
FROM msgs
INNER JOIN users
ON user_id=author_id AND concat("u_",user_id)=to_address;
with obvious error
I've tried using subqueries such as
SELECT
( SELECT username
FROM users
INNER JOIN msgs
ON user_id=author_id
) AS "From",
( SELECT username
FROM users
INNER JOIN msgs
ON CONCAT("u_",user_id)=to_address
) AS "To",
( SELECT timestamp(message_time) FROM msgs
) AS "Sent",
( SELECT message_subject FROM msgs
) AS "Subject",
( SELECT message_text AS "Message" FROM msgs
) AS "Message"
and got "Subquery returns more than 1 row". Is there any way that I can do this successfully?
It sounds like you want something like this:
SELECT
from_user.username AS "From",
to_user.username AS "To",
timestamp(msgs.message_time) AS "Sent",
msgs.message_subject AS "Subject",
msgs.message_text AS "Message"
FROM msgs
INNER JOIN users AS from_user
ON msgs.author_id = from_user.user_id
INNER JOIN users AS to_user
ON msgs.to_address = CONCAT("u_", to_user.user_id);
Basically, you join the users table to the msgs table twice, giving each instance of the table a different name and a different join condition. Then you can pick a specific column out of a specific instance of the users table.
I think you want to do something like
SELECT msgs.*,
authors.whatever,
addresses.to_address
FROM msgs
JOIN users AS authors ON msgs.author_id = authors.id
JOIN users AS addresses ON msgs.address_id = addresses.id
My query is perhaps imprecise but you can probably see what I'm doing here.
As an aside, I would recommend not abbreviating msgs and using singular table names.
You need two joins as you want to get two separate users:
select f.username, t.username
from msgs m
inner join users f on f.user_id = m.author_id
inner join users t on concat("u_", t.user_id) = m.to_address
This will return the username associated with both the "author_id" and the "to_address", using correlated subqueries, instead of using JOIN. (Using a JOIN is the usual approach, but an approach using a correlated subquery gives you some additional flexibility.
SELECT (SELECT u.username
FROM users u
ON u.user_id = CONCAT("u_",u.user_id) = m.to_address
ORDER BY u.username LIMIT 1
) AS to_username
, (SELECT a.username
FROM users a
ON a.user_id = m.author_id
ORDER BY a.username LIMIT 1
) AS author_username
, m.*
FROM msgs m
NOTE: this differs a bit from an INNER JOIN in that this will return a row from msg when a matching username is not found for the to_address or the author_id.)
NOTE: this assumes that user_id is unique in the users table.
NOTE: if the username column is NOT NULL in the users table, then you can emulate the INNER JOIN, and NOT return a row if a matching username is not found for the author_id or to_address by adding
HAVING to_username IS NOT NULL
AND author_username IS NOT NULL