Where clause with multi AND & OR conditions - mysql

I got a table agenda in which the admin can make a reservation for him self or for someone else (another user). If the admin make the reservation for him self in agenda.user_id will be stored the id of admin.
In case that admin make a reservation for another person (another user) in agenda.user_id will be stored the id of the user for which the reservation will be made. The id of the admin will be stored in another column agenda.booked_user.
All the reservations are stored on agenda_users table also. agenda_users has this columns: id,agenda_id, user_id. The agenda_users.user_id it refers to agenda.user_id.
I want to retrieve all the reservations made by the admin which has made reservations for himself and for other users also.
I did a query with some AND & OR:
SELECT agenda.*
FROM agenda,agenda_users
WHERE agenda_users.agenda_id=agenda.id
AND (agenda_users.user_id=$user_id
AND agenda_users.user_id=agenda.user_id)
OR (agenda_users.user_id=agenda.user_id
AND agenda.booked_user=agenda.$user_id)
AND checkout IS NULL
AND NOW() < DATE_ADD(date_end, INTERVAL 6 HOUR) ORDER BY type ASC,date_start ASC
Cannot figure out the right solution to 'grab' all the reservations the admin has made for him self and other users.

solving the old-style-joins will leave you with this SQL:
SELECT agenda.*
FROM agenda
INNER JOIN agenda_users ON agenda_users.user_id=agenda.user_id AND agenda_users.agenda_id=agenda.id
WHERE
(agenda_users.user_id=$user_id) OR (agenda.booked_user=agenda.$user_id)
AND checkout IS NULL
AND NOW() < DATE_ADD(date_end, INTERVAL 6 HOUR) ORDER BY type ASC,date_start ASC;
This SQL is almost human-readable (and understandable). 😉
EDIT: Added extra () because AND has higher precedence than OR.
SELECT agenda.*
FROM agenda
INNER JOIN agenda_users ON agenda_users.user_id=agenda.user_id AND agenda_users.agenda_id=agenda.id
WHERE
((agenda_users.user_id=$user_id) OR (agenda.booked_user=agenda.$user_id))
AND checkout IS NULL
AND NOW() < DATE_ADD(date_end, INTERVAL 6 HOUR) ORDER BY type ASC,date_start ASC;

This is too long for a comment. So I am posting this as an answer and may adjust it, once you clarify doubts about the data model.
There is a parent table agenda and it has a child table agenda_users. So one agenda has several users. But the agenda table itself has two users, too. One is the person who made the reservation, but rather than using one column for that user, you are using sometimes one column and sometimes the other. You say that when an admin makes a reservation for another user, the admin gets stored in the column booked_user, although it's obviously not the booked user, but the booking user. I wonder whether you have understood the data model yourself, because the explanation sounds just wrong.
Then, an agenda should typically be identified by its id (hence the name), so the agenda_users should be linked via its agenda_id only. Are you sure that the user_id of the two tables must match, too? That would mean an agenda.id is unique only in combination with a user_id? It is possible, but doesn't seem likely.
Your query has some issues, too.
agenda.$user_id is probably supposed to mean $user_id only?
The parentheses are probably wrong, too, as AND has precedence over OR, so the checkout and date_end criteria will only work for the part after OR.
Then you are missing qualifiers. This doesn't make the query wrong, but makes it more difficult to read. What table do checkout and date_end belong to? I assume it's the agenda table and will write my query accordingly, because you mentioned the columns of the agenda_users table and these two columns were not among them.
You want to select data from agenda. So, do so; don't join another table. If you have criteria based on the other table, then use IN or EXISTS for the lookup. In your case, though, - but I can only guess here - it seems you don't need the agenda_users table at all.
SELECT *
FROM agenda
WHERE (user_id = $user_id OR booked_user = $user_id)
AND checkout IS NULL
AND NOW() < DATE_ADD(date_end, INTERVAL 6 HOUR)
ORDER BY type, date_start;

It is cleaner in my opinion to use UNION instead of a very complex where conditions.
Note that you know the user_id you are filtering for, therefore you don't need to join.
/* The ones for admin created by the user */
SELECT
agenda.*
FROM
agenda A
WHERE
A.user_id = $user_id
UNION ALL
/* the ones where the admin created it, but not for itself */
SELECT
agenda.*
FROM
agenda A
WHERE
A.booked_user_id = $user_id
AND A.user_id <> $user_id
Don't forget to add the rest of the where conditions to both subqueries of the union

Related

Data design best practices for customer data

I am trying to store customer attributes in a MySQL database although it could be any type of database. I have a customer table and then I have a number of attribute tables (status, product, address, etc.)
The business requirements are to be able to A) look back at a point in time to see if a customer was active or what address they had on any given date and B) have a customer service rep be able to put things like entering future vacation holds. I customer might call today and tell the rep they will be on vacation next week.
I currently have different tables for each customer attribute. For instance, the customer status table has records like this:
CustomerID
Status
dEffectiveStart
dEffectiveEnd
1
Active
2022-01-01
2022-05-01
1
Vacation
2022-05-02
2022-05-04
1
Active
2022-05-05
2099-01-01
When I join these tables the sql typically looks like this:
SELECT *
FROM customers c
JOIN customerStatus cs
on cs.CustomerID = c.CustomerID
and curdate() between cs.dEffectiveStart and cs.dEffectiveEnd
While this setup does work as designed, it is slow. The query joins themselves aren't too bad, but when I try to throw an Order By on its done. The typical client query would pull 5-20k records. There are 5-6 other similar tables to the one above I join to a customer.
Do you any suggestions of a better approach?
That ON clause is very hard to optimize. So, let me try to 'avoid' it.
If you are always (or usually) testing CURDATE(), then I recommend this schema design pattern. I call it History + Current.
The History table contains many rows per customer.
The Current table contains only "current" info about each customer -- one row per customer. Your SELECT would need only this table.
Your design is "proper" because the current status is not redundantly stored in two places. My design requires changing the status in both tables when it changes. This is a small extra cost when changing the "status", for a big gain in SELECT.
More
The Optimizer will probably transform that query into
SELECT *
FROM customerStatus cs
JOIN customers c
ON cs.CustomerID = c.CustomerID
WHERE curdate() >= cs.dEffectiveStart
AND curdate() <= cs.dEffectiveEnd
(Use EXPLAIN SELECT ...; SHOW WARNINGS; to find out exactly.)
In a plain JOIN, the Optimizer likes to start with the table that is most filtered. I moved the "filtering" to the WHERE clause so we could see it; I left the "relation" in the ON.
curdate() >= cs.dEffectiveStart might use an index on dEffectiveStart. Or it _might` use an index to help the other part.
The Optimizer would probably notice that "too much" of the table would need to be scanned with either index, and eschew both indexes and simply do a table scan.
Then it will quickly and efficiently JOIN to the other table.

SQL select the available rooms by date cheking

I have in my database a table called rooms that contain the rooms information and property ,and another table called reservation table that contain the Room Reserved, FromDate and ToDate .
What i want to do is to make the user pick room size that he want to reserve and pick the date for reserving the room ,then i provide for him the available rooms depend on the Room Reservation table.
here what i did:
SELECT * FROM Rooms,Reservations WHERE
Rooms.R_Size = 'roomSize' AND ('4/19/2013' NOT
BETWEEN Reservation.FromDate AND Reservation.ToDate AND '4/19/2013'
NOT BETWEEN Reservation.FromDate AND Reservation.ToDate)
The problem its return to me duplicate's rooms and even if its between the reserved date in specific reservation but its not between reserved date in another reservation still it will return it to me.
What i want is to check if the room is reserved at the same or between a specif date and if it is i don't want it to be selected and returned at all.
Thanks.. and sorry for my poor english
There are two problems with your query. One is that there is no condition on the join between rooms and reservations, such that rooms of the correct size will be returned once for each reservation satisfying the date tests. Another problem is that your date test is wrong as it will not detect existing reservations that is completely within the date interval of the new reservation.
A query like this one should give you the result you want:
SELECT * FROM Rooms
LEFT JOIN Reservations
ON Reservations.R_Number = Rooms.Number
AND Reservations.ToDate > '4/19/2013'
AND Reservations.FromDate < '4/20/2013'
WHERE Rooms.R_Size = 'roomSize'
AND Reservations.R_Number IS NULL
It works by joining the rooms to the reservations for that room, and then selecting the rooms for which there are no reservations that conflicts with the new reservation being made.(Old reservation that ends before the new one starts, or that starts after the new one ends are no problem).
What you are doing here is a cross join. Every row from table a (Rooms) is joined with every row in table b (Reservations).
In order to make your query work, you need to specify that Rooms.Rooms_Key = Reservations.Rooms_ForignKey in your where clause (or an explicit join [inner,left,right] and specify the ON fields as they are easier to read in my opinion - explicit-vs-implicit for more info).
Once you have converted the join type, the where clause will start to give you better results, and you should be able to modify it if you still need to at that point.

What is the proper way to store friendship associations in a mysql DB

I want to create a table where my users can associate a friendship between one another. Which at the same time this table will work in conjunction to what I would to be a one-to-many relation between various other tables I am attempting to work up.
Right now I am thinking of something like this
member_id, friend_id, active, date
member_id would be the column of the user making the call, friend_id would be the column of the friend they are attempting to tie to, active would be a toggle of sorts 0 = pending, 1 = active, date would just be a logged date of the last activity on that particular row.
Now my confusion is if I were to query I would typically query for member_id then base the rest of the query off of associated friend_id's to display data accordingly to the right people. So with this logic of sorts in mind, that makes me think I would have to have 2 rows per request. One where its the member_id who's requesting and the friend_id of the request inserted into the table, then one thats the opposite so I could query accordingly every time. So in essences its like double dipping for every one action requested to this particular table I need to make 2 like actions to make it work.
Which in all does not make sense to me as far as optimization goes. So in all my question is what is the proper way to handle data for relations like this? Or am I actually thinking sanely about this being an approach to handling it?
If a friendship is always mutual, then you can choose between data redundancy (i.e. both directions having a row) for the sake of simpler queries, or learn to live with slightly more complex queries. I'd personally avoid data redundancy unless there is a compelling reason otherwise - you're not just wasting space and performance, but you'll need to be careful when enforcing it - a simple CHECK is incapable of referencing other rows and depending on your DBMS a trigger may be limited in what it can do with a mutating table.
An easy way ensure to only one row per friendship is to always insert the lower value in member_id and higher value in friend_id (make a constraint CHECK (member_id < friend_id) to enforce it). Then, when you query, you'll have search in both directions - for example, finding all friends of the given person (identified by person_id) would look something like this:
SELECT *
FROM
person
WHERE
id <> :person_id
AND (
id IN (
SELECT friend_id
FROM friendship
WHERE member_id = :person_id
)
OR
id IN (
SELECT member_id
FROM friendship
WHERE friend_id = :person_id
)
)
BTW, in this scheme, you'd probably want to rename member_id and friend_id to, say, friend1_id and friend2_id...
Two ways to look at it:
WHERE ((friend_id = x AND member_id = y) OR (friend_id = y AND member_id = x))
would allow you to query by simply stating one side of the relationship. If both sides are added, this method would still work without causing duplicate rows to be returned.
Conversely, adding both sides of the relationship, so that your queries consist of
WHERE friend_id = x AND member_id = y
not only makes queries easier to write, but also easier to plan (meaning better DB performance).
My vote is for the latter option.
Beautiful - there's no problem with your table as-is.
ALSO:
I'm not sure if this cardinality is "one to many", or "many to many":
http://en.wikipedia.org/wiki/Cardinality_%28data_modeling%29
Q: I were to query I would typically query for member_id then base the
rest of the query off of associated friend_id's to display data
accordingly to the right people
A: Frankly, I don't see any problem querying "member to friend", or "friend to member" (or any other combinations - e.g. friends who share friends). Again, it looks good.
Introduce a helper table like:
users
user_id, name, ...
friendship
user_id, friend_id, ....
select u.name as user, u2.name as friend from users u
inner join friendship f on f.user_id = u.user_id
inner join users u2 on u2.user_id = f.friend_id
I think this is pretty similar to what you have, just putting a query as an example.

MySQL How To Retrieve Table Name As A Field

Im not sure how to exactly word my issue but I will try my best. Im trying to model a database for a driving school. I have a "timeslot" table such that activities such as lessons, tests, and registration interviews can all be linked to a given timeslot for a staff member. One of the queries I am providing is to be able to view a "timetable" of events for a staff member. I have constructed the query and it is working, however it joins data from various other tables, and I would like to see the names of those tables to know what activity the timeslot is reserved for.
ER Model
The query I perform to check the staff timetable is the following:
SELECT Timeslot.*
FROM Timeslot
LEFT JOIN Test
ON Timeslot.Timeslot_ID = Test.Timeslot
LEFT JOIN Interview
ON Timeslot.Timeslot_ID = Interview.Timeslot
LEFT JOIN Lesson
ON Timeslot.Timeslot_ID = Lesson.Timeslot
WHERE Timeslot.Date BETWEEN CURDATE() AND (CURDATE() + INTERVAL 7 DAY)
AND Timeslot.Staff = 1;
This works and shows a list of all registered timeslots for a given staff member for the next week. What I would like is a further column which would show what type of activity it is, such as "Lesson", "Interview", or "Test". As you can see, I am currently storing this as a field in the timeslot table, which means that I have to specify this every time I insert a timeslot. I will be normalising the database to 3NF and want to avoid duplication. Is there a way I model this to get the name of the table, I considered using UNIONS and many other things but could use some help.
Many thanks and apologies if this seems a bit vague.
Mike
My stab at it, if you're keeping the model you've described, would be with a Case statement, like so:
Select Timeslot.*,
Case
When Test.Timeslot Is Not Null Then 'Test'
When Interview.Timeslot Is Not Null Then 'Interview'
When Lesson.Timeslot Is Not Null Then 'Lesson'
End As ActivityType
From ...
Your query would then have an "ActivityType" column at the very end that you could use.

Database Structure - two tables or one table?

I have one database table dealing with users login totals and another table dealing with individual login sessions. Should I keep these tables separate or should I go ahead and merge them?
users_logins
users_id
successful_logins(total)
last_online
users_logins_sessions
users_id
session_id
ip_address
user_agent
last_activity(time-stamp)
You could lose user_logins, as I assume last_online and last_activity contain same value.
You would however have to query the user_logins_sessions table to get the total for successful logins for a given user.
SELECT COUNT(user_id) FROM user_login_sessions WHERE user_id = ?
This really depends on you, however I understand it as (making assumption here) that sessions are cleared? Typically in my applications sessions expire, and a new one is created, I am not sure how you manage that in your users_logins_sessions as you don't give much more info on this, it could work either way.
You should merge if your 'session' table never deletes entries, OR leave it alone the way it is, if the sessions expire / are deleted at intervals.
I am also assuming the users_id is used somewhere else if you keep them separate.
If you only have the users_logins_sessions table, you can easily query for successful_logins and last_online.
SELECT COUNT(1) AS successful_logins
FROM users_logins_sessions
WHERE users_id = <user_id>;
SELECT MAX(last_activity) AS last_online
FROM users_logins_sessions
WHERE users_id = <user_id>;