mySQL SELECT help. IF or EXISTS? - mysql

I have this DB structure
* user
user_id
name
* client
client_id
name
* user_client
user_client_id
user_id
client_id
* message
message_id
client_id
description
If there are entries on user_client then the user has permissions restricted to the specific clients listed for his id on the table. If there are no entries, then the user has access to any client.
How can I select only messages that the user can read?
I'm trying to do an IF on the WHERE clause to check if any entries on the user_client table but I don't know where to go from there. It needs to select all messages from any client if no entries on user_client or only messages for client_id specified on user_client table
Thanks for the help!

I would suggest doing two different queries: one for the superusers and the other for the restricted users. Then you can join the two results with a UNION.
SELECT M.message_id,
M.client_id,
M.description
FROM message M
INNER JOIN user_client UC ON (UC.client_id = M.client_id)
INNER JOIN user U ON (UC.user_id = U.id)
WHERE U.id = :user_id
UNION
SELECT M.message_id,
M.client_id,
M.description
FROM message M
WHERE NOT EXISTS (
SELECT *
FROM user_client
WHERE user_id = :user_id
)
You can obtain the same result with other queries but IMHO this one is clearer and more maintainable.
Edit: If you want to ensure that the user exists you should join the second query with the user table.
SELECT M.message_id,
M.client_id,
M.description
FROM message M
JOIN user U
WHERE U.id = :user_id
AND NOT EXISTS (
SELECT *
FROM user_client
WHERE user_id = :user_id
)

One way the do this could be to use two different queries to create a set of the messages users can see and filter according to your needs; something like this should work:
select * from (
select u.user_id, u.name, c.name client, m.message_id, m.description
from user u
join user_client uc on u.user_id = uc.user_id
join client c on uc.client_id = c.client_id
join message m on c.client_id = m.client_id
union all
select u.user_id, u.name, c.name client, m.message_id, m.description
from user u
cross join client c
join message m on c.client_id = m.client_id
where user_id not in (select user_id from user_client)
) x
where x.user_id = 1;
Here users present in the user_client table are restricted to the set of messages that they have access to (the first set in the union), while users not present in the user_client table can see all messages (the second set in the union).
Sample SQL Fiddle

If I am understanding your question correctly, such as
1) Administrative user... They can look at EVERYTHING since they would have no records in the user_client table.
2) Client Supervisor... Such a person who's primary responsibility is to a specific client (or multiple clients). Therefore, the user DOES have a record in the user_client table. If so, then only allow the user to see records for those clients they DO have associations with.
select
m.message_id,
m.client_id,
m.description,
c.name as clientName
from
( select count(*) as HasClients
from user_client
where user_id = TheUserYouWant ) ClientCheck,
message m
left join user_client uc
on m.client_id = uc.client_id
AND uc.user_id = TheUserYouWant
join client c
on m.client_id = c.client_id
where
ClientCheck.HasClients = 0
OR NOT uc.user_id IS NULL
The query looks at the user_client table TWICE. The first time is to just get a count of those records that DO exist for the given user, regardless of which client associated with. The query will always come back with 1 row and it will either be 0 (no such records), or greater than 1 (however many they are associated with).
The second instance is a LEFT-JOIN to the user_client table, JUST IN CASE the person IS restricted to only looking at their own client messages.
The WHERE clause now comes in and says... if the underlying count of clients was zero, then ok to give me all messages. If any other value, then the user ID in the user_client table (as left-joined to the messages on both the CLIENT AND THE USER you want) MUST EXIST (via NOT a NULL value for the user_id column).
Now, you probably don't want to query EVERY message as it could get quite large as your database grows, but you could put whatever other criteria in the WHERE clause, such as date restrictions and/or client(s) you are interested in.

Related

Relational query return null values

I have a question. In my database there are 2 tables. For example say:
Calendar table
uid
name
Signup table
uid
calendar_id
user_id
coming (-1 no, 0 undecided, 1 yes)
0 = undecided but not record is also undecided
User table
uid
name
And i want to see what users are coming to a specific event. So i have the query:
SELECT *
FROM user u, signup s
WHERE u.uid = s.user_id
AND s.event = 1
But this shows me all the users who have actually created a record in the database. I also want to see the users who don't have a record in the signup table. How can i achieve this?
Kind regards,
Don't use the old implicit join syntax any more. Use explicit joins. In your case - a left join
SELECT *
FROM user u
LEFT JOIN signup s ON u.uid = s.user_id
AND s.event = 1

MySQL Query phpmyadmin

I'm trying to make a SQL query that will search for user id and populate the query with the username.
These are my tables:
Table Names: 'users' and 'schedule'
This is how I want it to look like, where 'schedule' table now shows the username instead of the user's ID
This is the query you are looking for:
SELECT s.REFID, s.jobnum, s.customer, u1.username AS engineer, u2.username AS sales
FROM schedule s, users u1, users u2
WHERE s.engineer=u1.id
AND s.sales=u2.id
You need to reference the users table two separate times, since you are checking in one sub-query for the engineer's username, and then checking in a separate sub-query for the salesperson's username.
Here is a link to an SQLFiddle that shows the result of the query's execution. It matches up with what you were looking for. I hope this helps.
Following Query will give you the expected result:
SELECT
s.refid as refid,
s.jobnum as jobnum,
s.customer as customer,
u_engg.username as engineer,
u_sales.username as sales
FROM
user u_engg join schedule s on u.id = s.engineer join
user u_sale on u_sale.id = s.sales
SELECT s.refid, s.jobnum, s.customer, u.username engineer, u.username sales
FROM schedule s
LEFT OUTER JOIN users u
ON s.engineer = u.id AND s.sales = u.id
It looks like you need to reference the users table two times. One JOIN to get the engineer username, and a second JOIN to get the sales username.
Something like this:
-- return all rows from schedule, and lookup of username where available
SELECT s.REFID
, s.jobnum
, s.customer
, e.username AS engineer
, a.username AS sales
FROM schedule s
LEFT
JOIN users e
ON e.id = s.engineer
LEFT
JOIN users a
ON a.id = s.sales
Using a LEFT [OUTER] JOIN ensures that the rows from schedule will be returned when there isn't a matching row in the users table. For example, if you had a NULL in the sales column of a row in schedule, there wouldn't be a matching row in the users table. With an [INNER] JOIN, the row from schedule would not be returned. But the query above does return the row, but with a NULL for the username when matching rows are not found.
If the engineer and sales columns are defined as NOT NULL, and foreign keys are defined and enforced, then the LEFT keyword can be omitted from the query above. In the more general case, where foreign keys are not enforced (e.g. MyISAM) or not defined, or the columns are nullable, we'd generally want the LEFT keywords.
UPDATE
Removing the LEFT keywords from the query will produce a query equivalent to that in the answer from Alvin Lee, which implements INNER JOIN operations.
The query from Alvin Lee will EXCLUDE rows from schedule that have a value in the engineer or sales column that is NULL, or has a value that does not match a value found in the id column of the users table.
To identify if any rows in the schedule table are not being returned by the query using an INNER JOIN, you can run a query that does an anti-join pattern.
-- find rows in schedule that don't have matching row in users
SELECT s.REFID
, s.jobnum
, s.customer
, s.engineer
, s.sales
FROM schedule s
LEFT
JOIN users e
ON e.id = s.engineer
LEFT
JOIN users a
ON a.id = s.sales
WHERE a.id IS NULL
OR e.id IS NULL
try this:
select sc.REFID, sc.jobnum, sc.customer, us.username as engineer, us.username as sales
from schedules as sc
left join users as us on sc.engineer = us.ID and sc.sales = us.ID

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

How to check combination of records from multiple rows(MySQL)

I am working on writing a query that is able to check multiple rows at the same time. If the combination of the same user's records provides the enough information I need, even every single record doesn't provides enough information I need, the user is considered passed.
For example:
There are two tables.
One is "user" which keep user's personal information:
id client_id first_name last_name date_of_birth ssn address
Another one is "lab" which keep users' medical test information:
id external_source_id user_id date wbc rbc hemoglobin hematocrit mcv mch mchc rdw plateletcount
One user can only have one record in user table, but could have multiple records in labs table. What I want to do is check the users' multiple lab records that belongs to the same user to see if the combination of those records provide the necessary information I need. If yes, the user is passed, even if any single lab record doesn't provide enough information. For example, the necessary information includes cholesterol, ldl, triglycerides, glucose. If a user has two lab records, one record provides cholesterol(NOT NULL) and ldl(NOT NULL), another one provides triglycerides(NOT NULL), glucose(NOT NULL). He is considered passed.
How do I write the query that is able to do that?
The query I currently have is like this:
SELECT users.id AS user_id, users.first_name, users.last_name, clients.name AS client,
users.social_security_number AS ssn, users.hiredate, hra.id AS hra_id, hra.date AS hra_date, hra.maileddate AS hra_maileddate,
screening.id AS screening_id, screening.date AS screening_date, screening.maileddate AS screening_maileddate
FROM users
INNER JOIN clients
ON(
users.client_id = clients.id
)
INNER JOIN hra
ON(
users.id = hra.user_id
)
LEFT JOIN labs
ON(
users.id = labs.user_id
)
WHERE users.client_id = '1879'
AND hra.date BETWEEN '2011-07-01' AND '2011-11-15'
AND hra.maileddate IS NOT NULL
AND labs.date BETWEEN '2011-05-15' AND '2011-11-15'
AND labs.maileddate IS NULL
AND labs.cholesterol IS NOT NULL
AND labs.ldl IS NOT NULL
AND labs.triglycerides IS NOT NULL
AND (labs.glucose IS NOT NULL OR labs.ha1c IS NOT NULL)
GROUP BY users.id
This will select all users in your example
select u.*
from user u
join lab l1 on l1.user_id = u.id and l1.wbc is not null
join lab l2 on l2.user_id = u.id and l2.hemoglobin is not null
join lab l3 on l3.user_id = u.id and l3.plateletcount is not null
-- etc for other fields that need to be not null
This will work even if the same records have more than one desired column, or if the values are spread out across rows.
If you want the lab values too, just select u.*, l1.wbc, l2.hemoglobin, ... etc
If you want the Users who PASS :
You can use a IN with AND clause
Select u.* from user u
where
u.user_id in (select user_id from lab where wbc is not null) and
u.user_id in (select user_id from lab where hemoglobin is not null) and
u.user_id in (select user_id from lab where plateletcount is not null);
if you want the users who DONT PASS
You can use a IN with OR clause
Select u.* from user u
where
u.user_id in (select user_id from lab where wbc is null) OR
u.user_id in (select user_id from lab where hemoglobin is null) OR
u.user_id in (select user_id from lab where plateletcount is null);
i hope that makes sense :)

Difficult MySQL Query

In our database tables we keep a number of counting columns to help reduce the number of complex look-up queries. For example, in our users table we have columns for the number of reviews written, photos uploaded, friends, followers, etc. To help make sure these stay in sync we have a script that runs periodically to check and update these counting columns.
I've been attempting to write an efficient query that calculates the number of friends a specific user has and haven't been able to figure out how to do it. In our friend model someone is a friend if they add you as a friend (no confirmation needed), so you have to count the number of unique of people when adding the number of friends you have added plus the number of people who have added you as a friend.
Here are two queries that each work to update the friend count for all the rows in our users table for friendship in a single direction. What I can't figure out it how to combine them so you get the total number of unique friends a user has:
Users Who Added You As A Friend
UPDATE users
JOIN (SELECT cid2, COUNT(*) as c
FROM connections
JOIN users ON connections.cid1 = users.user_id
WHERE connection_type = "MM"
AND connections.status="A"
AND users.status != "D"
GROUP BY cid2) f ON f.cid2 = users.user_id
SET users.friends = f.c
WHERE users.status != "D";
Users Who You Added As A Friend
UPDATE users u
JOIN (SELECT cid1, COUNT(*) as c
FROM connections
JOIN users ON connections.cid1 = users.user_id
WHERE connection_type = "MM"
AND connections.status = "A"
AND users.status != "D"
GROUP BY cid1) f ON f.cid1 = users.user_id
SET users.friends = f.c
WHERE users.status != "D";
Attempt 3...
UPDATE
users
INNER JOIN
(
SELECT
connections.cid1 AS user_id,
COUNT(*) AS total
FROM
(
SELECT cid1, cid2 FROM connections WHERE connection_type = 'MM' AND status = 'A'
UNION
SELECT cid2, cid1 FROM connections WHERE connection_type = 'MM' AND status = 'A'
)
AS connections
INNER JOIN
users
ON users.user_id = connections.cid2
AND users.status != 'D'
GROUP BY
connections.cid1
)
AS friends
ON friends.user_id = users.user_id
SET
users.friends = friends.total
WHERE
users.status != 'D';
(Other options deleted at OP's request. See edit history if interested.)
You could write it as a stored procedure which does both SELECT COUNT() and then adds it to a counter variable, then issue a single UPDATE
How important is the status flag? You're going to run into trouble trying to update the users table with a sub-query which references the same table.
Consider:
UPDATE connections SET status='D' WHERE cid1 IN
(SELECT user.id
FROM users u
WHERE status='D');
UPDATE connections SET status='D' WHERE cid2 IN
(SELECT user.id
FROM users u
WHERE status='D');
UPDATE users u
SET friends = (
SELECT COUNT(DISTINCT friend)
FROM (
SELECT c1.cid2 as friend
FROM connections c1
WHERE c1.connection_type = 'MM'
AND c1.connections.status='A'
AND c1.status<>'D'
AND c1.cid1=u.id
UNION
SELECT c2.cid1 AS friend
FROM connections c2
WHERE c2.connection_type = 'MM'
AND c2.connections.status='A'
AND c2.status<>'D'
AND c2.cid1=u.id
)
)