MySQL UNION DISTINCT - exclude - mysql

I have query like this:
SELECT cs_event.*, cs_file.name, cs_file.extension, cs_user.first_name, cs_user.last_name
FROM cs_event
LEFT JOIN cs_file ON cs_event.idfile = cs_file.idfile
LEFT JOIN cs_user ON cs_event.iduser = cs_user.iduser
WHERE type != 51
AND idportal = 1
UNION DISTINCT
SELECT cs_event.*, cs_file.name, cs_file.extension, cs_user.first_name, cs_user.last_name
FROM cs_event
LEFT JOIN cs_file ON cs_event.idfile = cs_file.idfile
LEFT JOIN cs_user ON cs_event.iduser = cs_user.iduser
WHERE shared_with_users LIKE '%i:2;%'
AND idportal = 1
ORDER BY add_date DESC
LIMIT 6
The problem is following:
Regular user can't see certain types of events (for now it is type 51) and he can see only things which are shared with him.
shared_with_users column can be null or have value - this column have value only for one type of event (type = 50) and for other events it is null.
I need to perform following:
User can access all events except event with type 51 and if the the event is type of 50, I need to check if the event is shared with him (shared_with_users column), and collect that also. Is it possible to make this kind of query?

Try this
SELECT cs_event.*, cs_file.name, cs_file.extension, cs_user.first_name, cs_user.last_name
FROM cs_event
LEFT JOIN cs_file ON cs_event.idfile = cs_file.idfile
LEFT JOIN cs_user ON cs_event.iduser = cs_user.iduser
WHERE type != 51 o or (type = 50 and shared_with_users LIKE '%i:2;%')
AND idportal = 1
ORDER BY add_date DESC
LIMIT 6

I think you can do this as a single query, with logic in the WHERE clause:
SELECT e.*, f.name, f.extension, u.first_name, u.last_name
FROM cs_event e LEFT JOIN
cs_file f
ON e.idfile = f.idfile LEFT JOIN
cs_user u
ON e.iduser = u.iduser
WHERE idportal = 1 AND
(type <> 51 OR shared_with_users LIKE '%i:2;%');
Some notes:
I don't think the LEFT JOINs are necessary. The WHERE clause may be turning them into inner joins anyway, but it is hard to tell without qualified column names.
I added table aliases so the query is easier to write and to read.
The logic for shared_with_users suggests that you have stored a list of values in a string. That is a bad choice.

Related

Incorrect SUM when using two LEFT JOINs and a GROUP BY

The following code returns an incorrect value for the sumHours field. It appears to prepare the sumHours field then once the GROUP BY runs, sum the sums together.
SELECT mmr_ID, mmr_projectName, SUM(mmr_hoursWorked.mmr_hoursWorked_hours) AS sumHours
FROM mmr
LEFT JOIN mmr_hoursWorked
ON mmr.mmr_ID = mmr_hoursWorked.mmr_hoursWorked_project AND mmr_hoursWorked.mmr_hoursWorked_mm = "P90826"
LEFT JOIN mmr_notes
ON mmr.mmr_ID = mmr_notes.mmr_notes_MMR_ref AND mmr_notes.mmr_notes_author = "P90826"
WHERE mmr_mmAssigned = "P90826" AND mmr_projectStatus != 1 OR mmr_notes.mmr_notes_author = "P90826" AND mmr_projectStatus != 1
GROUP BY mmr_ID
Actual Results
mmr_ID - 35
mmr_projectName - Project A
sumHours - 140.2
Expected Results
mmr_ID - 35
mmr_projectName - Project A
sumHours - 35.05
Due to JOIN statements combination of results are returned, so you should handle aggregates and joins separately. Try this:
SELECT t.*
FROM
(
SELECT mmr_ID, mmr_projectName, SUM(mmr_hoursWorked.mmr_hoursWorked_hours) AS sumHours
FROM mmr
LEFT JOIN mmr_hoursWorked
ON mmr.mmr_ID = mmr_hoursWorked.mmr_hoursWorked_project AND mmr_hoursWorked.mmr_hoursWorked_mm = 'P90826'
WHERE mmr_projectStatus != 1 AND mmr_mmAssigned = 'P90826'
GROUP BY mmr_ID, mmr_projectName, mmr_mmAssigned
) t
LEFT JOIN mmr_notes
ON t.mmr_ID = mmr_notes.mmr_notes_MMR_ref
WHERE mmr_notes.mmr_notes_author = 'P90826';
The issue was corrected by normalizing the database. The mmr_notes table was integrated into the mmr_hoursWorked table since it only had one unique field.

Search a table based on multiple rows in another table

Basically I have three MySQL tables:
Users - contains base information on users
Fields - describes additional fields for said users (e.g. location, dob etc.)
Data - Contains user data described via links to the fields table
With the basic design as follows (the below is a stripped down version)
Users:
ID | username | password | email | registered_date
Fields
ID | name | type
Data:
ID | User_ID | Field_ID | value
what I want to do is search Users by the values for the fields they have, e.g. example fields might be:
Full Name
Town/City
Postcode
etc.
I've got the following, which works when you're only wanting to search by one field:
SELECT `users`.`ID`,
`users`.`username`,
`users`.`email`,
`data`.`value`,
`fields`.`name`
FROM `users`,
`fields`,
`data`
WHERE `data`.`Field_ID` = '2'
AND `data`.`value` LIKE 'london'
AND `users`.`ID` = `data`.`User_ID`
AND `data`.`Field_ID` = `fields`.`ID`
GROUP BY `users`.`ID`
But what about if you want to search for Multiple fields? e.g. say I want to search for Full Name "Joe Bloggs" With Town/City set to "London"? This is the real sticking point for me.
Is something like this possible with MySQL?
I'm going with the assumption that "searching multiple fields" is talking about the Entity-Attribute-Value structure.
In that case, I propose that the first step is to create a derived query - basically, we want to limit the "EAV data joined" to only include the records that have the values we are interested in finding. (I've altered some column names, but the same premise holds.)
SELECT d.userId
FROM data d
JOIN fields f
ON f.fieldId = d.fieldId
-- now that we establish data/field relation, filter rows
WHERE f.type = "location" AND d.value = "london"
OR f.type = "job" AND d.value = "programmer"
This resulting rows are derived from the filtered EAV triplets that match our conditions. Only the userId is selected in this case (as it will be used to join against the user relation), but it is also possible to push fieldId/value/etc through.
Then we can use all of this as a derived query:
SELECT *
FROM users u
JOIN (
-- look, just goes in here :)
SELECT DISTINCT d.userId
FROM data d
JOIN fields f
ON f.fieldId = d.fieldId
WHERE f.type = "location" AND d.value = "london"
OR f.type = "job" AND d.value = "programmer"
) AS e
ON e.userId = u.userId
Notes:
The query planner will figure all the RA stuff out peachy keen; don't worry about this "nesting" as there is no dependent subquery.
I avoid the use of implicit cross-joins as I feel they muddle most queries, this case being a particularly good example.
I've "cheated" and added a DISTINCT to the derived query. This will ensure that at most one record will be joined/returned per user and avoids the use of GROUP BY.
While the above gets "OR" semantics well (it's both easier and I may have misread the question), modifications are required to get "AND" semantics. Here are some ways that the derived query can be written to get such. (And at this point I must apologize to Tony - I forget that I've already done all the plumbing to generate such queries trivially in my environment.)
Count the number of matches to ensure that all rows match. This will only work if each entity is unique per user. It also eliminates the need for DISTINCT to maintain correct multiplicity.
SELECT d.userId
FROM data d
JOIN fields f
ON f.fieldId = d.fieldId
-- now that we establish data/field relation, filter rows
WHERE f.type = "location" AND d.value = "london"
OR f.type = "job" AND d.value = "programmer"
GROUP BY d.userId
HAVING COUNT(*) = 2
Find the intersecting matches:
SELECT d.userId
FROM data d
JOIN fields f ON f.fieldId = d.fieldId
WHERE f.type = "location" AND d.value = "london"
INTERSECT
SELECT d.userId
FROM data d
JOIN fields f ON f.fieldId = d.fieldId
WHERE f.type = "job" AND d.value = "programmer"
Using JOINS (see Tony's answer).
SELECT d1.userId
FROM data d1
JOIN data d2 ON d2.userId = d1.userId
JOIN fields f1 ON f1.fieldId = d1.fieldId
JOIN fields f2 ON f2.fieldId = d2.fieldId
-- requires AND here across row
WHERE f1.type = "location" AND d1.value = "london"
AND f2.type = "job" AND d2.value = "programmer"
An inner JOIN itself provides conjunction semantics when applied outside of the condition. In this case I show "re-normalize" the data. This can also be written such that [sub-]selects appear in the select clause.
SELECT userId
FROM (
-- renormalize, many SO questions on this
SELECT q1.userId, q1.value as location, q2.value as job
FROM (SELECT d.userId, d.value
FROM data d
JOIN fields f ON f.fieldId = d.fieldId
WHERE f.type = "location") AS q1
JOIN (SELECT d.userId, d.value
FROM data d
JOIN fields f ON f.fieldId = d.fieldId
WHERE f.type = "job") AS q2
ON q1.userId = q2.userId
) AS q
WHERE location = "london"
AND job = "programmer"
The above duplicity is relatively easy to generate via code and some databases (such as SQL Server) support CTEs which make writing such much simpler. YMMV.
If I understood you right, this is what you want:
FROM `users`,
`fields`,
`data` `location`
`data` `name`
WHERE `location`.`Field_ID` = '2'
AND `location`.`value` LIKE 'london'
AND `location`.`Field_ID` = `fields`.`ID`
AND `name`.`Field_ID` = 'whathere? something for its name'
AND `name`.`value` LIKE 'london'
AND `name`.`Field_ID` = `fields`.`ID`
AND `users`.`ID` = `data`.`User_ID`
I'd prefer joins though
Well here you hit one of the downsides of the EAV you are using
SELECT u.ID, u.username,u.email, d1.value, f1.Name, d2.Value, f2.name
FROM `users` u,
inner join data d1 On d1.User_id = u.id
inner join data d2 On d2.User_id = u.id
inner join fields f1 on f1.id = d1.field_id
inner join fields f2 on f2.id = d2.field_id
WHERE d1.Field_id = '2' and d1.Value = 'london'
and d2.field_id = '??' and d2.value = 'Joe Bloggs'
GROUP BY `users`.`ID`
Messy isn't it? Bet you can't wait to go for, four or five values. Or think about (Forename = Joe Or surname = Bloggs) and City = London...

MySQL - Multirow Sum without Subquery

I currently have this working using a Sub-query, but as the DB grows this will become HUGELY inefficient. I'm wondering if there is a more efficient way to do what I need to do without sub-queries?
I need to have my final output look like so:
Question, Answer, Responses, Charts included in Response Count
Did this work?, N/A, 26, 30
Did this work?, Yes, 4, 30
This is my current query:
SELECT
bq_text,
ba_a,
bq_id,
COUNT(ba_a) AS ba_aC,
(SELECT COUNT(*) FROM board_done_sheet WHERE sd_b_id = bs.bs_id AND sd_sub = 1) AS sd_chartnumC
FROM board_done_sheet AS sh
LEFT JOIN board_done bd
ON (bd.bd_id = sh.sd_bd_id)
LEFT JOIN boardsubs bs
ON (bd.bd_b_id = bs.bs_id)
LEFT JOIN b_q_answers ba
ON (sh.sd_s_id = ba.ba_s_id)
LEFT JOIN bsquestions bq
ON (bq.bq_id = ba.ba_q_id)
LEFT JOIN multiples m
ON (ba.ba_m_id = m.m_id)
LEFT JOIN users u
ON (u.us_id = bd.bd_d_id)
LEFT JOIN profiles p
ON (p.p_u_id = bd.bd_d_id)
LEFT JOIN users rev
ON (rev.us_id = bd.bd_rev)
WHERE sd_sub = '1' AND bq_text <> 'Date' AND bq_id = 380
GROUP BY bs_id, bq_text, ba_a
That works perfectly, the problem is it has to use sub-queries which as time goes by will get less efficient.
I'm just wondering if there is a better more efficient way to do that summed field without it.
Presumably the subquery you're concerned about is the one in your toplevel SELECT.
That is easy to refactor so it won't get repeated.
Just JOIN it to the rest of the table. You'll want this sort of thing:
SELECT
bq_text, ...
COUNT(ba_a) AS ba_aC,
countup.countup AS sd_chartnumC
FROM board_done_sheet AS sh
LEFT JOIN board_done bd
ON (bd.bd_id = sh.sd_bd_id)
...
LEFT JOIN users rev
ON (rev.us_id = bd.bd_rev)
JOIN (
SELECT COUNT(*) AS countup , sd_b_id
FROM board_done_sheet
WHERE sd_sub = 1
GROUP BY sd_b_id
) AS countup ON countup.sd_b_id = bs.bs_id
WHERE sd_sub = '1'
AND bq_text <> 'Date'
AND bq_id = 380
GROUP BY bs_id, bq_text, ba_a
The countup subquery generates a summary table of counts and ids, and then joins it to the other tables.
A JOIN cascade of this complexity may become inefficient for other reasons as your table grows if you don't structure your indexes correctly.

MySQL do INNER JOIN if specific value is 1

I have this query:
SELECT
e.*, u.name AS event_creator_name
FROM `edu_events` e
LEFT JOIN `edu_users` u
ON u.user_id = e.event_creator
INNER JOIN `edu_event_participants`
ON participant_event = e.event_id && participant_user = 1
WHERE
MONTH(e.event_date_start) = 6
AND YEAR(e.event_date_start) = 2013
It works perfect, however, I only want to do the INNER JOIN if the value: e.event_type equals 1. If not, it should ignore the INNER JOIN.
I have tried for some time to figure it out, but the solutions seems difficult to implment for my proposes (as it is only for select/specific values).
I'm thinking about something like:
SELECT
e.*, u.name AS event_creator_name
FROM `edu_events` e
LEFT JOIN `edu_users` u ON u.user_id = e.event_creator
if(e.event_type == 1) {
INNER JOIN `edu_event_participants` ON participant_event = e.event_id && participant_user = 1
}
WHERE MONTH(e.event_date_start) = 6
AND YEAR(e.event_date_start) = 2013
I have edited the below following further feedback from #Matthias
-- This will get all events for a given user plus all globals
SELECT
e.*,
u.name AS event_creator_name
FROM `edu_users` u
-- in the events
INNER JOIN `edu_events` e 
ON (
-- Get all the ones that the user is participant
e.event_creator = u.user_id
-- Or where event_type is 1
OR
e.event_type = 1
)
AND e.event_date_start BETWEEN DATE('2013-06-01') AND DATE('2013-07-01')
    
-- Add in event participants even though it doesn't seem to be used?
INNER JOIN `edu_event_participants` AS eep
ON eep.participant_event = e.event_id
AND eep.participant_user = 1
    
-- Add the user ID into the WHERE
WHERE u.user_id = 1;
This just might not make too much sence as it feels as though edu_event_participants has too much information in. event_creator should really be stored against the event itself, and then event_participants just containing an event id, user id, and user type.
If you are looking to get all users on an event, it may be better to do a seperate query for that event to select all users based off an event_id
The note on your use of MONTH() and YEAR(). This will trigger a table scan, as MySQL will need to apply the MONTH() and YEAR() functions to all rows to determine which match that WHERE statement. If you instead calculate the upper and lower limits (i.e. 2013-06-01 00:00:00 <= e.event_date_start < 2013-07-01 00:00:00) then MySQL can use a far more efficient range scan on an index (assuming one exists on e.event_date_start)
If I understand correctly you only want the results where there is an entry on edu_event_participants with the same event_id and participant_user = 1 but only if event_type = 1, but you don't really want to get any information from the edu_event_participants table. If that is the case:
SELECT
e.*, u.name AS event_creator_name
FROM `edu_events` e
LEFT JOIN `edu_users` u
ON u.user_id = e.event_creator
WHERE
-- as Simon at mso.net suggested
WHERE e.event_date_start BETWEEN DATE('2013-06-01') AND DATE('2013-07-01')
-- MONTH(e.event_date_start) = 6
-- AND YEAR(e.event_date_start) = 2013
AND (
-- either event is public
e.event_type = 1 or
-- or the user is in the participants table
exists
(select 1 from `edu_event_participants`
where participant_event = e.event_id
AND participant_user = 1)
)
Maybe what you're after is displaying the left table value even if there's no matching data from right table? On that case you can use outer join like so:
LEFT OUTER JOIN `edu_event_participants` ON participant_event = e.event_id && participant_user = 1 AND e.event_type = 1

Trying to building optimised Query for Group and Subgroup for a user

i am trying to write the Query for three things .My table structure is like that
You can see Schema at http://sqlfiddle.com/#!2/56c2d/1
I am trying to write the query in MYSQL
user:- table
user_id
user_fname
This is User tabke which will save User Information
group:- "group" and "subgroup" is maintain in same table using column "group_parent_group_id"
group_id
group_title
group_parent_group_id(INT)
This is group table which will save Group and Subgroups
user_group: table
user_group_id
user_group_user_id
user_group_group_id
This ill store both User and Group relation using their Id
I am trying to write the Query for three things. Fetching Users Groups, Subgroups
1) Query to fetch list of All Groups for User Register. Query is gelow and is giving error
Query:
select user.id, user.user_fname, group.group_id, group.group_title
from `user`
inner join user_group on user_group.user_group_user_id = user.user_id
inner join group on group.group_id = user_group.user_group_group_id
where user_group.user_group_user_id = 1 and user_group.group_parent_group_id = 0
2) I am Looking the query to fetch all subgroups(For Whom user is already Register) for Group Id 1,2 or 1
3) I am Looking the query to fetch all subgroups(For Whom user is Not Register yet) for Group Id 1,2 or 1. Ideal is for giving him randomly suggest a subgroup to add
Please Help. I am a newbie in DB :(
Your query is probably failing as you have a table called group, which is a reserved word. You can use back tics to delimit the name to get away with this (as follows) but it would be a better idea to change the table name.
SELECT user.id, user.user_fname, `group`.group_id, `group`.group_title
FROM `user`
INNER JOIN user_group ON user_group.user_group_user_id = user.user_id
INNER JOIN `group` ON `group`.group_id = user_group.user_group_group_id
WHERE user_group.user_group_user_id = 1
AND user_group.group_parent_group_id = 0
EDIT updated for queries I think the OP requires.
First query will get a list of all the groups (ones that have no parent group id) that a user (in this case id 28) is a member of
SELECT y2m_user.user_id, y2m_user.user_first_name, y2m_group.group_id, y2m_group.group_title
FROM y2m_user
INNER JOIN y2m_user_group ON y2m_user_group.user_group_user_id = y2m_user.user_id
INNER JOIN y2m_group ON y2m_group.group_id = y2m_user_group.user_group_group_id
WHERE y2m_user.user_id = 28
AND y2m_group.group_parent_group_id = 0
This query will get a list of all the sub groups (ones where the parent group id is greater than 0) that a user (in this case id 28) is a member of
SELECT y2m_user.user_id, y2m_user.user_first_name, y2m_group.group_id, y2m_group.group_title
FROM y2m_user
INNER JOIN y2m_user_group ON y2m_user_group.user_group_user_id = y2m_user.user_id
INNER JOIN y2m_group ON y2m_group.group_id = y2m_user_group.user_group_group_id
WHERE y2m_user.user_id = 28
AND y2m_group.group_parent_group_id > 0
This query will get a list of all the sub groups (ones where the parent group id is greater than 0) that a user (in this case id 28) is NOT a member of
SELECT y2m_user.user_id, y2m_user.user_first_name, y2m_group.group_id, y2m_group.group_title
FROM y2m_user
CROSS JOIN y2m_group
LEFT OUTER JOIN y2m_user_group ON y2m_user_group.user_group_user_id = y2m_user.user_id AND y2m_group.group_id = y2m_user_group.user_group_group_id
WHERE y2m_user.user_id = 28
AND y2m_group.group_parent_group_id > 0
AND y2m_user_group.user_group_id IS NULL
Please excuse any typos as not tested (with your test data there are no sub groups).