I am making something like an announcement board that requires readers to acknowledge that they read it, and was wondering if there is a more efficient way of doing this.
I have 3 Tables on MySQL side:
+-----------------+ +-----------------+ +-----------------+
| Announcements | | Acknowledgement | | User |
+-----------------+ +-----------------+ +-----------------+
| announce_id | | ack_id | | user_id |
| announce_msg | | announce_id | | user_name |
| ... | | user_id | | ... |
+-----------------+ +-----------------+ +-----------------+
When a user "reads" the announcement (by clicking a button), Acknowledgment table will be inserted with the Announcement ID and User ID. When a second user "reads" the same announcement, Acknowledgement table will be inserted again with same Announcement ID and the second User ID and so on...
+--------------------------------+
| Acknowledgement |
+--------+-------------+---------+
| ack_id | announce_id | user_id |
+--------+-------------+---------+
| 1 | 1 | 1 |
| 2 | 1 | 4 |
| 3 | 1 | 3 |
| 4 | 3 | 1 |
| 5 | 3 | 6 |
| 6 | 3 | 2 |
+--------+-------------+---------+
Now to the problem. On the front end, when I list all the announcements on a page, I would have to first query for all the announcements. Then, for each announcement, I would have to do another query for all the users that have read this announcement.
$sql = "select * from Announcements";
$result = $pdo->query($sql);
while ($row = $result->fetch())
{
$announce_id = $row['announce_id'];
$announce_msg = $row['annouce_msg'];
$readers = "";
$sql2 = "select u.user_name from Acknowledgement as a INNER JOIN User as u where announcement_id =".$annouce_id;
$result2 = $pdo->query($sql);
while ($row2 = $result2->fetch())
{
$readers .= $row2['user_name'].", ";
}
echo "id:".$annouce_id.", message:".$announce_msg.", Readers:".$readers;
}
So if there 10 announcements on the page, there will be 10 sub-queries for each announcement. What I have now does the job right now... but what if there is 1000 announcements? Then there will be 1000 sub-queries? Sounds like the database will be really hammered. So I'm hoping there is a better way of doing this.
Also, if 1000 people in the user table reads all 1000 announcements, the acknowledgement table will have 1000x1000 entries. seems like the acknowledgement table will become really really long. Will that be a problem as time goes by?
This is a really rough example of what I'm trying to do but it did take me a long time to write all this. If more details is needed let me know.
There is a better way. You can use a single query with group_concat:
select a.*, group_concat(u.user_name separator ', ') as AllUsers
from Announcements a join
Acknowledgement ak
on a.Announce_Id = ak.Announce_Id join
User u
on u.user_ID = ak.User_ID
group by a.announce_id
This uses the MySQL feature of hidden columns to group by only one column (announce_id) but still pull in a bunch of other columns with no aggregations (everything else pulled in by the "*").
If your purpose here is to filter out the announcements that your current user has read, you can do this an entirely different way. Instead of querying for every announcement, and then finding out all the users that have read those announcements and examining those results to find ones that your use has read and trimming them from the displayed list, you can just query in one go for everything a particular user (or list of users) have not yet read.
Change your query to this:
SELECT * FROM Announcements WHERE Announce_id NOT IN (SELECT ANNOUNCE_ID FROM Acknowledgement WHERE User_ID = <INSERT USER ID HERE>)
That should return all Announcement rows that this particular user has not yet acknowledged. If you change that final WHERE clause to be WHERE User_ID IN () then you can specify a list of user IDs.
EDIT: Given the comment you posted above, you could use this query to get all announcements that have been read by no one:
SELECT * FROM Announcements WHERE Announce_id NOT IN (SELECT ANNOUNCE_ID FROM Acknowledgement WHERE User_ID IN (SELECT User_ID FROM User))
The logic for putting together a query to find announcements that haven't been read by someone (if not everyone) is escaping me right this second.
EDIT THE SECOND: Every announcement, and everyone who has and has not read it, requires use of a different kind of join that you've used above, a FULL OUTER JOIN. Unfortunately MySQL doesn't have that feature IIRC, but it can be simulated with a union query
SELECT A.*, ACK.*, U.* FROM Announcements AS A
INNER JOIN Acknowledgement AS ACK ON A.Announce_ID = ACK.Announce_ID
LEFT OUTER JOIN User AS U ON ACK.User_ID = U.User_ID
WHERE U.User_ID IS NOT NULL
UNION ALL
SELECT A.*, ACK.*, U.* FROM Announcements AS A
LEFT OUTER JOIN Acknowledgement AS ACK ON A.Announce_ID = ACK.Announce_ID
RIGHT OUTER JOIN User AS U ON ACK.User_ID = U.User_ID
I think that should do it. No facilities to test at the moment, of course.
Related
I have 2 table, records and common_member.
records just store a plugin data. (Not frequently queried) but common_member was a huge data table and frequently queried.
Sample data of common_member
+--------+-----------+
| uid | username |
+--------+-----------+
| 1 | admin |
| 2 | test1 |
+--------------------+
Sample data of records
+--------+-----------+-----------+
| uid | amount |createtime |
+--------+-----------+-----------+
| 1 | 50 |1234567 |
| 1 | 100 |5555555 |
| 2 | 2000 |9999999 |
+--------------------------------+
Normally I am using $lrecord = mysqli_fetch_all("SELECT t1.*,t2.username FROM records t1 LEFT JOIN common_member t2 ON (t1.uid = t2.uid) ORDER BY t1.createtime").
But after that I was be informed if using leftjoin connect with common_member, It will be very inefficient and there was a way just using where in the get the username from common_member.
So, how to get the username from common_member without using leftjoin?
I want the final result of records array to be: (Means username from common_member join to records)
+------+-------------+-----------+-----------+
| uid | username | amount | createtime|
+------+-------------+-----------+-----------+
| 1 | admin | 50 | 1234567 |
| 1 | admin | 100 | 5555555 |
| 2 | test1 | 2000 | 9999999 |
+--------------------------------------------+
My question is, how to use where in get the username in common_member without leftjoin ?
Thank you.
But after that I was be informed...
(with respect...) who informed you ? phpMyAdmin ? another interface ? your friend ? a blog.. ?
there is no problem with the left join, but you must check the indexes of your tables.
It is necessary that in both tables, there is an index (preferably primary) on the field uid.
Otherwise, the request will not be optimized and can be slow.
The answer to your question, how to use where in get the username in common_member without leftjoin ?, is that you can't.
The where clause is used for filtering results, not to add information to them. This could be usefull if you just want to retrive all rows in records which have an existing user. However, if database was properly designed, such a question wouldn't be asked, because records.uid would be an non-nullable foreign key referencing common_member.uid.
Hence, it wouldn't be necessary to make a left join, but only a join.
But, supposing this isn't your case, and the database isn't properly designed, and records.uid isn't a foreign key, then
select *
from records
where uid in (
select uid
from common_member
)
would return all records of existing users. But still, no username.
The only solution is to execute a join, as you are already doing:
SELECT t1.*,t2.username
FROM records t1
JOIN common_member t2 ON (t1.uid = t2.uid)
ORDER BY t1.createtime
I'm stuck with one SQL query. I have two tables:
users
________________________
| id | company | worker|
-------------------------
| 1 | my comp | John |
tasks
_________________________
| id | name | company |
-------------------------
| 1 | exm | my comp |
My problem is that I want to show tasks of these companies which worker is John. I'm in trouble in that for hours but I don't know how to do it. Is there any SQL query to do that?
You can do a simple join using company column from both tables and use where clause to filter results for John
SELECT t.*
FROM users u
JOIN tasks t USING(company)
WHERE u.worker ='John'
You want use the inner join tag. Just a modification on the other queries mentioned for better clarity.
SELECT task.name,user.worker,user.company
FROM tasks as task INNER JOIN users as user
ON user.company=task.company
WHERE user.worker='John';
You can do a simple join like...
$qry = "SELECT u.id,u.company,u.worker,t.id,t.name,t.company FROM users as u JOIN tasks as t ON u.company = t.company WHERE u.worker = 'John'";
I'm having trouble figuring out how to write an SQL query to return results from the following table structure.
The first thing I do is get a list of clients that have a status equal to 1 by:
SELECT * FROM clients WHERE status=1
Then I need to get all user email addresses that belong to a client. My plan was to loop through the results of the query above and running multiple queries for each client. As you can see from the table 'client_user_list' a single user can belong to multiple clients.
I tried doing something like this:
SELECT emailaddress
FROM users
INNER JOIN client_user_list ON users.user_id = client_user_list.user_id
WHERE users.client_id = 1
But it failed. As you can see I'm a total novice when it comes to this stuff. Any help would be appreciated, or feel free to point me to an appropriate resource to learn more. I've looked, but I haven't found anything that covers something complex like this.
Additional info: Using foreign keys there are relationships between clients <-> client_user_list and client_user_list <-> users
clients:
|---------------------------------------|
| client_id | client_name | status |
|---------------------------------------|
| 1 | John Doe | 1 |
| 2 | James Doe | 0 |
|---------------------------------------|
client_user_list:
|----------------------|
| client_id | user_id |
|----------------------|
| 1 | 5 |
| 2 | 6 |
| 1 | 6 |
|----------------------|
users:
|---------------------------------------|
| user_id | emailaddress |
|---------------------------------------|
| 5 | notan#email.com |
| 6 | afake#email.com |
|---------------------------------------|
Thanks so much in advance.
I'm not sure if this is your only problem, since you didn't specify what the exact problem is, but the WHERE-clause of your query contains an error. You query should be changed into this:
SELECT DISTINCT emailaddress
FROM users
INNER JOIN client_user_list ON users.user_id = client_user_list.user_id
WHERE client_user_list.client_id = 1
The users table does not have a field called client_id, the client_user_list table does.
You can get the clients with status = 1 and their users with only one query, by joining all three tables:
select clients.client_id, clients.client_name, users.user_id, users.emailaddress
from clients
inner join client_user_list on client_user_list.client_id = clients.client_id
inner join users on client_user_list.user_id = users.user_id
where clients.status = 1
order by clients.client_id, users.user_id
The following command should resolve this issue
I hope it is userful.
select distinct use.emailaddress
from clients cli
inner join client_user_list cul on (cli.client_id=cul.client_id)
inner join users use on (cul.user_id = use.user_id)
where cli.status = 1
I have three MySQL tables which relate to a messaging system. The schema and sample data is shown below for each table relating to my question:
`messages`:
+----+---------+----------+
| id | subject | senddate |
+----+---------+----------+
| 1 | Testing | 12344555 |
+----+---------+----------+
`message_senders`:
+------------+---------+---------+
| message_id | user_id | trashed |
+------------+---------+---------+
| 1 | 1 | 1 |
+------------+---------+---------+
`message_recipients`:
+------------+---------+------+----------+---------+
| message_id | user_id | type | readdate | trashed |
+------------+---------+------+----------+---------+
| 1 | 1 | to | 12344555 | 1 |
+------------+---------+------+----------+---------+
| 2 | 1 | cc | 12344555 | 1 |
My question is how would I select all messages sent by or received by a user, where the trashed parameter is set to 1, without selecting duplicate messages. For example, consider the following scenario:
I want to get the message IDs for all messages trashed by user_id 1, but I don't want to retrieve duplicate IDs (in the data above for example, user_id 1 is the sender AND recipient of message_id 1. I don't want to return the message_id of 1 twice, but want to get all messages for that user.
I think I need to use a combination of JOIN and UNION, but my brain isn't functioning after a long day of PHP!
Try this and see if it only returns one row for each message in the messages table...
select * from messages
left join message_senders on messages.id = message_senders.message_id
left join message_recipients on messages.id = message_recipients.message_id
where message_senders.trashed = 1 or message_recipients.trashed = 1 and messages.user_id = <value>
Assuming you got the query in your mind - I'll just hint you can use DISTINCT keyword to force the DB not to return duplicates.
I am sure you can work it out yourself, because self-conclusions work the best.
Also, a piece of advice - always store dates as datetime or date instead of int. You avoid daylight savings time problems and you can use various date functions provided by MySQL.
Dates are saved internally as 4 byte integers, same as int fields.
Here you go, give this a shot:
select distinct m.id
from messages m
left join message_senders s on m.id = s.message_id
left join message_recipients r on m.id = r.message_id
where ((s.user_id = 1 and s.trashed = 1) or (r.user_id = 1 and r.trashed = 1))
The solution that leaps to my mind is to use a UNIONd subquery:
SELECT DISTINCT * FROM messages WHERE id IN (
(SELECT message_id FROM message_recipients WHERE user_id=1 AND trashed=1)
UNION
(SELECT message_id FROM message_senders WHERE user_id=1 AND trashed=1)
)
i have a form that has a multiple select drop down. a user can select more than one options in the select. the name of the select is array[]; using php I call implode(",",$array)
in mysql db, it stores the field as a text in this format "places"= "new york, toronto, london" when i want to display these fields i explode the commas.
I am trying to run a report to display the places. here is my select:
"select * from mytable where db.places .. userSelectedPlaces"
how can i check toronto in lists of "places" that user selected? note "places" in the db might be either just "toronto" or it might be comma separated lists of places like "ny, toronto, london, paris, etc".
If it is possible, you would be much better off using another table to hold the places that the user has selected. Call it SelectedPlaces with columns:
mytable_id - To join back to the table in your query
place - EG: "Toronto"
Then you can run a simple query to figure out if Toronto has been selected:
SELECT *
FROM mytable m
INNER JOIN SelectedPlaces sp ON sp.mytable_id = m.id
WHERE sp.place = 'Toronto'
If I understand you correctly, your database design is just wrong. Try reading about it more. Generally, in good design you should not have lists of values as one field in database and you should introduce new table for it.
But if you want to do it this way, you can use strcmp function.
If i understood correctly, this should work:
WHERE DB.PLACES LIKE '%TORONTO%'
but as other users said, its not a nice thing to have denormalized tables.
To directly answer your question, your query needs to look something like this
SELECT *
FROM mytable
WHERE places LIKE( '%toronto%' )
But, be aware, that LIKE() is slow.
To indirectly answer your question, your database schema is all wrong. That is not the right way to do a M:N (many-to-many) relationship.
Imagine instead you had this
mytable place mytable_place
+------------+ +----------+----------+ +------------+----------+
| mytable_id | | place_id | name | | mytable_id | place_id |
+------------+ +----------+----------+ +------------+----------+
| 1 | | 1 | new york | | 1 | 1 |
| 2 | | 2 | toronto | | 1 | 2 |
| 3 | | 3 | london | | 1 | 3 |
+------------+ +----------+----------+ | 2 | 2 |
| 3 | 1 |
| 3 | 3 |
+------------+----------+
The table mytable_places is what's called a lookup table (or, xref/cross-reference table, or correlation table). Its only job is to keep track of which mytable records have which place records, and vice versa.
From this example we can see that The 1st mytable record has all 3 places, the 2nd has only toronto, and the 3rd has new york and london.
This opens you up too all sorts of queries that would be difficult, expensive, or impossible with your current design.
Want to know how many mytable records have toronto? No problem
SELECT COUNT(*)
FROM mytable_place x
LEFT JOIN place p
ON p.place_id = x.place_id
WHERE p.name = 'toronto';
How about the number of mytable records per place, sorted?
SELECT p.name
, COUNT(*) as `count`
FROM mytable_place x
LEFT JOIN place p
ON p.place_id = x.place_id
GROUP BY p.place_id
ORDER BY `count` DESC, p.name ASC
And these are going to be much faster than any query using LIKE since they can use indexes on columns such as place.name.