I have the following tables, USER and RECORDS. The USER table simply has the name of the user and the group they have access to. If a user has null as their group (such as BOB does), that means they are a super user and have access to all the groups.
USER:
username | group
---------|--------
Bob | (null)
Kevin | 1
John | 2
Mary | 1
I then have a RECORDS table that has a list of records:
record | field_name | value
----------------------------
1 AGE 92
1 HEIGHT 9'2
1 __GROUP__ 1
2 AGE 11
2 HEIGHT 1'1
2 __GROUP__ 2
3 AGE 68
3 HEIGHT 6'8
This is not my design but it is what I have to query on. In the RECORDS table, the group that a record belongs to is indicated by the __GROUP__ value in the field_name column. So what I am trying to do is get a list of all the records each user has access to.
So based on the user table:
BOB has access to all the records
KEVIN and MARY only have access to records who have a field_name =
'__ GROUP __' and value = 1
JOHN only have access to records who have a field_name = '__ GROUP __'
and value = 2
This means
BOB has access to records 1, 2, and 3.
Kevin has access to only record 1
Mary has access to only record 1
John only has access to record 2
If the GROUP is missing from a record that means only a super user ( a user with group = null) can access it.
I understand if in the RECORDS table it would have a "GROUP" column this would be easier, but unfortunately, this isn't my database and I cannot make changes to the structure.
**Sorry, I forgot to mention it is possible for a user to be in multiple groups! For instance, Mary could be in groups 1 and 2.
One option uses exists:
select u.*, r.*
from user u
cross join records r
where u.grp is null or exists (
select 1
from records r1
where
r1.ecord = r.record
and r1.field_name = '__GROUP__'
and r1.value = u.grp
)
This gives you the entire records rows that each user has access to.
If you just want the list of record ids, you can use aggregation instead:
select u.userame, r.record
from users u
cross join records r
group by u.userame, u.grp, r.record
having
u.grp is null
or max(r.field_name = '__GROUP__' and r.value = u.grp) = 1
Related
I have 3 MYSQL tables, one with members information (with ids), one with subscription years (also with ids), and a join table to save every year each member have been subscribed (using years and members ids). I managed to get the list of all the members related to a specific year using the MYSQL code below, but I can't figure out how to get the list of members that are not related to the same years (the ones not included on the first list) using MYSQL code.
I already tried to search for the members without the specific year id, using the code below, but this will return a list with all the members except for the ones that were subscribed only on that year.
The code to extract all the member subscribed on a year knowing its year id (x):
SELECT DISTINCT m.id, first_name, last_name
FROM members_years sc
INNER JOIN members m ON m.id = sc.member_id
WHERE sc.year_id = x
While the nonworking code to extract the list of members not related with a subscription year knowing its year id (x):
SELECT DISTINCT m.id, first_name, last_name
FROM members_years sc
INNER JOIN members m ON m.id = sc.member_id
WHERE sc.year_id != x
To resume I need to extract using a single MYSQL code the list of all members that are not related with a specific subscription year.
Here an example of tables:
Members:
Id | First_name | Last_name
——————————————————
1 | John | Smith
——————————————————
2 | John | Doe
——————————————————
3 | Jane | Doe
Years:
Id | Year
——————
1 | 2013
——————
2 | 2014
——————
3 | 2015
Members_years:
Id | member_id | year_id
———————————————
1 | 1 | 1
———————————————
2 | 1 | 2
———————————————
3 | 2 | 3
———————————————
4 | 3 | 1
———————————————
5 | 3 | 2
———————————————
6 | 3 | 3
With the 3 previous tables, searching with x=3 (2015) with the first code we will get John Doe and Jane Doe, that is fine.
But with second code, using x=3, we get John Smith and Jane Doe instead of only Jonh Smith.
One way to do what you want is with a left join. You want to find all rows which don't have a particular relation for a year x
SELECT m.id, first_name, last_name
FROM members m
LEFT JOIN members_years sc ON (m.id = sc.member_id and sc.year_id = x)
WHERE sc.year_id IS NULL;
So, you LEFT JOIN members_years looking for rows with your target year for each member in the ON clause.
Any member which doesn't have that year would have NULL values from that join, so the WHERE clause looks for those and we end up with just the members you need.
If you're unfamiliar with LEFT JOIN, you might find it instructive to leave off the WHERE clause and include some columns from sc:
SELECT m.id, first_name, last_name, sc.year_id
FROM members m
LEFT JOIN members_years sc ON (m.id = sc.member_id and sc.year_id = x);
You'll get a row for every member - some with a value for year_id, some with NULL.
I think #IVO GELOV’s comment contains the code you are looking for. The reason you are seeing John Doe and Jane Doe pop up in the results of your second query is because both of those users have a record on members_years where year_id Is Not equal to 3. (Although John Doe doesn’t actually have a record where year_id Is Not equal to 3 in your example data, I am assuming that on the actual data you are querying against this is true). Your query needs to instead determine what users DO have a record where year_id Is Equal to 3, and then return every other user besides the users that meet this criteria in your search result.
I am working in MySQL. I have the following query:
SELECT tar.ID, COUNT(tsr.StudentID) AS Students
FROM Teacher_ApplicationRecord tar
LEFT JOIN Teacher_StudentRecord tsr ON sar.ID = tsr.TeacherID AND tsr.Session = 1 AND tsr.Year = 2017
WHERE tar.ApplicationYear = 2017
AND tar.Session1 = 1
This query returns no results. However if I take the COUNT(tsr.StudentID) out of the SELECT statement, it returns all the teacher ID's with NULL for the tsr table.
What I want is a query that returns all the teacher ID's and a count of the students assigned to that teacher, with 0 if the result is NULL.
I have tried COALESCE(COUNT(tsr.StudentID), 0) AND IFNULL(COUNT(tsr.StudentID), 0) with no success so far. Any other thoughts?
UPDATE:
The tsr table has 4 columns: TeacherID, StudentID, Year, Session. It has no records yet. It will be populated next year when students are assigned to teachers.
The tar table has a list of TeacherID's in it with some other data, such as year and faculty.
I want my results to look like below:
+-----------+-----------------+
| TeacherID | COUNT(StudentID)|
+-----------+-----------------+
| 1 | 0 |
+-----------+-----------------+
| 2 | 0 |
+-----------+-----------------+
etc.
As students are assigned to teachers, the COUNT(StudentID) numbers will go up. Hope this helps.
UPDATE 2
The tar table looks like this:
+---------+---------------+
|TeacherID|ApplicationYear|
+---------+---------------+
| 1 | 2017 |
+---------+---------------+
| 2 | 2017 |
+---------+---------------+
| 3 | 2017 |
+---------+---------------+
It has other columns but they are not relevant to the question.
The tsr table looks like this:
+---------+---------+----+-------+
|TeacherID|StudentID|Year|Session|
+---------+---------+----+-------+
| 1 | 10 |2017| 1 |
+---------+---------+----+-------+
| 1 | 11 |2017| 1 |
+---------+---------+----+-------+
| 2 | 12 |2017| 1 |
You can try joining the teacher table to the student table (in that order), and then using GROUP BY to count the number of students per teacher:
SELECT tsr.ID,
COUNT(sar.ID) AS numOfStudents -- count no matching students as zero
FROM Teacher_StudentRecord tsr
LEFT JOIN Student_ApplicationRecord sar
ON tsr.TeacherID = sar.ID AND
tsr.Session = 1 AND
tsr.Year = 2017
GROUP BY tsr.ID
The usefulness of a LEFT JOIN here is the edge case where a teacher has no matching students. In this case, the result set, before aggregation happens, would have a single record for that teacher, and the student ID value would be NULL, which be ignored by COUNT, resulting in a correct zero count.
Note that I removed the WHERE clause, whose intended logic is already contained in the ON clause. This WHERE clause was throwing off your results by removing teacher records before they could even be aggregated.
Update:
Please try the following query to see if you get results:
SELECT tsr.ID,
COUNT(sar.ID) AS numOfStudents
FROM Teacher_StudentRecord tsr
LEFT JOIN Student_ApplicationRecord sar
ON tsr.TeacherID = sar.ID
GROUP BY tsr.ID
If this gives you no results, then no teachers are actually connected to any students, and your data has a problem.
Here is a demo using your sample data. It works as expected:
SQLFiddle
I would like to create a sql query which create a column that doesn't exists in the db tables and gets fills on whether a row exists or not exists in a specific table.
For example:
I have 3 tables:
Users (For users list) - UID, UName
Locations (List of all available locations) - LID, LName
UsersLocations (All the locations the users have checked into) - UserID, LocationID
I need a sql query that from a user id get me a table of all the locations with a column that says whether the user has been in this location or not.
Example for Users table
UID | UName
1 John
4 Amy
5 Dann
Example for Locations table:
LID | LName
1 London
2 Barcelona
3 Paris
4 New York
Example for UsersLocations table:
UserID | LocationID
5 1
5 2
Example for output (for userid = 5):
User ID | Location | Was Here
5 London true
5 Barcelona true
5 Paris false
5 New York false
The output needs to include all the Locations from the locations table.
Also the UsersLocations table only contains the userID of the users that checked into that location.
Hmmm. One method is a correlated subquery:
select u.userid, l.location,
(case when exists (select 1 from userLocations where ul.userid = u.userid and ul.lid = l.locationid)
then 'true'
else 'false'
end) as WasHere
from location l cross join
(select 5 as userid) u;
The only part that is database-specific is the subquery for u.
I want to get a list of all my users and mark the ones that also appear in another table named memo (based on their unique identifiers) if a certain row value is 16. Each row of the mp table has a worker and a memo. The worker is the unique user id. So a user is in a memo (say 16) if u.keyid = mp.worker and mp.memo = 16. This is the query I wrote.
SELECT DISTINCT
u.name AS name,
IF ((mp.worker = u.keyid AND mp.memo = 16),1,0) AS isin
FROM users AS u, mp;
So isin should be 1 if there is an entry in table mp such that mp.worker = u.keyid for the memo 16.
However this query gets me a complete list of users with all 0 And all the users (again) that were in the memos with a 1. So say The users are John, Michael, Sarah and Jane. Say only John and Jane were in the meeting I get this result:
John 0
Micahel 0
Sarah 0
Jane 0
John 1
Jane 1
But what I want is:
John 1
Micahel 0
Sarah 0
Jane 1
How should I write it to get what I want?
Your query is doing a CROSS JOIN (cartesian product).
You just need an EXISTS clause (this may also be done with a left join, but I think EXISTS is clearer)
SELECT u.Name
CASE WHEN EXISTS (Select null from mp
where mp.worker = u.keyid
and mp.memo = 16)
THEN 1 else 0 END
AS isin
FROM users u
You can of course use an IF clause instead of the CASE... WHEN (which is ANSI, while IF is not)
For simplicity, I will give a quick example of what i am trying to achieve:
Table 1 - Members
ID | Name
--------------------
1 | John
2 | Mike
3 | Sam
Table 1 - Member_Selections
ID | planID
--------------------
1 | 1
1 | 2
1 | 1
2 | 2
2 | 3
3 | 2
3 | 1
Table 3 - Selection_Details
planID | Cost
--------------------
1 | 5
2 | 10
3 | 12
When i run my query, I want to return the sum of the all member selections grouped by member. The issue I face however (e.g. table 2 data) is that some members may have duplicate information within the system by mistake. While we do our best to filter this data up front, sometimes it slips through the cracks so when I make the necessary calls to the system to pull information, I also want to filter this data.
the results SHOULD show:
Results Table
ID | Name | Total_Cost
-----------------------------
1 | John | 15
2 | Mike | 22
3 | Sam | 15
but instead have John as $20 because he has plan ID #1 inserted twice by mistake.
My query is currently:
SELECT
sq.ID, sq.name, SUM(sq.premium) AS total_cost
FROM
(
SELECT
m.id, m.name, g.premium
FROM members m
INNER JOIN member_selections s USING(ID)
INNER JOIN selection_details g USING(planid)
) sq group by sq.agent
Adding DISTINCT s.planID filters the results incorrectly as it will only show a single PlanID 1 sold (even though members 1 and 3 bought it).
Any help is appreciated.
EDIT
There is also another table I forgot to mention which is the agent table (the agent who sold the plans to members).
the final group by statement groups ALL items sold by the agent ID (which turns the final results into a single row).
Perhaps the simplest solution is to put a unique composite key on the member_selections table:
alter table member_selections add unique key ms_key (ID, planID);
which would prevent any records from being added where the unique combo of ID/planID already exist elsewhere in the table. That'd allow only a single (1,1)
comment followup:
just saw your comment about the 'alter ignore...'. That's work fine, but you'd still be left with the bad duplicates in the table. I'd suggest doing the unique key, then manually cleaning up the table. The query I put in the comments should find all the duplicates for you, which you can then weed out by hand. once the table's clean, there'll be no need for the duplicate-handling version of the query.
Use UNIQUE keys to prevent accidental duplicate entries. This will eliminate the problem at the source, instead of when it starts to show symptoms. It also makes later queries easier, because you can count on having a consistent database.
What about:
SELECT
sq.ID, sq.name, SUM(sq.premium) AS total_cost
FROM
(
SELECT
m.id, m.name, g.premium
FROM members m
INNER JOIN
(select distinct ID, PlanID from member_selections) s
USING(ID)
INNER JOIN selection_details g USING(planid)
) sq group by sq.agent
By the way, is there a reason you don't have a primary key on member_selections that will prevent these duplicates from happening in the first place?
You can add a group by clause into the inner query, which groups by all three columns, basically returning only unique rows. (I also changed 'premium' to 'cost' to match your example tables, and dropped the agent part)
SELECT
sq.ID,
sq.name,
SUM(sq.Cost) AS total_cost
FROM
(
SELECT
m.id,
m.name,
g.Cost
FROM
members m
INNER JOIN member_selections s USING(ID)
INNER JOIN selection_details g USING(planid)
GROUP BY
m.ID,
m.NAME,
g.Cost
) sq
group by
sq.ID,
sq.NAME