SQL - Query count for related table affected by other join - mysql

The tables in this query are as follows:
Post
User
Comment
Tag
Tagged_Post
Post_Category
I'm trying to query all relevant information about a post which has relations such as the User who made the post, comments on that specific Post, the many or none tags on the Post, and category that the Post is in.
Here is my SQL query:
$sql = "SELECT post.*, user.name, user.avatar, group_concat(DISTINCT tag.slug) as tags, post_category.slug as category, count(comment.post_id) as comments
FROM post
INNER JOIN user on user.id = post.user_id
INNER JOIN post_category on post_category.id = post.category_id
LEFT JOIN tagged_post on tagged_post.post_id = post.id
LEFT JOIN tag on tagged_post.tag_id = tag.id
LEFT OUTER JOIN comment on post.id = comment.post_id
GROUP BY post.id";
This outputs the following:
Array
(
[0] => Array
(
[id] => 1
[user_id] => 1
[category_id] => 1
[title] => Hi, I'm Bob Ross. AMA
[body] => That's right. I'm bob ross and this is my post. I'm not dead btw
[date_created] => 2018-09-02 11:45:29
[date_modified] =>
[name] => bob_ross
[avatar] =>
[tags] => painting,ama
[category] => news-and-politics
[comments] => 6
)
[1] => Array
(
[id] => 2
[user_id] => 2
[category_id] => 2
[title] => I'm Saul Goodman!!
[body] => woohoo
[date_created] => 2018-09-02 12:12:12
[date_modified] =>
[name] => saul_goodman
[avatar] =>
[tags] =>
[category] => general-discussion
[comments] => 0
)
[2] => Array
(
[id] => 3
[user_id] => 3
[category_id] => 4
[title] => yo im jesse
[body] => test
[date_created] => 2018-09-02 12:24:45
[date_modified] =>
[name] => jesse_pinkman
[avatar] =>
[tags] => ama,painting
[category] => animals-and-nature
[comments] => 4
)
)
The amount of tags seems to be affecting the count on the comments. For instance, on the first post, there are 3 comments and 2 tags. The count for comments on post with ID of 1, is showing 6. If I were to add an additional tag on this post (3 total tags), then the comment count would display 9 (3 tags x 3 comments).
Can someone help me understand why this is happening?

The reason is that using multiple JOINs acts like a Cartesian product, so you get 2*3=6 rows for the group. When you apply count, you get 6 valid (non-null) values and that's your result.
To fix, use:
... COUNT(DISTINCT comment.comment_id) as comments

Related

Only get data when value matches 100% of multiple other values SQL

I have two tables that I join like this:
SELECT * FROM color_photo c
LEFT JOIN photos p
ON c.photo_id = p.id
WHERE c.color_id IN (4,9,11)
Above gives the following result:
Array
(
[id] => 4
[photo_id] => 4
[color_id] => 4
[path] => photos/419d7f6ce1043f472faf44ee517c3c67.jpg
[preview] => images/photos/previews/preview-9eba598915ea34de6d80b537a25610f3.jpg
[thumbnail] => images/photos/thumbnails/thumb-9eba598915ea34de6d80b537a25610f3.png
[name] => golden retriever mix dog walking in the forest.jpg
[extension] => jpg
[photographer_id] => 1
[popularity] => 0
[order] => 500
[created_at] => 2021-07-16 11:32:55
[updated_at] => 2021-07-16 11:32:55
)
Array
(
[id] => 2
[photo_id] => 2
[color_id] => 9
[path] => photos/604af943f1565e02de8f3d98202f6226.jpg
[preview] => images/photos/previews/preview-5fbba3dc932f4304712dfc65860679d1.jpg
[thumbnail] => images/photos/thumbnails/thumb-5fbba3dc932f4304712dfc65860679d1.png
[name] => 97903847_m.jpg
[extension] => jpg
[photographer_id] => 1
[popularity] => 8
[order] => 500
[created_at] => 2021-06-23 10:08:49
[updated_at] => 2021-07-16 15:01:01
)
Array
(
[id] => 3
[photo_id] => 3
[color_id] => 9
[path] => photos/9bc2dc45f3473252d4353446c8dbc168.jpg
[preview] => images/photos/previews/preview-9bc2dc45f3473252d4353446c8dbc168.jpg
[thumbnail] => images/photos/thumbnails/thumb-9bc2dc45f3473252d4353446c8dbc168.png
[name] => 94507149_m.jpg
[extension] => jpg
[photographer_id] => 1
[popularity] => 7
[order] => 499
[created_at] => 2021-06-23 10:09:46
[updated_at] => 2021-07-20 10:09:42
)
Array
(
[id] => 1
[photo_id] => 1
[color_id] => 11
[path] => photos/c3b2e455ded9f954fed6a4e039dc6cfa.png
[preview] => images/photos/previews/preview-c3b2e455ded9f954fed6a4e039dc6cfa.jpg
[thumbnail] => images/photos/thumbnails/thumb-c3b2e455ded9f954fed6a4e039dc6cfa.png
[name] => a2logo_opacity_big.png
[extension] => png
[photographer_id] => 1
[popularity] => 7
[order] => 500
[created_at] => 2021-06-23 10:08:21
[updated_at] => 2021-07-04 18:33:14
)
Array
(
[id] => 3
[photo_id] => 3
[color_id] => 11
[path] => photos/9bc2dc45f3473252d4353446c8dbc168.jpg
[preview] => images/photos/previews/preview-9bc2dc45f3473252d4353446c8dbc168.jpg
[thumbnail] => images/photos/thumbnails/thumb-9bc2dc45f3473252d4353446c8dbc168.png
[name] => 94507149_m.jpg
[extension] => jpg
[photographer_id] => 1
[popularity] => 7
[order] => 499
[created_at] => 2021-06-23 10:09:46
[updated_at] => 2021-07-20 10:09:42
)
The important rows for my question are photo_id and color_id. A photo can have multiple colors. As you can see photo_id: 3 is present 2 times both with a different color_id. These color_ids are posted to this query from another page, so the (4,9,11) are dynamic and can be any combination.
I want to be able to filter photos by color. So for example a photo of a dog has three colors, 4,9,11, the dog with photo_id 2 should show up. But when posting a color id that the dog photo does not have, for example 12, so you get 4,9,11,12 I want the dog to be removed from the result.
I know IN is not the correct SQL for this because it will just retrieve data that matches any of those color_ids. I want to retrieve only the photos that match all color_ids, and if there is one that doesn't match, remove this photo_id from the result entirely. How can I do this? I'm using MYSQL.
I've tried it like this:
SELECT * FROM color_photo c
LEFT JOIN photos p
ON c.photo_id = p.id
WHERE c.color_id = 4 AND c.color_id = 9
And
SELECT * FROM color_photo c
LEFT JOIN photos p
ON c.photo_id = p.id
WHERE c.color_id IN (4) AND c.color_id IN (9)
But both return no data.
A simple method for an exact match uses aggregation and filtering using GROUP_CONCAT():
SELECT p.*
FROM color_photo c LEFT JOIN
photos p
ON c.photo_id = p.id
GROUP BY p.id
HAVING GROUP_CONCAT(c.color_id ORDER BY c.color_id) = '1,4,9';
If you wanted a subset match (so, there could be more colors in the photo), I would instead recommend:
SELECT p.*
FROM color_photo c LEFT JOIN
photos p
ON c.photo_id = p.id
WHERE c.color_id IN (1, 4, 9)
GROUP BY p.id
HAVING COUNT(*) = 3;
In order to exclude a photo with 4, 9 and 4, 9, 11, 999 when searching for 4, 9, 11 you could use the following:
SELECT p.id
FROM photos p
INNER JOIN color_photo c ON p.id = c.photo_id
GROUP BY p.id
HAVING COUNT(*) = 3
AND COUNT(CASE WHEN c.color_id IN (4, 9, 11) THEN 1 END) = 3

Join 2 tables with different number of rows

I would like to join 2 tables (contact and contact_meta) in ONE sql query knowing that contact_meta has several rows
Contact
id fname lname email
1 Nick John njohn#gmail.com
2 Laura Pitt lpitt#gmail.com
Contact_meta
id_contact contact_meta_key contact_meta_value
1 Newsletter yes
1 Level weak
2 Newsletter yes
I tried
SELECT * FROM contact as c
JOIN contact_meta as cm ON c.id = cm.id_contact
but where there are more than one row in contact_meta, I only get the last one.
Array (
[0] => stdClass Object ( [id] => 1 [lname] => Nick [fname] => John [email] => njohn#gmail.com [contact_meta_key] => level [contact_meta_value] => weak )
[1] => stdClass Object ( [id] => 2 [lname] => Laura [fname] => pitt [email] => lpitt#gmail.com [contact_meta_key] => newsletter [contact_meta_value] => yes
);
Moreover, I would like [newsletter] => yes instead of [contact_meta_key] => newsletter [contact_meta_value] => yes
What I wish :
Array (
[0] => stdClass Object ( [id] => 1 [lname] => Nick [fname] => John [email] => njohn#gmail.com [level] => weak [newsletter] => yes)
[1] => stdClass Object ( [id] => 2 [lname] => Laura [fname] => pitt [email] => lpitt#gmail.com [newsletter] => yes
);
You need to pivot the meta table.
SELECT c.*,
MAX(IF(contact_meta_key = 'Newsletter', contact_meta_value, NULL)) AS newsletter,
MAX(IF(contact_meta_key = 'Level', contact_meta_value, NULL)) AS level
FROM contact AS c
LEFT JOIN contact_meta AS cm ON c.id = cm.id_contact
GROUP BY c.id
DEMO
The thing is today John has 2 entries in contact_meta but tomorrow it can be 5 and Laura 9, I store many datas in it. If it possible to get all meta datas contact from users in 1 SQL query.
I found a solution but I think not good, because 2 SQL queries (I use Wordpress) :
$user = $wpdb->get_results( "SELECT * FROM contact as c JOIN relation_browser_contact as rbc ON c.contact_id = rbc.id_contact JOIN browser as b ON rbc.id_browser = b.browser_id");
$fellows = array();
$i=0;
foreach ($user as $key => $value) {
$data=array();
foreach ($user[$i] as $key => $value) {
if ( Helper::encrypt_decrypt('decrypt', $value) ){
$data[$key] = Helper::encrypt_decrypt('decrypt', $value);
}else{
$data[$key] = $value;
}
$metas = $wpdb->get_results( "SELECT contact_meta_key, contact_meta_value FROM contact_meta WHERE id_contact = ".$user[$i]->contact_id );
foreach ($metas as $key => $value) {
$data[$value->contact_meta_key] = $value->contact_meta_value;
}
}
$fellows[] = $data;
$i++;
}
print_r($fellows);
In this case, I have all I want, but just wondering if SQL query can do it...

Join 2 MySQL Tables to list all posts and find users followed by a particular user (Logged INN User)

I have two tables in my project.
POSTS Table
p_id p_user_id p_title p_description
.................................................
1 1 Post 1 lorem ipsum * 1
2 1 Post 2 lorem ipsum * 2
3 2 Post 3 lorem ipsum * 3
4 3 Post 4 lorem ipsum * 4
FOLLOWERS Table
f_id f_following_users_id f_followed_users_id f_date
...................................................................
1 2 1 2018-01-25
2 2 3 2018-01-25
3 3 2 2018-01-25
Now I want to get list of all users depending on Logged in users id. The logged in users id is taken from $_SESSION['user_id'].
The result I want is like as follows.
First Case: if logged in users user_id is 1, ie, $_SESSION['user_id'] = 1, or following_users_id in 2nd table is 1, The result should be:
Array
(
[0] => Array
(
[p_id] => 1
[p_user_id] => 1
[p_title] => Post 1
[p_description] => lorem ipsum * 1
[f_id] =>
[f_following_users_id] =>
[f_followed_users_id] =>
[f_date] =>
)
[1] => Array
(
[p_id] => 2
[p_user_id] => 1
[p_title] => Post 2
[p_description] => lorem ipsum * 2
[f_id] =>
[f_following_users_id] =>
[f_followed_users_id] =>
[f_date] =>
)
[2] => Array
(
[p_id] => 3
[p_user_id] => 2
[p_title] => Post 3
[p_description] => lorem ipsum * 3
[f_id] =>
[f_following_users_id] =>
[f_followed_users_id] =>
[f_date] =>
)
)
last four fields of array should be blank as user with user_id haven't followed anyone
Second Case: if logged in users user_id is 2, ie, $_SESSION['user_id'] = 2, or following_users_id in 2nd table is 2, The result should be:
Array
(
[0] => Array
(
[p_id] => 1
[p_user_id] => 1
[p_title] => Post 1
[p_description] => lorem ipsum * 1
[f_id] => 1
[f_following_users_id] => 2
[f_followed_users_id] => 1
[f_date] => 2018-01-25
)
[1] => Array
(
[p_id] => 2
[p_user_id] => 1
[p_title] => Post 2
[p_description] => lorem ipsum * 2
[f_id] => 1
[f_following_users_id] => 2
[f_followed_users_id] => 1
[f_date] => 2018-01-25
)
[2] => Array
(
[p_id] => 3
[p_user_id] => 2
[p_title] => Post 3
[p_description] => lorem ipsum * 3
[f_id] =>
[f_following_users_id] =>
[f_followed_users_id] =>
[f_date] =>
)
)
First four fields of first 2 array should be filled with first row of followers table as user with user id has followed user with user_id 1. And 4 rows of 3rd post should be blank as user with user_id 2 haven't followed user with user_id 2**
Third Case: if logged in users user_id is 3, ie, $_SESSION['user_id'] = 3, or following_users_id in 2nd table is 3, The result should be:
Array
(
[0] => Array
(
[p_id] => 1
[p_user_id] => 1
[p_title] => Post 1
[p_description] => lorem ipsum * 1
[f_id] =>
[f_following_users_id] =>
[f_followed_users_id] =>
[f_date] =>
)
[1] => Array
(
[p_id] => 2
[p_user_id] => 1
[p_title] => Post 2
[p_description] => lorem ipsum * 2
[f_id] =>
[f_following_users_id] =>
[f_followed_users_id] =>
[f_date] =>
)
[2] => Array
(
[p_id] => 3
[p_user_id] => 2
[p_title] => Post 3
[p_description] => lorem ipsum * 3
[f_id] => 3
[f_following_users_id] => 3
[f_followed_users_id] => 2
[f_date] => 2018-01-25
)
)
First 2 arrays 4 last column should be blank as User_id 3 haven't followed those posts users. But third arrays last four column should be filled as user_id 3 has followed user_id 2.
I have searched for all solution but in vain. Maybe I couldn't find right post how to do this even on Stack Overflow.
The MySQL query I tried is:
SELECT * FROM posts AS p LEFT JOIN followers AS f ON p.p_user_id=f.f_followed_users_id WHERE f.f_followed_users_id = p_user_id AND f.f_following_users_id = 1 ORDER BY p.p_id
But I don't get the results I wanted. I tried with left join right join outer and inner joins. But no success. Can I join these 2 tables like I want?
I feel like you could try something like looping instead of joining tables as ur followers table will have only one row which satisfy your conditions.
so why dont you try like this:
1) get all posts from your database.
2) foreach post you retrieved, get data from followers table with ur conditions.
3) create a new array.
4) if step 2 return result, merge followers table data to new array along with post.
5) else merge post itself to new array.
and once you finish for each loop, try to print it and check data using,
echo "<pre>";
print_r($new_array);
exit;
try this and let me know if you need any further help

MySQL GROUP BY kills ORDER BY

I have a problem and there are already some similar questions but they didn't solve my problem.
I have several hotels and pictures in it. The pictures are ordered by a number. I want all hotels and the picture with the lowest number.
When I try following query I get the right order:
SELECT s25.entry_id AS id,
s25.value AS title,
s35.file AS picture,
s86.value AS picture_sort
FROM sym_entries_data_25 AS s25
LEFT JOIN sym_entries_data_34 AS s34 ON (s25.entry_id = s34.relation_id)
LEFT JOIN sym_entries_data_35 AS s35 ON (s34.entry_id = s35.entry_id)
LEFT JOIN sym_entries_data_86 AS s86 ON (s34.entry_id = s86.entry_id)
ORDER BY s86.value
I will get the Array
[0] => Array
(
[id] => 243
[title] => Hotel
[picture] => louis2.jpg
[picture_sort] => 1
)
[1] => Array
(
[id] => 243
[title] => Hotel
[picture] => louis1.jpg
[picture_sort] => 2
)
[2] => Array
(
[id] => 243
[title] => Hotel
[picture] => louis3.jpg
[picture_sort] => 3
)
...And so on
But I only want one result per hotel, so I tried a GROUP BY:
SELECT s25.entry_id AS id,
s25.value AS title,
s35.file AS picture,
s86.value AS picture_sort
FROM sym_entries_data_25 AS s25
LEFT JOIN sym_entries_data_34 AS s34 ON (s25.entry_id = s34.relation_id)
LEFT JOIN sym_entries_data_35 AS s35 ON (s34.entry_id = s35.entry_id)
LEFT JOIN sym_entries_data_86 AS s86 ON (s34.entry_id = s86.entry_id)
GROUP BY s25.value
ORDER BY s86.value
Then I only get one Result but a random one, not the first one:
[0] => Array
(
[id] => 243
[title] => Hotel
[picture] => louis3.jpg
[picture_sort] => 3
)
What could be the problem here?
when you are doing group by, you have no guarantee regarding the values of the columns you are not grouping by.
for example if you have a table with columns a and b, and data like:
a | b
-----
1 | 2
1 | 3
if you'll do select * from table group by a, you can get either 2 or 3 as the value of b.
if you only want the top result for the hotel, what you need to do is not group by, but take the result where picture_sort equals to the min(picture_sort)

sql select multiple rows in subselect

First things first, I know this is incorrect currently. I know this because it doesn't work. So with that said, any assistance in getting me closer to my desired result is appreciated.
I have a query:
$query = "SELECT cat.*,
prod.title AS product,
cat1.title AS category,
(SELECT cat2.*
FROM ic_store_catalog AS cat2
WHERE cat2.parentid = cat.parentid) AS children
FROM ic_store_catalog AS cat
LEFT JOIN ic_products AS prod
ON cat.productid = prod.productid
LEFT JOIN ic_product_categories AS cat1
ON cat.categoryid = cat1.categoryid
WHERE `storeid` = $storeid
AND `column` = $column
AND `parentid` = 0
ORDER BY `the_order` ASC";
This errors out giving me: Operand should contain 1 column(s) as the problem. This exists in the sub-select guaranteed.
Now what I need to achieve when I use print_r in PHP is an Array similar in structure to this:
[0] => Array
(
[catalogID] => 165
[storeID] => 0
[categoryID] => 7
[parentID] => 0
[productID] => 4
[title] =>
[column] => 1
[the_order] => 1
[cat_order] => 1
[category] => Cookies & Brownies
[children] => Array
(
[0] => Array (
[catalogID] => 166
[storeID] => 0
[categoryID] => 8
[parentID] => 7
[productID] => 5
[the_order] => 1
[cat_order] => 1
[category] => Brownies
)
)
)
Any help in getting me closer to this result is appreciated.
(SELECT cat2.*
FROM ic_store_catalog AS cat2
WHERE cat2.parentid = cat.parentid)
You should replace cat2.* by only one column that will represent children.