MySQL Select Inner Join (Only Unique) - php

In my app, I have vehicles which are assigned to a specific class. In the query below I am trying to select all classes which have at least one vehicle in it that is in a certain area.
The code below works fine, however if there are multiple vehicles inside of the class that fit the location parameter, multiple instances of the class are returned in the results.
How can I structure this query so only one instance of every class is returned, regardless of how many vehicles inside of the class fit the paramater?
$get_veh = $pdo->prepare("SELECT * FROM tbl_car_class_category JOIN tbl_vehicles ON tbl_vehicles.veh_class_id = tbl_car_class_category.id_class_cat WHERE tbl_vehicles.veh_advanceLng between (:loc_long-:radius/cos(radians(:loc_lat))*69) and (:loc_long+:radius/cos(radians(:loc_lat))*69) and veh_advanceLat between (:loc_lat-(:radius/69)) and (:loc_lat+(:radius/69)) AND veh_status=:veh_status");
$get_veh->bindparam(":veh_status", $veh_status);
$get_veh->bindparam(":loc_lat", $loc_lat);
$get_veh->bindparam(":loc_long", $loc_long);
$get_veh->bindparam(":radius", $radius);
$get_veh->execute();

If the intent is return just rows from the category table, we could replace the JOIN with an EXISTS (correlated subquery)
For example:
SELECT c.*
FROM tbl_car_class_category c
WHERE EXISTS
( SELECT 1
FROM tbl_vehicles v
WHERE v.veh_class_id = c.id_class_cat
AND v.veh_advanceLng
BETWEEN (:loc_long-:radius/cos(radians(:loc_lat))*69)
AND (:loc_long+:radius/cos(radians(:loc_lat))*69)
AND v.veh_advanceLat
BETWEEN (:loc_lat-(:radius/69))
AND (:loc_lat+(:radius/69))
AND v.veh_status=:veh_status
)
If we also need to return a row from the vehicle table, we can use aggregation in an inline view, and a JOIN .
SELECT c.*
, g.*
FROM tbl_car_class_category c
JOIN ( SELECT v.veh_class_id
, MIN(v.id) AS min_id -- PK or unique identifier
FROM tbl_vehicles v
AND v.veh_advanceLng
BETWEEN (:loc_long-:radius/cos(radians(:loc_lat))*69)
AND (:loc_long+:radius/cos(radians(:loc_lat))*69)
AND v.veh_advanceLat
BETWEEN (:loc_lat-(:radius/69))
AND (:loc_lat+(:radius/69))
AND v.veh_status=:veh_status
GROUP BY v.veh_class_id
) m
ON m.veh_class_id = c.id_class_cat
JOIN tbl_vehicles g
ON g.id = m.min_id
AND g.veh_class_id = m.veh_class_id
ORDER BY ...
(NOTE: this assumes that id_class_cat is UNIQUE in tbl_car_class_category)

Have you tried LIMIT?
$get_veh = $pdo->prepare("SELECT * FROM tbl_car_class_category JOIN tbl_vehicles ON tbl_vehicles.veh_class_id = tbl_car_class_category.id_class_cat WHERE tbl_vehicles.veh_advanceLng between (:loc_long-:radius/cos(radians(:loc_lat))*69) and (:loc_long+:radius/cos(radians(:loc_lat))*69) and veh_advanceLat between (:loc_lat-(:radius/69)) and (:loc_lat+(:radius/69)) AND veh_status=:veh_status LIMIT 1");
http://www.mysqltutorial.org/mysql-limit.aspx
Or DISTINCT: https://dev.mysql.com/doc/refman/5.7/en/distinct-optimization.html

for get a single row for each class you can use a (fake eg: min) aggregation functio and group by
( do the absence of the table schema assuming that you have col1, col2, col3 instead of * )
$get_veh = $pdo->prepare("SELECT
tbl_car_class_category.id_class_cat
, min(col1)
, min(col2)
, min(col3)
FROM tbl_car_class_category
JOIN tbl_vehicles ON tbl_vehicles.veh_class_id = tbl_car_class_category.id_class_cat
WHERE tbl_vehicles.veh_advanceLng between (:loc_long-:radius/cos(radians(:loc_lat))*69)
and (:loc_long+:radius/cos(radians(:loc_lat))*69)
and veh_advanceLat between (:loc_lat-(:radius/69))
and (:loc_lat+(:radius/69)) AND veh_status=:veh_status
GROUP BY tbl_car_class_category.id_class_cat");

Related

Combine two mysql group queries

I'm making a web app to create tournaments and as i have learned PHP in the course of this project, so my skills aren't probably the best.
I have an identifier in my database day2_semifinal or day2_additional which basically identifies the type of semifinal.
So my first query is:
$numberquery = mysql_query("
SELECT *
FROM tourneyplayers
INNER JOIN results
on (resultid=r_id)
INNER JOIN players
ON (p_id=playerid)
INNER JOIN tourneys
on (T_Id=tourneyid)
WHERE tourneyid='$tourneyid' and
in_day2 = 1 and
day2_semifinal IS NOT NULL
GROUP BY day2_semifinal
ORDER BY agegroupid",$connection);
This will get me all the semifinal groups, i'll iterate over them and query all the players in group:
$semigroup = $group['day2_semifinal'];
$playerQuery = mysql_query("
SELECT *
FROM tourneyplayers
INNER JOIN results
on (r_id=resultid)
INNER JOIN players
on (p_id=playerid)
WHERE tourneyid='$tourneyid' AND
day2_semifinal = '$semigroup' and
in_day2 = 1
ORDER BY day2startplace",$connection);
Now after i've created tables and echoed all the data from player queries for day2_semifinal, i run another query:
$numberquery = mysql_query("SELECT * FROM tourneyplayers INNER JOIN results on (resultid=r_id) INNER JOIN players ON (p_id=playerid) WHERE tourneyid='$tourneyid' and in_day2 = 1 and day2_additional_nosemi IS NOT NULL AND day2_additional_nosemi <> 0 GROUP BY day2_additional_nosemi ORDER BY agegroupid",$connection);
Which is fairly similar to the first one, only thing different is day2_semifinal identifiers have changed to day2_additional. After that query, i'll again, iterate over the day2_additional_nosemi groups and query the players inside of them:
$additionalgroup = $group['day2_additional_nosemi'];
$playerQuery = mysql_query("SELECT * FROM tourneyplayers INNER JOIN results on (r_id=resultid) INNER JOIN players on (p_id=playerid) WHERE tourneyid='$tourneyid' AND day2_additional_nosemi = '$additionalgroup' and in_day2 = 1 ORDER BY day2startplace",$connection);
Now this works, but this creates an issue with ordering, since the first query orders them by agegroupid but only for players in day2_semifinal (and i'd like to have day2_additional players ordered together with day2_semifinal). If i run another query the previous data has already been echoed and ordering is not right. How could i concatenate two $numberquery queries in order to select players after them as well?
I'm answering my own question as i figured out a way to do this. What i did, was removed ORDER BYfrom both queries and created a new query which concatenated the two with UNION:
SELECT * FROM (
SELECT *
FROM tourneyplayers as tp1
INNER JOIN results as r1
on (tp1.resultid=r1.r_id)
INNER JOIN players as p1
ON (p1.p_id=tp1.playerid)
WHERE tp1.tourneyid=96 and
tp1.in_day2 = 1 and
r1.day2_semifinal IS NOT NULL
GROUP BY r1.day2_semifinal
UNION ALL
SELECT *
FROM tourneyplayers as tp2
INNER JOIN results as r2
on (tp2.resultid=r2.r_id)
INNER JOIN players as p2
ON (p2.p_id=tp2.playerid)
WHERE tp2.tourneyid=96 and
tp2.in_day2 = 1 and
r2.day2_additional_nosemi IS NOT NULL AND
r2.day2_additional_nosemi <> 0
GROUP BY r2.day2_additional_nosemi
) t ORDER BY t.agegroupid;

Selecting from comments and images table for each article in a while loop

I have an articles table that and I am displaying it in the homepage in a while loop. Inside the while loop I want to display the comments count and images count for each article.
It is working for me now, but it is three queries in total, I am trying to combine it in the first query and then just display all of them in one while loop. Here is what I am trying to achieve:Articles page
The current format I am following:
//a represents articles table, c represents comments table, i represents image table
$query = mysqli_query($conn, "SELECT a.a_id, a.title, a.datetime, a.user_id FROM a ORDER BY a.datetime DESC");
while($fetch = mysqli_fetch_assoc($query){
$imageQ = msqli_query($conn, "SELECT COUNT(image_path), image_path FROM i WHERE a_id = '$fetch['a_id']'");
$imageFetch = mysqli_fetch_assoc($imageQ);
$commentQ = mysqli_query($conn, "SELECT COUNT(comment_id) FROM c WHERE a_id = '$fetch['a_id']'");
$commentFetch = mysqli_fetch_assoc($commentQ);
}
I want to cram all of these queries into one single query that fetches the article and comments count and image count for each article and the first image.
The images and comments are separate dimensions of the data. So, you have to be careful about how to bring them together. In your case, you can aggregate the values before doing the joins:
SELECT a.a_id, a.title, a.datetime, a.user_id,
i.num_images, c.num_comments
FROM a LEFT JOIN
(SELECT a_id, COUNT(image_path) as num_images
FROM i
GROUP BY a_id
) i
ON i.a_id = a.a_id LEFT JOIN
(SELECT a_id, COUNT(comment_id) as num_comments
FROM c
GROUP BY a_id
) c
ON c.a_id = a.a_id
ORDER BY a.datetime DESC;
You can use mysql nested queries something like
SELECT a.,tab1.,tab2.* FROM a INNER JOIN (SELECT * FROM b ) as tab1 INNER JOIN (SELECT * FROM c) as tab2
Hope this can get you to get desired output.
Thanks

Loading details from multiple tables

I was using this:
SELECT res.*, rac.*, u.*, t.*, c.*
FROM Results res
INNER JOIN Races rac USING (RaceID)
INNER JOIN Users u USING (UserID)
INNER JOIN Teams t USING (TeamID)
INNER JOIN Cars c USING (CarID)
WHERE res.SeasonNumber = '$SeasonNumber' AND res.LeagueID = '$LeagueID' AND Position = '1' AND ResultConfirmed = '1'
ORDER BY Position ASC
Which works fine, but I've since realised I need to have CarID added in to Results table, but when I add it in, it gives me the error that the field is ambiguous. What I'd like to do is get the Car name from Cars table where CarID joins Cars and Results. When I try to do this though:
SELECT res.*, rac.*, u.*, t.*, c.*
FROM Results res
INNER JOIN Races rac USING (RaceID)
INNER JOIN Users u USING (UserID)
INNER JOIN Teams t USING (TeamID)
INNER JOIN Cars c USING (res.CarID)
WHERE res.SeasonNumber = '$SeasonNumber' AND res.LeagueID = '$LeagueID' AND Position = '1' AND ResultConfirmed = '1'
ORDER BY Position ASC
I get the following error:
You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use
near '.CarID) WHERE res.SeasonNumber = '1' AND res.LeagueID = '1' AND
Position = '1' ' at line 6
You can replace your USING clause with ON(),in USING() clause i guess you add the columns name that are same in other table you are joining but you placed the join in last and using alias res mysql won't allow this
INNER JOIN Cars c ON(res.CarID =c.CarID)
If you need to use USING() clause you need to adjust the join placements like
SELECT res.*, rac.*, u.*, t.*, c.*
FROM
Cars c
INNER JOIN Results res USING (CarID)
INNER JOIN Races rac USING (RaceID)
INNER JOIN Users u USING (UserID)
INNER JOIN Teams t USING (TeamID)
WHERE res.SeasonNumber = '$SeasonNumber' AND res.LeagueID = '$LeagueID' AND Position = '1' AND ResultConfirmed = '1'
ORDER BY Position ASC
But ON() clause is more readable form

Limiting a left join to returning one result?

I currently have this left join as part of a query:
LEFT JOIN movies t3 ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The trouble is that if there are several movies with the same name and same popularity (don't ask, it just is that way :-) ) then duplicate results are returned.
All that to say, I would like to limit the result of the left join to one.
I tried this:
LEFT JOIN
(SELECT t3.movie_name FROM movies t3 WHERE t3.popularity = 0 LIMIT 1)
ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The second query dies with the error:
Every derived table must have its own alias
I know what I'm asking is slightly vague since I'm not providing the full query, but is what I'm asking generally possible?
The error is clear -- you just need to create an alias for the subquery following its closing ) and use it in your ON clause since every table, derived or real, must have its own identifier. Then, you'll need to include movie_id in the subquery's select list to be able to join on it. Since the subquery already includes WHERE popularity = 0, you don't need to include it in the join's ON clause.
LEFT JOIN (
SELECT
movie_id,
movie_name
FROM movies
WHERE popularity = 0
ORDER BY movie_name
LIMIT 1
) the_alias ON t1.movie_id = the_alias.movie_id
If you are using one of these columns in the outer SELECT, reference it via the_alias.movie_name for example.
Update after understanding the requirement better:
To get one per group to join against, you can use an aggregate MAX() or MIN() on the movie_id and group it in the subquery. No subquery LIMIT is then necessary -- you'll receive the first movie_id per name withMIN() or the last with MAX().
LEFT JOIN (
SELECT
movie_name,
MIN(movie_id) AS movie_id
FROM movies
WHERE popularity = 0
GROUP BY movie_name
) the_alias ON t1.movie_id = the_alias.movie_id
LEFT JOIN movies as m ON m.id = (
SELECT id FROM movies mm WHERE mm.movie_id = t1.movie_id
ORDER BY mm.id DESC
LIMIT 1
)
you could try to add GROUP BY t3.movie_id to the first query
Try this:
LEFT JOIN
(
SELECT t3.movie_name, t3.popularity
FROM movies t3 WHERE t3.popularity = 0 LIMIT 1
) XX
ON t1.movie_id = XX.movie_id AND XX.popularity = 0
On MySQL 5.7+ use ANY_VALUE & GROUP_BY:
SELECT t1.id,t1.movie_name, ANY_VALUE(t3.popularity) popularity
FROM t1
LEFT JOIN t3 ON (t3.movie_id=t1.movie_id AND t3.popularity=0)
GROUP BY t1.id
more info
LEFT JOIN only first row
https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
Easy solution to left join the 1 most/least recent row is using select over ON phrase
SELECT A.ID, A.Name, B.Content
FROM A
LEFT JOIN B
ON A.id = (SELECT MAX(id) FROM B WHERE id = A.id)
Where A.id is the auto-incremental primary key.
LEFT JOIN (
SELECT id,movie_name FROM movies GROUP BY id
) as m ON (
m.id = x.id
)
// Mysql
SELECT SUM(db.item_sales_nsv) as total FROM app_product_hqsales_otc as db
LEFT JOIN app_item_target_otc as it ON
db.id = (SELECT MAX(id) FROM app_item_target_otc as ot WHERE id = db.id)
and db.head_quarter = it.hqcode
AND db.aaina_item_code = it.aaina_item_code AND db.month = it.month
AND db.year = it.year
WHERE db.head_quarter = 'WIN001' AND db.month = '5' AND db.year = '2022' AND db.status = '1'

How do I calculate avg and count on multiple columns involving many tables?

Here are my different tables:
computers (id,name)
monitors (id,name)
computer_monitor (id, computer_id,monitor_id)
useractivity (id,userid,timestamp,computer_monitor_id,ip)
useropinion (id,userid,computer_monitor_id,timestamp,rating)
user (id,name,email)
I want to search after the name of computer or monitor and get a row like this in return:
computer name and/or monitor name
computer_monitor_id
avg(rate)
count(useractivity)
avg(rate) is on that specific computer_monitor_id that matches the name, the same goes for count.
A computer with no connection to monitor has a value of 0 on monitor field in computer_monitor table and vice versa for monitor->computer.
useractivity and useropinion only contains the ID from computer_monitor table
As I understand, the query should be built around the computer_monitor table. All other tables connect to it, including those from which you want to obtain the stats.
SELECT
c.name AS ComputerName,
m.name AS MonitorName,
uo.AverageRating,
ua.ActivityCount
FROM computer_monitor cm
LEFT JOIN computer c ON c.id = cm.computer
LEFT JOIN monitor m ON m.id = cm.monitor
INNER JOIN (
SELECT computer_monitor_id, AVG(rating) AS AverageRating
FROM useropinion
GROUP BY computer_monitor_id
) uo ON cm.id = uo.computer_monitor_id
INNER JOIN (
SELECT computer_monitor_id, COUNT(*) AS ActivityCount
FROM useractivity
GROUP BY computer_monitor_id
) ua ON cm.id = ua.computer_monitor_id
Actually, as you can see, useropinion and useractivity are aggregated first, then joined. This is to avoid the Cartesian product effect when a computer_monitor.id matches more than one row both in useropinion and in useractivity.
<?php
$res_comp = mysql_query("select * from computers where name = '$name'");
$res_monitor = mysql_query("select * from monitor where name = '$name'");
if(mysql_num_rows($res_comp) > 0)
{
$row_comp = mysql_fetch_array($res_comp);
$comp_id = $row_comp['id'];
$res_result = mysql_query("select computers.name, computer_monitor.id, count(computer_monitor_id) from computers, computer_monitor, useractivity where computers.id = '$comp_id' AND computer_monitor_id = '$comp_id' AND useractivity.computer_monitor_id = '$comp_id'");
}
// repeat the same for monitor also. then use mysql_fetch_array to show your data.
?>
hopefully this will help.
This might do the trick...(one table with the computer/monitor relation ship, the other with a xref table threw me, and check the join types depending on your data)
SELECT computers.name AS ComputerName
, monitors.name AS MonitorName
, AVG(useropinion.rating) AS AvgRating
, COUNT(useractivity.id) AS ActivityCount
FROM computers
INNER JOIN computer_monitor ON (computers.id = computer_monitor.computer_id)
INNER JOIN useractivity ON (computers.id = useractivity.computer_id)
INNER JOIN monitors ON (computer_monitor.monitor_id = monitors.id)
INNER JOIN useropinion ON (computer_monitor.id = useropinion.computer_monitor_id) AND (monitors.id = useractivity.monitor_id)
INNER JOIN USER ON (useropinion.user_id = user.id) AND (useractivity.user_id = user.id)

Categories