Using multiple MySQL joins to each get multiple values - mysql

I have a table "rate" that has the columns "id", "location", "local_rate", "long_rate".
Id is the integer value to differentiate the clients. The results are grouped by location. Each location group has an entry from each client. Each client has a different local_rate and long_rate. What I need to get is the location, the lowest local_rate and the client with that rate, and the lowest long_rate and the client with that rate. There will be a row for each location in the table. If there are multiple matching results for a rate it should just pick one and return the value and the client id with that value.
Results will be displayed as:
location1 | id(min_local_rate) | min_local_rate | id(min_long_rate) | min_long_rate
location2 | id(min_local_rate) | min_local_rate | id(min_long_rate) | min_long_rate
....
Here's my query so far:
SELECT local.location, local.id, local.local_rate, long.id, long.long_rate
FROM
(SELECT MIN(local_rate), location, id FROM rate GROUP BY location) local
JOIN
(SELECT MIN(long_rate), location, id FROM rate GROUP BY location) long
ON local.location = long.location
GROUP BY local.location;
What I'm getting back is the same local client id in every row and the same long client id in every row. The location, local rates, and long rates are correct.

First, you can calculate the minimum and maximum local rates for each location in one subquery. Then, you can join back to the original data to get the client ids.
If there is only one client with the min/max, then the following should do what you want:
select l.location, l.minlr, rmin.id, l.maxlr, rmax.id
from (select location, min(local_rate) as minlr, max(local_rate) as maxlr
from rate r
group by location
) l join
rate rmin
on rmin.location = l.location and rmin.local_rate = l.minlr join
rate rmax
on rmax.location = l.location and rmax.local_rate = l.maxlr;
If there are multiple rows that match, this will produce multiple rows for each location. You question doesn't specify what to do in this case.

Related

Display date and and id corresponding to maximal third value in SQL

I have 3 tables in my database as you can see below: Travel, a table which contains informations about drivers, Travel, which contains informations about travels, and DroveBy, a table which displays which driver drove which Ttravel (relation between the ID's). I would like to write a query which returns the ID of a driver, its name, and the date he traveled the most. In the example below, I would like to return:
1-Armand-2012-07-18
2-Elish-2012-06-18
3-Armand-2012-07-18.
Thanks a lot
You can take the max of difference between start_time and arrived_time like below query
select d.driver_id, d.name, t.travel_date
from(
select dB.driver_id,
max(timestampdiff(minute,start_time,arrival_time)) maxTime
from droveBy dB
join travel t on dB.travel_id = t.travel_id
group by dB.driver_id)t1
join travel t on t1.maxTime = timestampdiff(minute,t.start_time,t.arrival_time)
join droveBy dB on t1.driver_id = dB.driver_id and t.travel_id = dB.travel_id
join Driver d on dB.driver_id = d.driver_id;
This gave me below result
Hope this would help you out.

MySQL Exclude results from JOIN

I have two tables:
locations
---------------------
| id INT, PK, AI |
| x INT |
| y INT |
---------------------
signals
---------------------
| id INT, PK, AI |
| location_id INT |
| mac INT |
| strength INT |
---------------------
One location can (will) have up to maximum of 4 signals. Where location is X,Y point on an image and signal is Access Point in range of X,Y and it's signal strength.
Anyhow I have a method to which I provide a List of up to 4 MAC addresses and I need to find all locations that contain those 4 addresses.
Right now I'm doing this programmatically by:
1. Take top signal strength at X,Y and it's mac addresses
2. SELECT * FROM signals WHERE mac = ScanResult.BSSID
3. Create array of ID's from returned signals.location_id
4. In bulk select all locations if their ID is in the array along with all signals related to those locations
5. Complex loop in loop creates an array containing all locations that have in their relationship all the 4 mac addresses that I provided in the List and delete others.
This is incredibly messy and has redundant queries but since I'm not very good with SQL it was a patch that sort of worked.
Now I was wondering if I can do this using SQL alone and return locations that contain those 4 mac addresses.
I have:
SELECT locations.*, signals.* FROM locations INNER JOIN signals ON locations.id = signals.location_id;
I would be less confused if I only had to exclude locations where a relation would be 1:1 but here each locations has up to 4 signals. Is there a way I could provide an "array" to the query and say from that JOIN remove all locations that do not contain this number of mac addresses and these mac addresses.
You can use a having clause to ensure a location has all four MAC addresses:
SELECT l.id
, l.x
, l.y
FROM locations l
JOIN signals s
ON s.location_id = l.location_id
WHERE s.MAC in (1, 2, 3, 4)
GROUP BY
l.id
, l.x
, l.y
HAVING COUNT(DISTINCT s.MAC) = 4
Is there a way I could provide an "array" to the query
Yes, there is an operator for this. You can even provide another query as parameter
SELECT * FROM locations WHERE id IN (SELECT location_id FROM signals WHERE mac = ScanResult.level);
you can also use NOT IN to exclude elements which are contained in the list
Andomar's solution is correct based on the sample query. However, the data structure differs from the query, and the query doesn't really make sense. I think this is all that is necessary:
SELECT s.location_id
FROM signals s
WHERE s.MAC in (1, 2, 3, 4)
GROUP BY s.location_id
HAVING COUNT(DISTINCT s.MAC) = 4;

Codeigniter mysql query joining 2 values across 3 tables

Although I can get the joins to work, it doesn't return the lowest deal possible. The result should be lowest handset price possible with the lowest monthly cost. Currently I can get the lowest handset price, but not with the lowest monthly cost and similarly lowest monthly cost but not with the lowest priced handset.
I have 3 tables DEALS, TARIFFS and HANDSETS. When a customer comes to a manufacturer product listing page, it loads all products related to that manufacturer (in most instances around 40 products). What I would like is it to load the cheapest deal available for each product. Tariff table has over 4000 records. Handset table has over 3000 records. Deals table has over 1 million records.
There is a representation of the the 3 tables below with relevant columns:
Handset Table
==================================
id | manufacturer_id
-----------------------------------
100 1
Deals Table
==================================================
tariff_id | handset_id | handset_cost
--------------------------------------------------
15 100 44.99
20 100 114.99
Tariffs Table
==============================
id | monthly_cost
------------------------------
15 12.50
20 7.50
This is the query
$this->db->select('h.name, t.monthly_cost, d.handset_cost');
$this->db->from('aff_deals d');
$this->db->join('aff_tariffs t', 't.id = d.tariff_id', 'inner');
$this->db->join('aff_handsets h', 'h.id = d.handset_id', 'inner');
if (isset($manuid)) {
$this->db->where('h.manufacturer_id', $manuid);
}
$this->db->order_by('d.handset_cost ASC');
$this->db->order_by('t.monthly_cost ASC');
$this->db->group_by('h.name');
$query = $this->db->get();
$result = $query->result();
Unfortunately this returns handset cost of £114.99 and monthly cost of £7.50, when I need £44.99 and £12.50. The example above is a simple snap shot. I have tried MIN(d.handset_cost), sub-queries but cannot get the desired results. Somehow I need to be able to get the lowest price handsets, even if it's 0.00 (FREE), with its equivalent monthly cost. Any help would be most welcome.
According to your query you are misusing Mysql's GROUP BY extension without having any aggregate function this will lead your query to return indeterminate results for columns which are absent in group by taking your query as an example,columns t.monthly_cost, d.handset_cost values are indeterminate if you are specific to pick minimum row from deals table per handset then you can use below query
SELECT h.name,
t.monthly_cost,
d.handset_cost
FROM aff_deals d
INNER JOIN (SELECT handset_id,MIN(handset_cost) handset_cost
FROM aff_deals
GROUP BY handset_id) dd
ON d.handset_id = dd.handset_id AND d.handset_cost = dd.handset_cost
INNER JOIN aff_tariffs t ON t.id = d.tariff_id
INNER JOIN aff_handsets h ON h.id = d.handset_id
WHERE h.manufacturer_id =1
ORDER BY d.handset_cost ASC,t.monthly_cost ASC
See Demo
For active record query it will difficult to replicate above (subselect) query bu you can directly run this query through $this->db->query('your query')

how to get average of rows that have a certain relationship

I have a bunch of data that is stored pertaining to county demographics in a database. I need to be able to access the average of data within in the state of a certain county.
For example, I need to be able to get the average of all counties who's state_id matches the state_id of the county with a county_id of 1. Essentially, if a county was in Virginia, I would need the average of all of the counties in Virginia. I'm having trouble setting up this query, and I was hoping that you guys could give me some help. Here's what I have written, but it only returns one row from the database because of it linking the county_id of the two tables together.
SELECT AVG(demographic_data.percent_white) as avg_percent_white
FROM demographic_data,counties, states
WHERE counties.county_id = demographic_data.county_id AND counties.state_id = states.state_id
Here's my basic database layout:
counties
------------------------
county_id | county_name
states
---------------------
state_id | state_name
demographic_data
-----------------------------------------
percent_white | percent_black | county_id
Your query is returning one row, because there's an aggregate and no GROUP BY. If you want an average of all counties within a state, we'd expect only one row.
To get a "statewide" average, of all counties within a state, here's one way to do it:
SELECT AVG(d.percent_white) AS avg_percent_white
FROM demographic_data d
JOIN counties a
ON a.county_id = d.county_id
JOIN counties o
ON o.state_id = a.state_id
WHERE o.county_id = 42
Note that there's no need to join to the state table. You just need all counties that have a matching state_id. The query above is using two references to the counties table. The reference aliased as "a" is for all the counties within a state, the reference aliased as "o" is to get the state_id for a particular county.
If you already had the state_id, you wouldn't need a second reference:
SELECT AVG(d.percent_white) AS avg_percent_white
FROM demographic_data d
JOIN counties a
ON a.county_id = d.county_id
WHERE a.state_id = 11
FOLLOWUP
Q What if I wanted to bring in another table.. Let's call it demographic_data_2 that was also linked via the county_id
A I made the assumption that the demographic_data table had one row per county_id. If the same holds true for the second table, then a simple JOIN operation.
JOIN demographic_data_2 c
ON c.county_id = d.county_id
With that table joined in, you could add an appropriate aggregate expression in the SELECT list (e.g. SUM, MIN, MAX, AVG).
The trouble spots are typically "missing" and "duplicate" data... when there isn't a row for every county_id in that second table, or there's more than one row for a particular county_id, that leads to rows not included in the aggregate, or getting double counted in the aggregate.
We note that the aggregate returned in the original query is an "average of averages". It's an average of the values for each county.
Consider:
bucket count_red count_blue count_total percent_red
------ --------- ---------- ----------- -----------
1 480 4 1000 48
2 60 1 200 30
Note that there's a difference between an "average of averages", and calculating an average using totals.
SELECT AVG(percent_red) AS avg_percent_red
, SUM(count_red)/SUM(count_total) AS tot_percent_red
avg_percent_red tot_percent_red
--------------- ---------------
39 45
Both values are valid, we just don't want to misinterpret or misrepresent either the value.

Mysql subquery with joins

I have a table 'service' which contains details about serviced vehicles. It has an id and Vehicle_registrationNumber which is a foreign key. Whenever vehicle is serviced, a new record is made. So, for example if I make a service for car with registration ABCD, it will create new row, and I will set car_reg, date and car's mileage in the service table (id is set to autoincreament) (e.g 12 | 20/01/2012 | ABCD | 1452, another service for the same car will create row 15 | 26/01/2012 | ABCD | 4782).
Now I want to check if the car needs a service (the last service was either 6 or more months ago, or the current mileage of the car is more than 1000 miles since last service), to do that I need to know the date of last service and the mileage of the car at the last service. So I want to create a subquery, that will return one row for each car, and the row that I'm interested in is the newest one (either with the greatest id or latest endDate). I also need to join it with other tables because I need this for my view (I use CodeIgniter but don't know if it's possible to write subqueries using CI's ActiveRecord class)
SELECT * FROM (
SELECT *
FROM (`service`)
JOIN `vehicle` ON `service`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
JOIN `branch_has_vehicle` ON `branch_has_vehicle`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
JOIN `branch` ON `branch`.`branchId` = `branch_has_vehicle`.`Branch_branchId`
GROUP BY `service`.`Vehicle_registrationNumber` )
AS temp
WHERE `vehicle`.`available` != 'false'
AND `service`.`endDate` <= '2011-07-20 20:43'
OR service.serviceMileage < vehicle.mileage - 10000
SELECT `service`.`Vehicle_registrationNumber`, Max(`service`.`endDate`) as lastService,
MAX(service.serviceMileage) as lastServiceMileage, vehicle.*
FROM `service`
INNER JOIN `vehicle`
ON `service`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
INNER JOIN `branch_has_vehicle`
ON `branch_has_vehicle`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
INNER JOIN `branch`
ON `branch`.`branchId` = `branch_has_vehicle`.`Branch_branchId`
WHERE vehicle.available != 'false'
GROUP BY `service`.`Vehicle_registrationNumber`
HAVING lastService<=DATE_SUB(CURDATE(),INTERVAL 6 MONTH)
OR lastServiceMileage < vehicle.mileage - 10000
;
I hope I have no typo in it ..
If instead of using * in the subquery you specify the fields you need (which is always good practice anyway), most databases have a MAX() function that returns the maximum value within the group.
Actually, you don't even need the subquery. You can do the joins and use the MAX in the SELECT statement. Then you can do something like
SELECT ...., MAX('service'.'end_date') AS LAST_SERVICE
...
GROUP BY 'service'.'Vehicle_registrationNumber'
Or am I missing something?