MySQL - Inner Join Count from two tables - php

I have two tables:
Category
| id | name | case |
Items
| id | name | categoryid | <-- this is the relation column
I'm tryig to show all categorys with your respective number of items, like this:
Category Name: Abstract [15 items]
I'm using this code:
$getcategory = mysqli_query($con, "
SELECT c.name
, c.id
, c.case
, i.id
, COUNT(i.categoriaid) AS photos
FROM category c
JOIN items i
ON c.id = i.categoriaid
WHERE i.id != ''
ORDER
BY c.id ASC
");
while ($showcategory = mysqli_fetch_array($getcategory)) {
echo '
<div class="category-container">
<div class="category-title">'.$showcategory['name'].'</div>
<div class="category-img-container">
<div class="img-stretch"><img src="'.$showcategory['case'].'" alt=""/></div>
</div>
<div class="category-count"><div><span class="destaque alto">[ '.$showcategory['photos'].' ]</span> telas</div></div>
</div>
';
}
But this don't work. What's wrong?

The query doesn't look right. There's an aggregate in the SELECT list, and no GROUP BY clause. If the query returns a result, it's going to be a single row. And it doesn't make sense to return non-aggregates in the SELECT list, if we're just going to return a COUNT.
Assuming that id is the PRIMARY KEY (or a UNIQUE KEY) in the category table, if sql_mode doesn't include ONLY_FULL_GROUP_BY, we might be able to get this to run:
SELECT c.name
, c.id
, c.case
, MIN(i.id) AS min_id
, MAX(i.id) AS max_id
, COUNT(i.categoriaid) AS photos
FROM category c
LEFT
JOIN items i
ON i.categoriaid = c.id
AND i.id <> ''
GROUP BY c.id
ORDER BY c.id
If sql_mode includes ONLY_FULL_GROUP_BY, we can either use aggregate functions (e.g. MIN or MAX) around c.name and c.case, or we can extend the GROUP BY clause to include those columns.
SELECT c.name
, c.id
, c.case
, MIN(i.id) AS min_id
, MAX(i.id) AS max_id
, COUNT(i.categoriaid) AS photos
FROM category c
LEFT
JOIN items i
ON i.categoriaid = c.id
AND i.id <> ''
GROUP
BY c.id
, c.name
, c.case
ORDER
BY c.id
Without a specification, we're just guessing. There could any number of things "wrong" with this statement. As a example list: invalid table references, invalid column references (is there a column name categoriaid in the items table? I took that from the SQL in the question).
I suggest you take the SQL writing to another environment and get the SQL written and tested. Then bring that statement back into your PHP code. (Divide and conquer.)
Also, in the PHP code, we can test whether query execution was successful, by testing the return from mysqli_query. If it's FALSE, then we know it wasn't successful, and we can retrieve the error with mysqli_error function.
$sql = "SELECT ... ";
if( !$getcategory = mysqli_query($con,$sql) ) {
// statement execution was not successful
die mysqli_error($con);
}

Related

PHP - Get only single image from many inside while loop [duplicate]

I read many threads about getting only the first row of a left join, but, for some reason, this does not work for me.
Here is my structure (simplified of course)
Feeds
id | title | content
----------------------
1 | Feed 1 | ...
Artists
artist_id | artist_name
-----------------------
1 | Artist 1
2 | Artist 2
feeds_artists
rel_id | artist_id | feed_id
----------------------------
1 | 1 | 1
2 | 2 | 1
...
Now i want to get the articles and join only the first Artist and I thought of something like this:
SELECT *
FROM feeds
LEFT JOIN feeds_artists ON wp_feeds.id = (
SELECT feeds_artists.feed_id FROM feeds_artists
WHERE feeds_artists.feed_id = feeds.id
LIMIT 1
)
WHERE feeds.id = '13815'
just to get only the first row of the feeds_artists, but already this does not work.
I can not use TOP because of my database and I can't group the results by feeds_artists.artist_id as i need to sort them by date (I got results by grouping them this way, but the results where not the newest)
Tried something with OUTER APPLY as well - no success as well.
To be honest i can not really imagine whats going on in those rows - probably the biggest reason why i cant get this to work.
SOLUTION:
SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
SELECT artist_id
FROM feeds_artists fa
WHERE fa.feed_id = f.id
LIMIT 1
)
WHERE f.id = '13815'
If you can assume that artist IDs increment over time, then the MIN(artist_id) will be the earliest.
So try something like this (untested...)
SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
SELECT
MIN(fa.artist_id) a_id
FROM feeds_artists fa
WHERE fa.feed_id = f.feed_id
) a
Version without subselect:
SELECT f.title,
f.content,
MIN(a.artist_name) artist_name
FROM feeds f
LEFT JOIN feeds_artists fa ON fa.feed_id = f.id
LEFT JOIN artists a ON fa.artist_id = a.artist_id
GROUP BY f.id
#Matt Dodges answer put me on the right track. Thanks again for all the answers, which helped a lot of guys in the mean time. Got it working like this:
SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
SELECT artist_id
FROM feeds_artists fa
WHERE fa.feed_id = f.id
LIMIT 1
)
WHERE f.id = '13815'
based on several answers here, i found something that worked for me and i wanted to generalize and explain what's going on.
convert:
LEFT JOIN table2 t2 ON (t2.thing = t1.thing)
to:
LEFT JOIN table2 t2 ON (t2.p_key = (SELECT MIN(t2_.p_key)
FROM table2 t2_ WHERE (t2_.thing = t1.thing) LIMIT 1))
the condition that connects t1 and t2 is moved from the ON and into the inner query WHERE. the MIN(primary key) or LIMIT 1 makes sure that only 1 row is returned by the inner query.
after selecting one specific row we need to tell the ON which row it is. that's why the ON is comparing the primary key of the joined tabled.
you can play with the inner query (i.e. order+limit) but it must return one primary key of the desired row that will tell the ON the exact row to join.
Update - for MySQL 5.7+
another option relevant to MySQL 5.7+ is to use ANY_VALUE+GROUP BY. it will select an artist name that is not necessarily the first one.
SELECT feeds.*,ANY_VALUE(feeds_artists.name) artist_name
FROM feeds
LEFT JOIN feeds_artists ON feeds.id = feeds_artists.feed_id
GROUP BY feeds.id
more info about ANY_VALUE: https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
I've used something else (I think better...) and want to share it:
I created a VIEW that has a "group" clause
CREATE VIEW vCountries AS SELECT * PROVINCES GROUP BY country_code
SELECT * FROM client INNER JOIN vCountries on client_province = province_id
I want to say yet, that I think that we need to do this solution BECAUSE WE DID SOMETHING WRONG IN THE ANALYSIS... at least in my case... but sometimes it's cheaper to do this that to redesign everything...
I hope it helps!
Here is my answer using the group by clause.
SELECT *
FROM feeds f
LEFT JOIN
(
SELECT artist_id, feed_id
FROM feeds_artists
GROUP BY artist_id, feed_id
) fa ON fa.feed_id = f.id
LEFT JOIN artists a ON a.artist_id = fa.artist_id
I want to give a more generalized answer. One that will handle any case when you want to select only the first item in a LEFT JOIN.
You can use a subquery that GROUP_CONCATS what you want (sorted, too!), then just split the GROUP_CONCAT'd result and take only its first item, like so...
LEFT JOIN Person ON Person.id = (
SELECT SUBSTRING_INDEX(
GROUP_CONCAT(FirstName ORDER BY FirstName DESC SEPARATOR "_" ), '_', 1)
) FROM Person
);
Since we have DESC as our ORDER BY option, this will return a Person id for someone like "Zack". If we wanted someone with the name like "Andy", we would change ORDER BY FirstName DESC to ORDER BY FirstName ASC.
This is nimble, as this places the power of ordering totally within your hands. But, after much testing, it will not scale well in a situation with lots of users and lots of data.
It is, however, useful in running data-intensive reports for admin.
For some database like DB2 and PostgreSQL, you have to use the key word LATERAL for specifying a sub query in the LEFT JOIN : (here, it's for DB2)
SELECT f.*, a.*
FROM feeds f
LEFT JOIN LATERAL
(
SELECT artist_id, feed_id
FROM feeds_artists sfa
WHERE sfa.feed_id = f.id
fetch first 1 rows only
) fa ON fa.feed_id = f.id
LEFT JOIN artists a ON a.artist_id = fa.artist_id
I know this is not a direct solution but as I've faced this and it's always a huge problem for me, and also using left join select etc. sometimes lead to a heavy process cost in database and server, I prefer doing this kind of left joins using array in php like this:
First get the data in range from second table and while you need just one row from second table, just save them with left join in-common column as key in result array.
SQL1:
$sql = SELECT artist_id FROM feeds_artists fa WHERE fa.feed_id {...RANGE...}
$res = $mysqli->query($sql);
if ($res->num_rows > 0) {
while ($row = $res->fetch_assoc()) {
$join_data[...$KEY...] = $row['artist_id'];
}
Then, get the base data and add detail of left join table from previous array while fetch them like this:
SQL2:
$sql = SELECT * FROM feeds f WHERE f.id {...RANGE...};
$res = $mysqli->query($sql);
if ($res->num_rows > 0) {
while ($row = $res->fetch_assoc()) {
$key = $row[in_common_col_value];
$row['EXTRA_DATA'] = $join_data[$key];
$final_data[] = $row;
}
Now, you'll have a $final_data array with desire extra data from $join_data array. this usually works good for date range data and like this.

MySQL Multi-Join Query

I have multiple tables I am trying to grab data from in a single query. I seem to be close to a solution but can not seem to get the data result I am expecting.
Examples of my tables are as follows (fields have been truncated):
Table c
id
name
abbreviation
Table mr (relationship table tat ties tables c and m together by ID)
id
c.id
m.id
Table m
id
Table cnt
id
c.id
Table cmp
id
cnt.id
active
What I WANT is all fields from C, all fields from M where m.id = c.id, all active (active = 1) id's from CMP that match on cnt.id.
My most recent query (after dozens of iterations) is:
SELECT c.id AS id
, c.name AS name
, c.abbreviation AS abbr
, c.active AS active
, c.last_modified AS last_modified
, c.modified_by AS modified_by
, mr.media_id
, mr.related_object_table
, mr.related_object_id
, m.orig_name AS img_name
, m.unique_name AS img_slug
, m.file_type AS confed_file_type
, m.file_size AS file_size
, COUNT('cmp.id') AS comps
FROM confederations AS c
LEFT JOIN media_relationships AS mr
ON mr.related_object_id = c.id
AND mr.related_object_table = 'confederations'
LEFT JOIN media AS m
ON m.id = mr.media_id
INNER JOIN countries AS cnt
ON cnt.confederations_id = c.id
INNER JOIN competitions AS cmp
ON cmp.countries_id = cnt.id
AND cmp.active = 1;
I am not proficient with Joins.
Basically, the result i am expecting is: For each Confederation (table C) I want that confederations name, abbreviation, active status (active), last modified date, modified by; from the Media Relationship table (table MR) I want the image id associated with that confederation so I can use that id to grab the image name and image slug for the confederations primary image from the Media table (M).
Now I also want the total number of Competitions (table CMP) for a given Confederation. Competitions are stored with a Country ID that is tied to the primary key ID of a country in the Countries Table (table CNT). Each Country in table CNT has a Confederations ID. So to get the total number of Competitions per Confederation I am 'trying' to get all Countries within their respective Confederation by CONFEDERATIONS_ID in table CNT, then foreach confederation I want select all the competitions from table CMP with matching COUNTRIES_ID from the group of country id's for that given confederation. (At this point i think i am confusing myself with how to get what i want)
Somehow I am getting the CORRECT NUMBER of competitions, but I am getting duplicate Confederations as results. For Example I am getting something similar to this (assume I have 3 different confederations with 2, 1, and 3 competitions respectively):
Competitions 1 : name 1 | abbreviation 1 | image 1 | total competitions = 2;
Competitions 1 : name 1 | abbreviation 1 | image 1 | total competitions = 1;
Competitions 1 : name 1 | abbreviation 1 | image 1 | total competitions = 3
What am i doing wrong?
Through trial and error, I actually solved this on my own. I came back to post my answer and see Degan's answer, and though it is written differently than mine I think its very close to what I ended up with:
SELECT
cnf.id AS confed_id, cnf.name AS confed_name, cnf.abbreviation AS
confed_abbr, cnf.active AS confed_active, cnf.modified_by AS
confed_mod_by, cnf.last_modified AS confed_last_mod,
COUNT(cnt.id) AS total_countries,
COUNT(cmp.id) AS total_comps,
mr.media_id, mr.related_object_table, mr.related_object_id,
mr.primary_img,
m.orig_name AS img_name, m.unique_name AS img_slug, m.file_type AS file_type
FROM confederations AS cnf
LEFT JOIN media_relationships AS mr
ON mr.related_object_id = cnf.id AND mr.related_object_table = 'confederations'
LEFT JOIN media AS m
ON m.id = mr.media_id
LEFT JOIN countries AS cnt
ON cnt.confederations_id = cnf.id AND cnt.active = 1
LEFT JOIN competitions AS cmp
ON cmp.countries_id = cnt.id AND cmp.active = 1
GROUP BY cnf.id
So farthis seems to be giving me results i can use. I am not certain if my choice of Left Join for all my joins is in fact giving me everything i need (it SEEMS to be) and whether this will omit/add records once the tables get larger. If anyone can point out a problem in my query and my choice of using Left Join as opposed to a combination of LEFT and INNER JOINs as Degan did, that would be helpful.
When aggregating you need to group by.
Perhaps this is close to what you are looking for:
SELECT c.id AS id
, c.name AS name
, c.abbreviation AS abbr
, m.orig_name AS img_name
, SUM('cmp.id') AS comps
FROM confederations AS c
LEFT JOIN media_relationships AS mr
ON mr.related_object_id = c.id
AND mr.related_object_table = 'confederations'
LEFT JOIN media AS m
ON m.id = mr.media_id
INNER JOIN countries AS cnt
ON cnt.confederations_id = c.id
INNER JOIN competitions AS cmp
ON cmp.countries_id = cnt.id
AND cmp.active = 1
GROUP BY c.id AS id
, c.name AS name
, c.abbreviation AS abbr
, m.orig_name AS img_name

Php, Mysql sort results based on number of records from another table

I'm joining two tables to display car brands. Here is the structure:
SELECT DISTINCT
b.title
FROM
brands as b
INNER JOIN items as i
ON i.brand_id = b.id
WHERE i.status = 1
ORDER BY COUNT(i.brand_id) DESC;
The above only produces one record. If I remove "ORDER BY COUNT(i.brand_id) DESC;" it displays all the records correctly.
I would like to sort result based on number of vehicles under each brand category. So for example if bmw category has the most car listed under, it should be the first one.
SELECT b.title
FROM brands as b
INNER JOIN items as i
ON i.brand_id = b.id
WHERE i.status = 1
GROUP BY b.title
ORDER BY COUNT(i.brand_id) DESC;
This should work for you.
I would use
SELECT b.title, count(i.brand_id)
FROM items i
LEFT JOIN brands as b
ON b.id = i.brand_id
WHERE i.status = 1
GROUP BY i.brand_id
ORDER BY COUNT(i.brand_id) DESC;
Your concern is the amount of cars in inventory. You want to break them down by how many of each brand you have. So, you're mainly concerned with the items tables and only need the brands table to get the information stored in the items table (brand name). Lastly, in order to get aggregate the number of brands of each, you must let MySQL know what you want in the GROUP BY.
I haven't used an EXPLAIN, but I would think the bottom query is more efficient.
You could join brands on an aggregate query:
SELECT b.title
FROM brands b
INNER JOIN (SELECT brand_id, COUNT(*) AS cnt
FROM items
WHERE status = 1
GROUP BY brand_id) i ON i.brand_id = b.id
ORDER BY cnt DESC;

MySQL joins - make sure one record is always found

I am trying to get a MySQL-query to return some activities on a city's page based on to parameters:
The city's shorttag
The chosem CategoryID's
MySQL Query:
SELECT
a.ActivityID as ActivityID,
a.Name as ActivityName,
a.Description as ActivityDescription,
a.Address as ActivityAddress,
a.Mail as ActivityMail,
a.Shorttag as ActivityShorttag,
a.Phone as ActivityPhone,
(SELECT ap.ThumbPath FROM ActivityPictures ap WHERE Acti_ActivityID = a.ActivityID ORDER BY MainPicture DESC, PictureID ASC LIMIT 1) as ActivityThumbPath,
c.CityID as CityID,
c.Name as CityName,
c.PostalCode as CityPostalCode,
c.Shorttag as CityShorttag,
c.ShortDescription as CategoryShortDescription,
c.LongDescription as CategoryLongDescription,
ca.CategoryID as CategoryID
FROM
Cities c
LEFT JOIN
Activities a
ON
c.CityID = a.City_CityID
LEFT JOIN
ActivityCategoryConn acc
ON
a.ActivityID = acc.Acti_ActivityID
LEFT JOIN
Categories ca
ON
acc.Cate_CategoryID = ca.CategoryID
AND
ca.CategoryID IN (2)
WHERE
c.Shorttag = 'city-shorttag'
ORDER BY
a.ActivityID desc
I want this to ALWAYS return at least one row with the info from "Cities c" - but if no activities are matching the CategoryID, the rest can just be NULL. If there is Activities these should of course return the number of rows matching.
How can I make this query ALWAYS return at least one record containing c.* - even if the array in the "IN"-statement doesnt contain a matched CategoryID?
You can fall back to a null by ORing with a IS NULL statement
AND
(
ca.CategoryID IN (2)
OR
ca.CategoryID IS NULL
)

PHP: 2 tables - Maximum id2

I want to get all id with the max id2 value.
I tried just to get the max id2 but then it will looks for the overall maximum value of id2 inside the table , but i want to get all maxiums of id.
So I got 2 tables - table news and table topics.
Everytime I create a news there will automaticly create a topic. Now I want to show all news - and the current number of replies. So first step - topicid = id.
and every topic got id and id2.
id is the topic id
and id2 is the reply id
so if i got topic (a) with 4 comments it would look like
(id(1),id2(1))
(id(1),id2(2))
(id(1),id2(3))
(id(1),id2(4))
now a new topic (b) with 6 comments
(id(2),id2(1))
(id(2),id2(2))
(id(2),id2(3))
(id(2),id2(4))
(id(2),id2(5))
(id(2),id2(6))
so i want to get ((id(1),id2(4)) and (id(2),id2(6)))
<?php
$news = "SELECT n.titel,n.datum,n.typ_news,n.news,n.verfasser,n.time,n.topicid,
t.id, t.id2 FROM news n LEFT JOIN topics t ON t.id = n.topicid
ORDER BY n.id DESC LIMIT 10 ";
$neuenews = mysql_query($news);
while ($dnews = mysql_fetch_array($neuenews))
{
echo " <div style='text-align:center;color:#FFFFFF;font-size: 24px;'> "
.$dnews['titel'].
"a";
}
Ehm this was the solution :
$dn1 = mysql_query('select c.id, c.name, c.description, c.position,c.bild,
(select count(t.id) from topics as t where t.parent=c.id and t.id2=1) as topics,
(select count(t2.id) from topics as t2 where t2.parent=c.id and t2.id2!=1) as replies
from categories as c group by c.id order by c.position asc');
Try this:
SELECT n.titel,n.datum,n.typ_news,n.news,n.verfasser,n.time,n.topicid, t.id, MAX(t.id2) AS id2
FROM news n
LEFT JOIN
topics t
ON t.id = n.topicid
GROUP BY n.topicid
ORDER BY n.id DESC LIMIT 10
It looks like the specified/desired result from the topics table is accomplished by a query like this:
SELECT t.id
, MAX(t.id2) AS max_id2
FROM topics t
GROUP BY t.id
OPTION 1
To get that result joined to rows in news, you could use that query as an inline view in your query in place of the topics table. For example:
SELECT n.titel
, n.datum
, n.typ_news
, n.news
, n.verfasser
, n.time
, n.topicid
, t.id
, t.max_id2
FROM news n
LEFT
JOIN ( SELECT m.id
, MAX(m.id2) AS max_id2
FROM topics m
GROUP BY m.id
) t
ON t.id = n.topicid
ORDER BY n.id DESC LIMIT 10
OPTION 2
If id is UNIQUE (or PRIMARY KEY) in news table, then you may be able to eliminate the inline view, do a join to topics, and do a GROUP BY n.id, something like this:
SELECT n.titel
, n.datum
, n.typ_news
, n.news
, n.verfasser
, n.time
, n.topicid
, t.id
, MAX(t.id2) AS max_id2
FROM news n
LEFT
JOIN topics t
ON t.id = n.topicid
GROUP BY n.id
ORDER BY n.id DESC LIMIT 10
Not clear enough. what are the columns id= 1 id2=1 etc.? Two tables each with 2 columns? No clue.
I'm thinking something on the order but No clue what you actually want.
SELECT `MAX(`id2`) as MAX,`id` FROM `News` WHERE `id2` = `MAX`

Categories