MySQL SELECT COUNT from one table and ID from another? - php

I'm trying to query information from two different tables, but I'm not figuring out how to do it best. As a disclaimer, I'm still learning MySQL/PHP, and I don't have control over the tables as they're set up - I'm trying to work with what I've got, since I can't add/change the tables. Below are the tables and the relevant attributes:
Table(attribute1, attribute2, ...);
------------------------------------
reports(id, reporter_id, added)
report_comments(comment_id, report_id, comment_text, commenter_id)
The reporter_id refers to the user who filed a report, and commenter_id is not the same person as reporter_id.
I want to get a count of how many report comments have, for example, the word "incorrect" in comment_text, for each reporter_id. I then want to make a table that shows each reporter's ID and the number of comments that are associated with that reporter's reports since "1383359439" (timestamp).
So far, I've not been very successful. My current query looks like this:
SELECT r.id, r.reporter_id,
(SELECT COUNT(*) FROM report_comments WHERE comment_text LIKE '%incorrect%' AND report_id = r.id) AS comments
FROM reports AS r
LEFT JOIN report_comments AS rc ON r.id = rc.report_id
WHERE r.added > 1383359439
GROUP BY r.reporter_id;
The resulting page, when I set the HTML table to list "reporter_id" followed by "comments", gives everyone who has filed a report since the time listed, but the count is either "0" or "1", with any reporter who has had "incorrect" in any report comment getting a "1" and those without "incorrect" getting "0":
Reporter1 | 0
Reporter2 | 1
Reporter3 | 0
Reporter4 | 1
Reporter5 | 1
The thing is, some reporters have had several comments with "incorrect" in them, and I want to get a count of each, and ONLY for those reporters (not ones who've never had an "incorrect" comment). For example:
Reporter2 | 2
Reporter4 | 17
Reporter5 | 3
I'm clearly missing something - what am I doing wrong?

You need to utilize grouping for this.
SELECT
r.reporter_id AS `reporter_id`,
COUNT(rc.report_id) AS `incorrect_count`
FROM reports AS r
INNER JOIN report_comments AS rc
ON r.id = rc.report_id
WHERE rc.comment_text LIKE '%incorrect%'
AND r.added > ?
GROUP BY `reporter_id`
Here ? represents the timestamp you are trying to compare against.
To answer your follow-up question, there are a couple of ways to do this. I might suggest use of SUM() in conjunction with CASE like this:
SELECT
r.reporter_id AS `reporter_id`,
SUM(
CASE WHEN rc.comment_text LIKE '%incorrect%'
THEN 1
ELSE 0
END CASE
) AS `incorrect_count`,
SUM(
CASE WHEN rc.comment_text LIKE '%fake%'
THEN 2
ELSE 0
END CASE
) AS `fake_count`,
FROM reports AS r
INNER JOIN report_comments AS rc
ON r.id = rc.report_id
WHERE
rc.comment_text LIKE '%incorrect%'
OR rc.comment_text LIKE '%fake%'
AND r.added > ?
GROUP BY `reporter_id`

It's something like this:
SELECT r.reporter_id, COUNT(*) comments
FROM reports AS r
INNER JOIN report_comments AS rc ON r.id = rc.report_id
WHERE r.added > 1383359439
AND comment_text LIKE '%incorrect%'
GROUP BY r.reporter_id;
I removed r.id since it doesn't make sense to have in this case as one reporter can have many reports (so multiple r.id).

You could try
SELECT r.id, COUNT(c.id) tot
FROM reports r INNER JOIN report_comments
ON r.id = c.report_id
AND c.comment_text LIKE '%incorrect%'
AND r.added > 1383359439
GROUP BY r.reporter_id

Related

Left join and count in same query return incorrect result

The problem is that if there is 0 comment or 1 comment the count shows 1 while the rest is working well means that 2, 3, etc working fine.
$sql = "SELECT blog.*,count(blog.id) as Total FROM blog left JOIN comment on comment.id = blog.id GROUP BY date desc";
Your query should look like this:
SELECT b.date, count(c.id) as Total
FROM blog b LEFT JOIN
comment c
ON c.id = b.id
GROUP BY b.date DESC;
This assumes that date comes from blog (which should be the case if your current query is working). The difference is that you are counting from the second table, not the first.
This does not use * for columns from blog. That is usually a very, very bad idea when using GROUP BY. The best practice (enforced by almost all SQL engines) is to only include unaggregated columns in the SELECT when they are in the GROUP BY.
Note: It seems very awkward that the same column id is used for the JOIN between two very different entities (blogs and comments).
i just change to count(comment.id) from count(blog.id)

MySQL - structuring query to discard common results

That title is really not useful, but its a complex question (in my head, maybe) ... anywho...
Say I have a MySQL table of Countries (A-Z all countries in the world) with id & name
Then I have a table where I am tracking which countries a user has been to: Like so:
Country Table
id name
1 india
2 luxembourg
3 usa
Visited Table
id user_id country_id
1 1 1
2 1 3
Now here's what I want to do, when I present the form to add to the list of visited countries I want country.id 1 & 3 to be excluded from the query result.
I know I can filter this using PHP ... which is something I have done in the past ... but surely there must be a way to structure a query in such a way that 1 & 3 are excluded from the returned results, like:
SELECT *
FROM `countries`
WHERE `id`!= "SELECT `country_id`
FROM `visited`
WHERE `user_id`='1'"
I suspect it has something to do with JOIN statements but I can't quite figure it out.
Bonus gratitude if someone can point me in the right direction with Laravel.
Thanks you all :)
Is this what you want?
select c.*
from countries c left join
visited v
on c.id = v.country_id and v.user_id = 1
where v.country_id is null;
You can also express this as a not in or not exists, but the left join method typically has pretty good performance.
The left outer join keeps all records in the first table regardless of whether or not the on clause evaluates to true. If there are no matches in the second table, then the columns are populated with NULL values. The where clause simply chooses these records -- the ones that do not match.
Here is another way of expressing this that you might find easier to follow:
select c.*
from countries c
where not exists (select 1 from visited where c.id = v.country_id and v.user_id = 1)
You can use your query like this.
SELECT *
FROM `countries` c LEFT JOIN `visited` v on c.id = v.country_id
WHERE v.`country_id` is null
AND v.`user_id` = 1
This is a operation of a LEFT JOIN. What is means is that I'm selecting all registries from the table countries that may or may not is on the table visited based on the ID of the country.
So it will bring you this group
from country from visited
1 1
2 no registry
3 3
So on the where condition (v.country_id is null) I'm saying: I only want the ones that on this left join operation is only on the country table but it is not on visited table so it brings me the id 2. Plus the condition that says that those registries on visited must be from the user_id=1
SELECT * FROM COUNTRIES LEFT JOIN
VISITED ON
countries.id = visited.country_id and visited.country_id NOT IN ( SELECT country_id FROM visited )
if i understand right maybe you need something like this ?

MySQL join multiple rows of query into one column

Table structure
client_commands (the "main" table):
id | completed
command_countries:
id | command_id | country_code
command_os:
id | command_id |OS
command_id on references the id column on client_commands.
Problem
I can add client commands with filters based on countries and operating systems. To try and normalise my DB structure, for each new command added:
Add a new row to client_commands
For each country, I add a new row to command_countries, each referencing client_command.id
For each OS, I add a new row to command_os, each referencing client_command.id
For one of the pages on my site, I need to display all client_commands (where completed = 0) as well as all the countries and operating systems for that command. My desired output would be something like:
id | countries | OS
1 | GB, US, FR| 2, 3
2 | ES, HU | 1, 3
I'm not sure how to go about doing this. The current query I'm using returns multiple rows:
SELECT a.id, b.country_code, c.OS
FROM client_commands a
LEFT JOIN command_countries b on b.command_id = a.id
LEFT JOIN command_os c on c.command_id = a.id
WHERE a.completed = 0
Any help?
Thanks!
EDIT: I forgot to mention (if you couldn't infer from above) - there can be a different number of operating systems and countries per command.
--
Also: I know I could do this by pulling all the commands, then looping through and running 2 additional queries for each result. But if I can, I'd like to do it as efficiently as possible with one query.
You can do this in one query by using GROUP_CONCAT
SELECT a.id,
GROUP_CONCAT(DISTINCT b.country_code SEPARATOR ' ,') `countries`,
GROUP_CONCAT(DISTINCT c.OS SEPARATOR ' ,') `os`,
FROM client_commands a
LEFT JOIN command_countries b on b.command_id = a.id
LEFT JOIN command_os c on c.command_id = a.id
WHERE a.completed = 0
GROUP BY a.id
if you want the ordered results in in a row you can use ORDER BY in GROUP_CONCAT like
GROUP_CONCAT(b.country_code ORDER BY b.command_id DESC SEPARATOR ' ,') `countries`
But be aware of that fact it has a limit of 1024 character to concat set by default but this can be increased b,steps provided in manual

MYSQL multiple grouping with join

SELECT
p.product,
q.format,
p.title
FROM
product p
JOIN info q ON p.product = q.product
WHERE p.user='$user'
GROUP BY p.product,q.format
I want to first group by 'product' from the product table but the also by format on the info table.
This is to not show duplicates of format and product. At the moment only the grouping by product is working.
Table - products
product | title
0 one
1 two
1 two - a
2 three
Table - product_details
product | title | format |
0 one home
1 two home
1 two - a home
2 three work
So for this example I want a list like:
product | title | format
0 one home
2 three work
Instead of:
product | title | format
0 one home
1 two home
2 three work
After your table structures were posted, I can see what your intent is, I believe. It looks like you are attempting to limit your output result set to those values for product.product which are never repeated. That is, values for product.product which have exactly one product.title.
For that, you can use a GROUP BY aggregation to return only those with COUNT(*) = 1 after the group is applied.
In this case, since you only expect one row back per product.product anyway, you can do the aggregation at the top level, not requiring a subquery. If you had joined in other tables, and ended up getting multiple rows back per product due to other one-to-many relationships, you would need to use the subquery method instead (to be portable anyway - MySQL would still probably allow this)
SELECT
p.product,
q.format,
p.title
FROM
products p
JOIN product_details q ON p.product = q.product
GROUP BY
p.product,
q.format,
p.title
HAVING COUNT(*) = 1
Here is a demonstration: http://sqlfiddle.com/#!2/72eda/6
If you did expect multiple rows back per p.product, such as if you joined in additional one-to-many related tables, an efficient way to handle that is to perform a JOIN against a subquery that imposes that limit in the HAVING clause. Those which don't meet the HAVING condition won't be returned in the subquery and therefore get discarded by the INNER JOIN.
SELECT
p.product,
q.format,
p.title
FROM
products p
INNER JOIN product_details q ON p.product = q.product
/* Subquery returns only product values having exactly 1 row */
INNER JOIN (
SELECT product
FROM products
GROUP BY product
HAVING COUNT(*) = 1
) pcount ON p.product = pcount.product
WHERE p.user = '$user'
http://sqlfiddle.com/#!2/72eda/2

Proper way to join tables in mysql to have a single result

So I have a mysql database with three tables that has three table I am trying to retrieve rows of data from content based on a condition on data phpro_tag_types
The structures of the tables is like so
phpro_tag_types
tag_type_id | tag_type_name
<pk>
phpro_tag_targets
tag_target_id | tag_id | sub_tag_id | tag_target_name | tag_type_id
<pk> | <FK> | <FK> | | <FK>
content
content_id | tag_target_id | bunch of other things|
<pk> | <fk> |
The relationships between the table is like so
content.tag_target_id : phpro_tag_targets.tag_target_id
1 : m //each tag_target_id is in content once
//and phpro_tag_targets many times
phpro_tag_targets.tag_type_id : phpro_tag_types.tag_type_id
M: 1 // there is many occurrences of tag_type_id
//in phpro_tag_targets and one occurrence in of tag_type_id in phpro_tag_type
(I hope I have explained this thoroughly enough using the correct terms if not I apologize, clearly I am still kind of green with this)
Now I have a SQL query that looks like this
SELECT *
FROM phpro_tag_types types
INNER JOIN phpro_tag_targets targets ON types.tag_type_id=targets.tag_type_id
INNER JOIN content c ON targets.tag_target_id = c.tag_target_id
WHERE types.tag_type_id=14
ORDER BY update_time DESC
Now this query works however not exactly quite as I intended. The problem is the resulting array that is returned has multiple instance of the same piece of content ie. a single content_id (I believe because the same tag_target_id exists in phpro_tag_targets multiple times) however I would only like the results array to only contain unique content_id's as this is the data I am actually outputting to users.
As a side note putting Distinct into the query also doesn't seem to work as there is no way to only make sure content is DISTINCT (at least I could find)
Any help with this would be greatly appreciated as I am kind of lost on how to achieve this
"I am trying to retrieve rows of data from content based on a condition on data phpro_tag_types"
Assuming you are trying to fetch fields of content. What about following IN() sub-query
SELECT *
FROM content c
WHERE tag_target_id IN (
SELECT DISTINCT tag_target_id
FROM phpro_tag_types types
INNER JOIN phpro_tag_targets targets ON types.tag_type_id=targets.tag_type_id
WHERE types.tag_type_id=14
)
ORDER BY update_time DESC;
BTW,
"As a side note putting Distinct into the query also doesn't seem to work as there is no way to only make sure content is DISTINCT (at least I could find)"
DISTINCT c.* does not make sense?
SELECT DISTINCT c.*
FROM phpro_tag_types types
INNER JOIN phpro_tag_targets targets ON types.tag_type_id=targets.tag_type_id
INNER JOIN content c ON targets.tag_target_id = c.tag_target_id
WHERE types.tag_type_id=14
ORDER BY update_time DESC;
There's a general trick to do a left outer join on the duplicating table in such a way that only one entry has null values and then limit the query to that row:
SELECT *
FROM phpro_tag_types types
INNER JOIN phpro_tag_targets targets ON types.tag_type_id=targets.tag_type_id
INNER JOIN content c ON targets.tag_target_id = c.tag_target_id
LEFT OUTER JOIN phpro_tag_targets t2 on targets.tag_target_id=t2.tag_target_id
AND t2.tag_id < targets.tag_id
WHERE types.tag_type_id=1
AND t2.tag_target_id IS NULL;
This seems a bit crazy but does work and in my experience is performant unless you're dealing with a 'very large' data set (whatever that means).
I'm not 100% sure what the semantics here are, but this query is assuming that you don't care what 'tag_id' you get, you just want to get any tag that matches the 'type_id', which looks to me to match your intent.
NOTE: this will cause duplicate column names in the '*' select, so you need to limit to types.*, targets.*, content.* or spell out the column names.

Categories