This question has been asked before but never really answered (at least the way I want to do it). I have three tables in my database, threads, responses and votes. I need them to share the id field only. So I can do something like
SELECT * FROM threads AS t JOIN responses AS r JOIN votes AS v
WHERE id = 15
and will only retrieve a thread a response or (exclusive) a vote record with id = 15.
Is there a possible way to do this without creating an extra table? I'm not asking if it's a good idea (which is probably not), but if it's possible and how to do it.
If I understand right, you want to fetch thread, or response, or vote with id = 5. There is no opportunity to provide id field uniqueness across tables (i.e. prevent create response and vote with the same id value) in MySQL, but you can do it in your app.
And you can fetch needed row this way:
SELECT id, name, 'thread' AS `type` FROM threads WHERE id=5
UNION
SELECT id, name, 'response' AS `type` FROM responses WHERE id=5
UNION
SELECT id, name, 'vote' AS `type` FROM votes WHERE id=5
Your select will throw an error, because the id field in the where clause is ambiguous. I suppose there is an id field in all of these three tables.
SELECT * FROM threads AS t JOIN responses AS r JOIN votes AS v
WHERE id = 15
So even if the id field is "somehow shared" between these fields, you will have to make your where clause unambiguous:
SELECT * FROM threads AS t JOIN responses AS r JOIN votes AS v
WHERE t.id = 15
Furthermore, I suppose threads and responses have a many-to-one relationship which means there can be several response corresponding to a single thread. This means the responses table would have a thread_id field. The id field in the responses table could not have the same value as the id field in the corresponding thread record, since the id field must be unique in both tables.
The same logic goes to the relationship between responses and votes (a single response would have many votes).
Hence I conclude there is no possible way to share the id field between these three tables.
Related
This question already has an answer here:
How can I use FIND_IN_SET within a JOINED table?
(1 answer)
Closed 5 years ago.
Got 2 tables - contacts and messages:
contact_id | contact_email
1 | some#mail.com
2 | other#mail.com
3 | no#nono.com
message_id | message_recipients
1 | 1,2,3
2 | 3
message_recipients field contains ID(s) of contact(s) message was assigned to. Each message can have one or more IDs assigned, so they are separated by , symbol.
I need to show all contacts, and count of messages are assigned to each contact. Since message_recipients field may contain multiple IDs, I can't run a query like SELECT * FROM contacts, messages WHERE contacts.contact_id=messages.message_recipients because it won't work properly.
If I run SELECT * FROM contacts FULL JOIN messages, it returns many duplicated rows from contacts table. Sure thing, I can run SELECT * FROM contacts FULL JOIN messages GROUP BY contact_id, but this one returns only 1st message from messages table.
I know that in order to count how many messages each contact has assigned to, I will probably need to explode message_recipients field from each row into array and use code like if (in_array($contact_id, $message_recipients_array)) {$total++;} or similar. Now my main concern is how to all I need by writing as simple query as possible.
Fix your table structure. Do not store multiple values in one cell. See Normalization
For now, you can use FIND_IN_SET:
select c.contact_id,
c.contact_email,
count(*) no_of_messages
from messages m
join contacts c on find_in_set(c.contact_id, m.message_recipients) > 0
group by c.contact_id,
c.contact_email
But this will be slow as it can't use any index on the contact_id or message_recipients.
To actually fix the issues, don't include recipient_id in the messages table.
You should have stored single recipient in one row in a separate mapping table with many to many relation with (maybe) the following structure.
messages_recipients (
id int PK,
message_id int FK referring message(message_id),
message_recipient_id int FK referring contacts(contact_id)
)
Then all you had to do was:
select c.contact_id,
c.contact_email,
count(*) no_of_messages
from messages_recipients m
join contacts c on c.contact_id = m.message_recipient_id
group by c.contact_id,
c.contact_email
This query is Sargable and will be faster.
Fix your data structure! Storing ids in strings is a really bad idea. Why?
Numbers should be stored as numbers not strings.
SQL does not offer very good string functions.
Foreign key constraints should be properly expressed.
The query optimizer cannot use indexes or partitions.
SQL has a great method for storing lists: it is called a "table".
Sometimes, we are stuck with other people's really, really bad design decisions. MySQL does offer a method for doing what you want, find_in_set(). This is a hack to get around the short-comings of a bad data layout:
select . . .
from contacts c join
messages m
on find_in_set(c.contact_id, m.message_recipients) > 0
Imagine an 1:N relationship among tables t_parent and t_child. The PK of the parent table t_parent is requestid and of course this is the FK for t_child. Also, t_child has another field called usermail which contains en email address. I want to write a SELECT statement which will return for every record of t_parent:
1)requestid (easy)
2)the number of records in t_child assosiated with the corresponding requestid (by using count, I am getting only one row as output even if the records of t_parent are more)
3)the emails from field usermail of the associated (with current requestid) records of t_child, all together combined in a string.
Is the above SELECT possible?
Thank you
SELECT t_parent.id, COUNT(*), GROUP_CONCAT(t_child.usermail)
FROM t_parent
LEFT JOIN t_child
ON t_parent.id = t_child.parent_id
GROUP BY t_parent.id
I have a problem formulating a MySQL query to do the following task, although I have seen similar queries discussed here, they are sufficiently different from this one to snooker my attempts to transpose them. The problem is (fairly) simple to state. I have three tables, 'members', 'dog_shareoffered' and 'dog_sharewanted'. Members may have zero, one or more adverts for things they want to sell or want to buy, and the details are stored in the corresponding offered or wanted table, together with the id of the member who placed the ad. The column 'id' is unique to the member, and common to all three tables. The query I want is to ask how many members have NOT placed an ad in either table.
I have tried several ways of asking this. The closest I can get is a query that doesn't crash! (I am not a MySQL expert by any means). The following I have put together from what I gleaned from other examples, but it returns zero rows, where I know the result should be greater than zero.
SELECT id
FROM members
WHERE id IN (SELECT id
FROM dog_sharewanted
WHERE id IS NULL)
AND id IN (SELECT id
FROM dog_shareoffered
WHERE id IS NULL)
THis query looks pleasingly simple to understand, unlike the 'JOIN's' I've seen but I am guessing that maybe I need some sort of Join, but how would that look in this case?
If you want no ads in either table, then the sort of query you are after is:
SELECT id
FROM members
WHERE id NOT IN ( any id from any other table )
To select ids from other tables:
SELECT id
FROM <othertable>
Hence:
SELECT id
FROM members
WHERE id NOT IN (SELECT id FROM dog_shareoffered)
AND id NOT IN (SELECT id FROM dog_sharewanted)
I added the 'SELECT DISTINCT' because one member may put in many ads, but there's only one id. I used to have a SELECT DISTINCT in the subqueries above but as comments below mention, this is not necessary.
If you wanted to avoid a sub-query (a possible performance increase, depending..) you could use some LEFT JOINs:
SELECT members.id
FROM members
LEFT JOIN dog_shareoffered
ON dog_shareoffered.id = members.id
LEFT JOIN dog_sharewanted
ON dog_sharewanted.id = members.id
WHERE dog_shareoffered.id IS NULL
AND dog_sharewanted.id IS NULL
Why this works:
It takes the table members and joins it to the other two tables on the id column.
The LEFT JOIN means that if a member exists in the members table but not the table we're joining to (e.g. dog_shareoffered), then the corresponding dog_shareoffered columns will have NULL in them.
So, the WHERE condition picks out rows where there's a NULL id in both dog_shareoffered and dog_sharewanted, meaning we've found ids in members with no corresponding id in the other two tables.
Please forgive my ignorance here. SQL is decidedly one of the biggest "gaps" in my education that I'm working on correcting, come October. Here's the scenario:
I have two tables in a DB that I need to access certain data from. One is users, and the other is conversation_log. The basic structure is outlined below:
users:
id (INT)
name (TXT)
conversation_log
userid (INT) // same value as id in users - actually the only field in this table I want to check
input (TXT)
response (TXT)
(note that I'm only listing the structure for the fields that are {or could be} relevant to the current challenge)
What I want to do is return a list of names from the users table that have at least one record in the conversation_log table. Currently, I'm doing this with two separate SQL statements, with the one that checks for records in conversation_log being called hundreds, if not thousands of times, once for each userid, just to see if records exist for that id.
Currently, the two SQL statements are as follows:
select id from users where 1; (gets the list of userid values for the next query)
select id from conversation_log where userid = $userId limit 1; (checks for existing records)
Right now I have 4,000+ users listed in the users table. I'm sure that you can imagine just how long this method takes. I know there's an easier, more efficient way to do this, but being self-taught, this is something that I have yet to learn. Any help would be greatly appreciated.
You have to do what is called a 'Join'. This, um, joins the rows of two tables together based on values they have in common.
See if this makes sense to you:
SELECT DISTINCT users.name
FROM users JOIN conversation_log ON users.id = converation_log.userid
Now JOIN by itself is an "inner join", which means that it will only return rows that both tables have in common. In other words, if a specific conversation_log.userid doesn't exist, it won't return any part of the row, user or conversation log, for that userid.
Also, +1 for having a clearly worded question : )
EDIT: I added a "DISTINCT", which means to filter out all of the duplicates. If a user appeared in more than one conversation_log row, and you didn't have DISTINCT, you would get the user's name more than once. This is because JOIN does a cartesian product, or does every possible combination of rows from each table that match your JOIN ON criteria.
Something like this:
SELECT *
FROM users
WHERE EXISTS (
SELECT *
FROM conversation_log
WHERE users.id = conversation_log.userid
)
In plain English: select every row from users, such that there is at least one row from conversation_log with the matching userid.
What you need to read is JOIN syntax.
SELECT count(*), users.name
FROM users left join conversion_log on users.id = conversation_log.userid
Group by users.name
You could add at the end if you wanted
HAVING count(*) > 0
I have a table that stores various types of flags. Each flag type has a reasonId column. So you could flag a post as spam and use several reasons; as abusive and use several reasons, etc.
I need a query to return all spam flags (flagTypeId=1) on a single post and, in addition, an extra column to return the number of times a flag reason occurred (reasonId). I need the full record set because I need to tack the user data, thus returning a grouped result is not sufficient by itself:
Assuming I have a flags table with PK id, int flagTypeId, int postId, int reasonId, and userId, I wrote this:
SELECT id, flagTypeId, postId, userId, reasonId, COUNT(reasonId) reasonCount
FROM flags
WHERE flagTypeId = #flagTypeId AND postId = #postId
GROUP BY reasonId
ORDER BY reasonCount DESC
This query does not return the correct number of records. If I have four spam records, and two of those four share the same reasonId, only three records come back. I want all four records to come back with an extra column showing the number of times a reasonId occurred.
Any ideas how I can modify my query to achieve this?
SAMPLE INPUT/OUTPUT
Assuming three peope flagged the same post, and two of them used the same flag reason.
id flagTypeId postid reasonid userid count
1 1 1 1 1 2
2 1 1 1 2 2
3 1 1 2 3 1
Would this work:
SELECT id, flagTypeId, postId, flags.reasonId, x.reasonCount
FROM
flags
JOIN (SELECT reasonid, COUNT(*) AS reasonCount FROM flags WHERE flagTypeId = #flagTypeId AND postId = #postId GROUP BY reasonid) AS X
ON flags.reasonid = x.reasonid
WHERE
flagTypeId = #flagTypeId AND postId = #postId
I think you're going about it a little backwards. Keep in mind that, if you're already retrieving all the information in a collection of records, you already have the count of records, just by getting the size of the returned collection.
Tweak your query to remove the GROUP BY clause and COUNT column. Then, assuming it was something like PHP, and you fetched the results of the modified query into an array $flagReasons, you can just reference count($flagReasons) to get the count.