Complex MySQL Conversation Group Query? - php

I have the following tables.
conversations
| id |
------
1
and
conversationMembers
| id | conversationId | userId | email
---------------------------------------
1 1 2 null
2 1 null test#test.com
3 1 7 null
Basically, I'm trying to construct a MySQL query that returns a row from the conversations table by an exact match of conversationMembers.
So, here's some examples of expected returns.
Let's say we want a a conversation id for a conversation between the exact following members: userId 2, userId 7, email test#test.com - This would see that in the conversationMembers table there's rows with the same conversation id and the exact match across all members of that conversation id that we're searching for. It would return conversations row with id 1.
Here's another example. We want a conversation id for a conversation between userId 2 and userId 7. This would see that there's not a conversation exclusively between userId 2 and userId 7, so it would not return anything.
And a final example. Let's say we want userId 7 and userId 9, this would also see there's no exclusive conversation between these 2 user id's and would return nothing.
What's the best way to go about doing it? I've played with subqueries but everything I've come up with doesn't seem to be able to handle the exact matching situation - I was having issues with selecting conversations for example - on userId 2 and 7 only (which should return nothing) and was getting conversationId 1 back, even though I didn't specify I wanted a conversation with test#test.com email as a part of it. I should only have gotten conversationId 1 back for if I searched on an exact match of all members in for conversationId.

One method is to use group by and having. This is nice because it is flexible with regards to what can be expressed. So, your first example is:
select conversionid
from conversationMembers
group by conversionid
having sum(userId = 2) > 0 and
sum(userId = 7) > 0 and
sum(email = 'test#test.com') > 0;
The condition being summed counts the number of members that match. The > 0 means there is at least one. For the second condition, the clause would be:
having sum(userId = 2) > 0 and
sum(userId = 7) > 0 and
sum(userId not in (2, 7)) = 0;
or alternatively:
select conversionid
from conversationMembers
group by conversionid
having sum(userId = 2) > 0 and
sum(userId = 7) > 0 and
count(distinct userId) = 2;

Related

Yii2 MySQL Order By Relation Count

Table 1 - User:
ID Name
1 Jonh
2 Mark
3 King
Table 2 - Book:
ID user_idstatus ...
1 1 1 ...
2 1 1 ...
3 1 1 ...
4 2 1 ...
5 1 0 ...
6 1 0 ...
Code:
$query = User::find();
$query->joinWith('books');
$query->select(['user.*', 'COUNT(book.id) AS booksCount']);
$query->andWhere(['book.status' => 1]); // Problem Here!
$query->groupBy(['user.id']);
$query->orderBy(['booksCount' => SORT_DESC]);
Problem:
The query is working properly, but it's not returning the user with id = 3.
If I remove the line $query->andWhere(['book.status' => 1]); it works fine and return all users.
What should I change to list all users, even those who do not have a related book with status = 1?
I found the answer:
$query = User::find();
$query->joinWith(['books' => function ($subquery) {
$subquery->onCondition(['book.status' => 1]);
}]);
$query->select(['user.*', 'COUNT(book.id) AS booksCount']);
$query->groupBy(['user.id']);
$query->orderBy(['booksCount' => SORT_DESC]);
Instead of using COUNT(book.id), if the status of the book is either 0 or 1, you can use SUM(book.status) to get the number of books the user has. Then you can remove your WHERE book.status = 1 clause, and it will return all the users with the number of books they have, even in user 3's case where they have 0 books.
The Problem
The real problem is in your where clause. Because WHERE is processed before grouping and user 3 doesn't have any rows where book.status = 1, then the user has no rows which are included in the base query. Therefor the user isn't present during/after the grouping.
If you want a pretty good idea of a catch-all case where you can count rows based on a condition, using COUNT(CASE WHEN book.status IS NULL THEN NULL ELSE NULLIF(0,book.status) END) will also give you the result you're looking for. Because COUNT() will not count rows where the expression is NULL, this would allow the book.status to be -1, 1, 2, and any other number as long as it isn't 0 (or NULL in user 3's case), and still be included in the count.

Very slow mysql request + php

The ideia is: select all professions from a table. After this count how many professionals have the profession id in his category column. The category column store the professions id's separeted by commas (1, 2, 3, 420). The professions table has 604 rows.
I have the following piece of code:
<?php
$select_professions = mysql_query("SELECT * FROM professions");
if(mysql_num_rows($select_professions) == "0"){
echo"No registers in DB";
}else{while($row_professions = mysql_fetch_assoc($select_professions)){
$id = $row_professions['id'];
$count_profiles = mysql_query("SELECT
COUNT(FIND_IN_SET(professions.id, professional.category) > 0) AS profile_numbers
FROM
professions
INNER JOIN
professional
WHERE
FIND_IN_SET(professions.id,professional.category) > 0
AND
professions.id = $id
GROUP BY
professions.id");
$reg_profiles = mysql_fetch_assoc($count_profiles);
$numProfiles = $reg_profiles['profile_numbers'];
if($numProfiles > 4){
$style = 'display:none';
}else{
$style = '';
}
?>
My basic question is WHY this is so slow in Google Chrome?
Its taking like 15 seconds to load entire page with these results in a html table. In Edge or Firefox is taking about 5 seconds. I heard about Chrome using so much memory lately but I don't believe its soo slowly. In time this is the first time I use the FIND_IN_SET function on mysql. Is that may are slowing down the request? Anyone knows what I'm doing wrong or how can be optimized? This is actualy working but we know that 15 seconds of waiting makes the user give up or think that page is not working. I have to say too that if I do the same consultation on my HeidiSQL it takes 1 second.
I recommend to normalize this:
The category column store the professions id's separeted by commas (1,
2, 3, 420)
This is an n:n relationship. Your layout:
professionals:
id | catgeory
12 | 1,2,4,50
professions
id | desc
1 | prof A
2 | prof B
...
The string operations (split the list, normalize internal, query result in to temp, ...) is very cost intensive. Better:
professionals:
id | ...
12 | ..
profrelations
pid | cid
12 | 1
12 | 2
12 | 4
12 | 50
professions
id | desc
1 | prof A
2 | prof B
...
This would skip the COUNT(FIND_IN_SET(professions.id, professional.category) > 0) as a string operation (even twice):
SELECT COUNT(cid) AS profile_numbers from professionals, profrelations where
professionals.id = profrelations.pid AND profrelations.pid = $id;
etc. You might restructure the above query like this, as long as you won't actually need any column from professions.
You can add a unique index on the cols (pid, cid) in table profrelations as one professional actually can have one profession only one times.
Remark
The different behaviour in two browser might result from the server caching the query: You're doing the query with Chrome, it's slow, but the result gets cached. Next with FF, server will respond with the cached result as its the same query again - fast. Try it three times or the other way round, should then be the same in all browsers.
At first,
this operation COUNT(FIND_IN_SET(professions.id, professional.category) > 0) will not return result that you expected. Count in above expression will return 1 even if find_in_set returns 0.
Secondly, I wouldn't use join in this case at all. This tables have no direct relation by identifiers.
I would optimize the query as following:
SELECT COUNT(professions.id) AS profile_numbers FROM professions, professional
WHERE FIND_IN_SET(professions.id,professional.category) > 0 AND professions.id = $id
GROUP BY professions.id

Joining tables for a comment list and determine if the user already upvoted/downvoted the comment

I've been building a comment system for a project of my own and the way I currently determine if the currently logged user has voted (up or down) on the comment is not very.. smart. Right now I query the database everytime a comment is going to be displayed, which is not ideal for even 100+ comments per page. That means 100+ extra queries.
I tried using JOIN and LEFT JOIN and LEFT OUTER JOIN and I can't figure out how to do this by myself. Nothing that I tried gives me the result I need.
This is what I came up with but I can't figure out how to only merge the values where the user has voted.
$q_get_comments = $conn->prepare('
SELECT comments.id,
comments.parent,
comments.subpage,
comments.author_id,
comments.author,
comments.message_cut,
comments.regdate,
comments.points,
votes_comments.user_id,
votes_comments.user_name,
votes_comments.comm_id,
votes_comments.direction
FROM comments LEFT JOIN votes_comments
ON comments.id = votes_comments.comm_id
WHERE comments.subpage= :subpage
ORDER BY points DESC, regdate DESC');
$q_get_comments->execute(array(':subpage' => $sub_id));
and my tables are set up like this:
Comments
id parent subpage author_id author message message_cut ip regdate points up down
------------------------------------------------------------------------------------------
1 0 68 6 name msg <p>msg</p> x date 5 4 0
2 0 68 6 name msg <p>msg</p> x date 3 2 0
3 2 68 6 name msg <p>msg</p> x date 2 3 2
Votes
id user_id user_name comm_id direction
------------------------------------------
1 6 Chris 2 0
2 6 Chris 3 2
3 6 Chris 1 1
votes.comm_id matches comments.id and that's the way I tried to join the tables.
But I only want to join the results where user_id = 6 and still show all the comments for the subpage, and only add.. say.. the user_id and direction to the according result in comments
The result I need with direction's value at the end to determine the vote status.
id parent subpage author_id author message message_cut ip regdate points up down direction
------------------------------------------------------------------------------------------
1 0 68 6 name msg <p>msg</p> x date 5 4 0 0
2 0 68 6 name msg <p>msg</p> x date 3 2 0 NULL
3 2 68 6 name msg <p>msg</p> x date 2 3 2 2
If direction is NULL, or blank field maybe, the user did not vote on this comment, if it has a value he did, or something along these lines.
( in the code direction 0 means a revoked/neutral vote, 1 means upvote and 2 means downvote, so if direction exists I can know that the user voted on this comment in some way, and I can show the right html for his vote )
Thank you in advance for any help or tips you can give!
Simply make 2 queries (sudo code, just showing sql, not the actual db calls) to select comments and votes, then merge the results in php:
$comments = $db->prepare('SELECT * FROM comments WHERE subpage = :subpage');
$comments->execute(array('subpage' => $subpage_id));
$commentsById=array();
foreach($comments as $comment)
$commentsById[$comment['id']]=$comment
$votes = $db->prepare('SELECT * FROM votes WHERE user_id = :user_id AND comm_id IN (' . implode(",", array_keys($commentsById)) . ')');
$votes->execute(array('user_id' => $user_id)); // the user_id of the user
foreach($votes as $vote)
$commentsById[$vote['comm_id']]['direction'] = $vote['direction'];
var_dump($commentsById);

Getting a query result whiting a group by

I’m trying to get some result of a query. The table stock information about which page in a website visited by a user. So I can have in a row user that visit page "A" per example and a second row with the same user who visit the page "B". I want to do a query that select the user or users who visit the page "A" and "B". I can’t make a “and” in my where condition because there are just one column.
There is the table structure
ID | user_id | page | views
1 1 A 44
2 1 B 120
3 2 A 140
4 3 A 22
I have try this but doesn't work
SELECT users.name,users.id,users.email
FROM users
JOIN help_messages ON users.id = help_messages.id_user
WHERE (PAGE LIKE '%clubs%' or PAGE like '%profile%') and views >= 1
GROUP BY help_messages.id_user, help_messages.page
So any solutions please. Thanks
A general approach to this uses aggregation and a having clause:
select user_id
from table t
group by user_id
having sum(page = 'A') > 0 and sum(page = 'B') > 0;
The first condition in the having clause counts the number of rows that have an 'A' and only allows user_ids that have at least one such row. The second condition does the same for 'B'.
This is a general approach because it is easy to add additional pages. Also, if you want someone who visited 'A' but not 'B', you would just use:
having sum(page = 'A') > 0 and sum(page = 'B') = 0;

Number of 1 time,2 times, 3 times, n times column entries in table in php mysql

Have Table :
id userid type created_date
1 4353535 1 04-06-2014
2 4353536 0 06-06-2014
3 4353537 1 11-06-2014
4 4353538 1 11-06-2014
5 4353539 0 19-06-2014
7 4353541 1 01-06-2014
10 4353544 1 12-06-2014
11 4353535 1 06-06-2014
12 4353536 1 10-06-2014
13 4353537 1 12-06-2014
What I Want : (with in date range)
How much user have single time entry with type 1
How much user have double time entry with type 1
How much user have triple time entry with type 1
How much user have four time entry with type 1
How much user have n time entry with type 1
(PHP & MYSQL)
First get the count for each user, then from that group the entrycount you can get your expected output
select EntryCount, count(userid) from (Select userid, count(id) as Entrycount from myentry group by userid where type=1) as sq group by Entrycount
This will work try
First, get the number of entries per user. Then, get the number of users grouped by number of entries.
SELECT numEntries, COUNT(*) AS numUsers
FROM (
SELECT userid, COUNT(*) AS numEntries
FROM tablename
WHERE type = 1
GROUP BY userid
) tbl
GROUP BY numEntries
Simplified demo
Demo with your data

Categories