I currently have a MySQL SELECT statement that pulls information from two tables to display a "Sent Messages" field for private messaging.
I'd like to know how I can do add a COUNT to my query to count the number of "receivers" in one thread.
Here's the basic gist of my table structure, (NOTE: the relational tie between the two tables is the 'message-id' in pm-info and the "id" in pm_data):
pm_info:
id message_id receiver_id is_read read_date
pm_data:
id date_sent title sender_id thread_id content
Here's my SELECT Statement:
SELECT pm_info.is_read, group_concat(DISTINCT receiver.usrFirst) as receiver_name,
pm_data.date_sent, pm_data.title, pm_data.thread_id, pm_data.id as data_id,
MAX(date_sent) AS thread_max_date_sent
FROM pm_info
INNER JOIN pm_data ON pm_info.message_id = pm_data.id
INNER JOIN tblUsers AS receiver ON pm_info.receiver_id = receiver.usrID
WHERE pm_data.sender_id = '$usrID'
GROUP BY pm_data.thread_id
ORDER BY thread_max_date_sent DESC
And it ouputs the recipients like this:
Message 1 - Recipients: John, David, Steve - thread_id = 1234
Message 2 - Recipients: Bill, David, John, Ed, Steve - thread_id = 1345
Basically, what I'd like to do is have the option to COUNT the recipients, so in the example above, "Message 1" would display three (3) recipients, and "Message 2" would display five (5) recipients.
This way, if I have a thread/message that was sent to 30 users, not all thirty names will be printed out.
Thanks!
Have you tried just replacing GROUP_CONCAT with COUNT? Like this:
COUNT(DISTINCT receiver.usrFirst) as receiver_count
Or better, use pm_info.receiver_id as in (which could potentially let you eliminate one of your joins):
COUNT(DISTINCT pm_info.receiver_id) as receiver_count
You could use a subquery to select up to 3 recipients per message, and then another subquery to concatenate their names:
select
( select group_concat(usrFirst separator ', ')
from tblUsers
where usrId in (
select usrId
from tblUsers
where usrId = pm_info.receiver_id
limit 3
)
) as receiver_name
from pm_info
....
You could move the count and group_concat into a concat statement.
... group_concat(DISTINCT receiver.usrFirst) as receiver_name ...
becomes
... concat('messages:', count(distinct receiver.usrFirst),'(',group_concat(DISTINCT receiver.usrFirst),')') as receiver_name ...
Related
I have db with few 1000 of contacts and would like to delete all duplicated records. Sql query I have at the moment works well ( when in records - tel, email, name1 are duplicated). Query deletes duplicates with lower id then last occurring record. But in some cases another fields of the record a filled in already (important ones will by title and name2). What i would like to achieve is for mysql to check if these fields are filled in and keep only the record with most information filed in.
My Query
<?php
$del_duplicate_contacts = $mysqli->query("
DELETE ca
FROM contacts ca
LEFT JOIN
(
SELECT MAX(id) id, name1, tel, email
FROM contacts
GROUP BY name1, tel, email
) cb ON ca.id = cb.id AND
ca.name1 = cb.name1 AND
ca.tel = cb.tel AND
ca.email = cb.email
WHERE cb.id IS NULL
");
?>
Example of table:
ID title name1 name2 tel email
1 John 01234 1#1.com
2 Mr John Smith 01234 1#1.com
3 John 01234 1#1.com
My query will delete record 1 and 2. I would like to keep only nr 2 and delete 1 and 3.
How I can achieve that? Is is possible? Or maybe i should involve PHP, if so How?
Use order by in group_concat, you can try this:
DELETE c1 FROM contacts c1
JOIN (
SELECT
substring_index(group_concat(id ORDER BY ((title IS NULL OR title ='') AND (name2 IS NULL OR name2 = '')), id DESC), ',', 1) AS id,
name1, tel, email
FROM contacts
GROUP BY name1, tel, email
) c2
ON c1.name1 = c2.name1 AND c1.tel = c2.tel AND c1.email = c2.email AND c1.id <> c2.id;
Demo Here
I have got solution using NOT EXIST clause instead of NOT IN
DELETE FROM contacts
WHERE NOT EXISTS (
SELECT 1 FROM (
SELECT * FROM (
SELECT * FROM contact AS tmp ORDER BY title DESC, name1 DESC, name2 DESC, email DESC, tel DESC )
as tbl group by name1)
as test WHERE contact.id= test.id
)
OOPS - This is the worst answer I have produced so far -- warning the top bit is dangerous, not sure why I did not include any group -- please continue to the bottom part, which is working now:
DELETE FROM contacts WHERE ID IN (
SELECT ID FROM (
SELECT DISTINCT a.ID
FROM contacts AS a
JOIN contacts AS b
ON a.name1 = b.name1
AND a.tel = b.tel
AND a.email = b.email
ORDER BY a.name1 DESC, a.name2 DESC, a.title DESC
LIMIT 1,100000
) AS tmp
)
LIMIT Must be 1, xxxx -- not 0, xxxx to keep the first one undeleted
As you cannot delete directly from the same table found in sub-query, just add a mask layer, so it is now tested working
Before Deleting, always double check what is going to be deleted:
SELECT * FROM contacts WHERE ID IN (
SELECT ID FROM (
SELECT DISTINCT a.ID
...
LIMIT 1,100000
) AS tmp
)
Apologies for the damage, luckily you did on a testing db
=====================================
Now here is the correct solution:
Let's check what is in the testing table:
According to question, we noticed only #2 #4 #5 is good to keep. And here is the result:
And we want to delete any records not in above list, before delete, we double check what is going to be deleted:
And we are ready to delete:
And here is the SQL, make sure you do on testing db first:
DELETE FROM contacts WHERE ID NOT IN (
SELECT * FROM (
SELECT ID FROM (
SELECT * FROM contacts ORDER BY title DESC, name1 DESC, name2 DESC, tel DESC, email DESC
) AS tmp
GROUP BY name1, tel, email
) AS del
)
This query will work without any ordering options !
DELETE FROM contacts where ID NOT IN (
SELECT ID FROM ( Select A.ID from contacts as A
join contacts AS B
ON A.name1 = B.name1
AND A.name2 = B.name2
AND A.tel = B.tel
AND A.email = B.email) As mytry);
My table for message inbox concept email is a primary in the table. I want to get the last message which is sent by particular email.
tbl_msg:
id
email
msg
Example Data:
Id email msg
1 xyz#gmail.com This is test
2 abc#gmail.com All is well
3 xyz#gmail.com This is test2
I want to get the last appearance of each email and msg
Id email msg
2 abc#gmail.com All is well
3 xyz#gmail.com This is test2
What I tried:
SELECT cnote.`id`,cnote.`email`,cnote.`msg` FROM `tbl_msg` cnote inner join (select distinct email,id from client_com group by email) as note
on cnote.id=note.id
Guide me if I wrong
Use This query:
SELECT m1.* FROM tbl_msgs m1 LEFT JOIN tbl_msgs m2 ON (m1.email = m2.email AND m1.id < m2.id) WHERE m2.id IS NULL;
This will join with the same table and with condition m1.id < m2.id , will get the latest one.
Another option is using subquery:
SELECT * FROM tbl_msgs WHERE id IN (SELECT MAX(id) FROM tbl_msgs GROUP BY email);
To get the latest messsage for each email address in the table you do not need a JOIN, you just need to ORDER and GROUP BY:
SELECT `id`, `email`, `msg`
FROM `tbl_msg`
GROUP BY `email`
ORDER BY `id` DESC
You can use this query:
SELECT id, email, msg FROM you_table
GROUP BY email
ORDER BY id DESC
With this one you will have only 1 row per email, and you'll have the last because is ordered by id DESC.
If you are running a PDO connection (The way I know to do it, not sure if there is a MySQLi way)
$theLastIdGiven = $handlerDbConnection->lastInsertId();
Or if you want to run it through a query, and get the last result, just add ORDER BY id DESC LIMIT 0, 1 and that will order the data by id in a descending form then get the first bit of data :)
Hope this helped you!
SELECT ID, email, Msg FROM tbl_msg WHERE ID IN (
(SELECT MAX(ID) FROM tbl_msg GROUP BY email))
hello all i am having this query in my webpage
$otherupdates=mysqli_query
($conn,"
SELECT f.sender,f.receiver,f.field,m.name
FROM friend f,members m
WHERE ((f.sender IN ($variable) OR f.receiver IN ($variable))
AND ( f.field='friends' || f.field='fan')
AND ( f.date > '$adatevariable' AND f.sender=m.id))
ORDER BY date DESC LIMIT 16"
);
$variable="'1','2','3'......"; // not important
$adatevariable=date('d', strtotime($today . ' - 2 day'));// not imp
what this query does is, fetches all the rows which matches the where clause and also fetches the name of sender (f.sender=m.id) but i want the name of receiver too by this query only i do not need another query..
is this possible please help
If you change your query to be:
select
f.sender,
f.receiver,
f.field,
senders.name AS sender_name,
recievers.name AS recievers_name
from
friend f
INNER JOIN members senders ON f.sender=senders.id
INNER JOIN members recievers ON f.receiver = recievers.id
where
(f.sender IN ($variable) or f.receiver IN ($variable))
and f.field IN ('friends', 'fan')
and f.date > '$adatevariable'
order by
date desc
limit 16
What this does is allow you to access the members table twice, once as senders and again as recievers. By joining these to your friend table on the sender and reciever fields, senders will have all the information of the sender and recievers will have all the information on the reciever.
i have two tables in my db .. one is Messages and the other is Contacts... both tables contain the mobile_number field.. in Contacts table there is no duplicate numbers ... but in Messages tables there are several duplicate numbers in mobileNo field...what i am doing is write now i am selecting the distinct mobile numbers from the Messages tables and then i am comparring the distinct numbers from the contacts table ... so if the messages_mobileNo is found on the contacts table then give me the contact name against the number otherwise messages_mobileNo ... so the problem is distinct not working .. i am not able to get the distinct numbers from the Messages table ... it is showing me the duplicate numbers
here is my query
SELECT DISTINCT Message.mobileNo,
Contact.mobileNo,
Contact.workNo,
Contact.homeNo,
Contact.other,
Contact.name,
Message.body,
Message.idTextMessage
FROM cakephp_db.textmessage AS Message
LEFT JOIN cakephp_db.contacts AS Contact ON (Message.user_id = Contact.user_id
AND ((Message.mobileNo = Contact.mobileNo)
OR (Message.mobileNo = Contact.workNo)
OR (Message.mobileNo = Contact.homeNo)
OR (Message.mobileNo = Contact.other)))
WHERE Message.User_id = 23
ORDER BY Message.idTextMessage DESC LIMIT 6
So your trying to get the last 6 messages of a person if I'm right?
SELECT Message.mobileNo,
Contact.mobileNo,
Contact.workNo,
Contact.homeNo,
Contact.other,
Contact.name,
Message.body,
Message.idTextMessage
FROM cakephp_db.textmessage AS Message
LEFT JOIN cakephp_db.contacts AS Contact ON Message.user_id = Contact.user_id
AND Message.mobileNo IN (Contact.mobileNo, Contact.workNo, Contact.homeNo, Contact.other)
WHERE Message.User_id = 23
GROUP BY Message.mobileNo
ORDER BY Message.idTextMessage DESC LIMIT 6
add a GROUP BY Message.mobileNo before your ORDER BY
If you are using MySQL SELECT DISTINCT with an ORDER BY added to it, you will have to create a temporary table where it can store its results. Here is a link offering more help:
MySQL DISTINCT Optimization
I have a hard nut to crack with joing 3 tables.
I have a newsletter_items, newsletter_fields and newsletter_mailgroups which I want to be joined to get a list of newsletters.
The newsletter_items contains the fields:
letter_id, letter_date, receivers, template, status
That can look like
1, 1234567899, 1,2 (comma separated), standard.html, 1
newsletter_fields contains the fields:
field_uid, field_name, field_content, field_letter_uid
That can look like
1, letter_headline, A great headline, 1
where field_letter_uid is the newsletter for which the field belongs to.
and newsletter_mailgroups contains the fields:
mailgroup_id, mailgroup_name, number_of_members
That can look like
1, Group1, 233
2, Group2, 124
3, Group3, 54
What I want is to combine these 3 tables to that I can get a list of all the newsletter like this:
Letter date | Letter headline | Receivers | Status
2008-01-01 12:00:00 | A great headline | Group1, Group 2 | 1
So in short I want my SQL query to join the 3 tables and in that process select the receivers from the mailgroup table and display them comma separated like Group1, Group 2
This what I got now
SELECT A.*, B.* FROM newsletter_items A, newsletter_fields B, WHERE B.field_letter_uid = A.letter_id AND field_name = 'letter_headline' AND A.template = '". $template ."';
But I can't seem to figure out how to get the mailgroups into that.
I recommend that you make your joins explicit.
It makes it easier to debug your query and to change inner with left joins.
There is absolutely never a good reason to use SQL '89 implicit join syntax.
SELECT ni.*
, nf.*
, group_concat(nm.mailgroup_name) as mailgroups
FROM newsletter_items ni
INNER JOIN newsletter_fields nf
ON (nf.field_letter_uid = ni.letter_id)
INNER JOIN newsletter_mailgroups nm
ON (find_in_set(nm.mailgroup_id, ni.receivers))
WHERE
nf.field_name = 'letter_headline'
ni.template = '". $template ."'
GROUP BY ni.letter_id;
Regarding your database design.
I recommend you normalize your database, that means that you move the comma separated fields into a different table.
So you make a table receivers
Receivers
----------
id integer auto_increment primary key
letter_id integer not null foreign key references newsletter_items(letter_id)
value integer not null
You then remove the field receiver from the table newsletter_items
Your query then changes into:
SELECT ni.*
, group_concat(r.value) as receivers
, nf.*
, group_concat(nm.mailgroup_name) as mailgroups
FROM newsletter_items ni
INNER JOIN newsletter_fields nf
ON (nf.field_letter_uid = ni.letter_id)
INNER JOIN newsletter_mailgroups nm
ON (find_in_set(nm.mailgroup_id, ni.receivers))
LEFT JOIN receiver r ON (r.letter_id = ni.letter_id)
WHERE
nf.field_name = 'letter_headline'
ni.template = '". $template ."'
GROUP BY ni.letter_id;
This change should also speed up your query significantly.
If it's allowed, why don't you create a new table called newsletter_item_receivers where you could store letter_id, receiver_id fields?
Having comma separated values in a field like this usually means you're missing a table :)
Edit:
By using CSV, you are making your life miserable when you want to retrieve an answer to "give me all newsletters that receiver_id=5 receives" :)
Here's a good answer to a similar question on SO: Comma separated values in a database field
Edit2:
If I understand your table relationships correctly then it would be something like this:
SELECT
a.letter_date,
b.receiver_id,
a.status
FROM newsletter_items_receivers b
LEFT OUTER JOIN newsletter_items a ON (a.letter_id = b.letter_id)
LEFT OUTER JOIN newsletter_mailgroups m ON (m.mailgroup_id = b.receiver_id)
NOTE! This query WILL NOT return a newsletter when there are no receivers of that newsletter.
If you need that functionality you can try something like this:
SELECT
x.letter_date,
y.mailgroup_name,
x.status
FROM (
SELECT
a.letter_date,
b.receiver_id,
a.status
FROM newsletter_items a
LEFT OUTER JOIN newsletter_items_rec b ON (b.letter_id = a.letter_id)) x
LEFT OUTER JOIN newsletter_mailgroups y ON (y.mailgroup_id = x.receiver_id)
I don't have access to SQL right now so I might have made some syntax errors (hopefully not logical ones :)).
As for why we are doing it like this, as #Konerak pointed out, you'd be well advised to read up on database normalization and why it's important.
You can start with this article from about.com, just glanced over it seems an OK read
http://databases.about.com/od/specificproducts/a/normalization.htm
Also, it would be good if you'd keep fields names the same across multiple tables.
For example you have letter_id in newsletter_items, but you have field_letter_uid in newsletter_fields. Just a thought :)
Try to use
SELECT A.*, B.*, group_concat(C.mailgroup_name SEPARATOR ',')
FROM newsletter_items A, newsletter_fields B, newsletter_mailgroups C
WHERE B.field_letter_uid = A.letter_id
AND field_name = 'letter_headline'
AND A.template = '". $template ."'
and find_in_set(c.mailgroup_id, A.receivers)
group by A.letter_id;