MySQL Exclude results from JOIN - mysql

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;

Related

How do you check for matching value in third column based on distinct combinations of other two columns?

I have a table with Building name, such as A, B, C. These building names could be repeated. I have another column which has the floor. Such as floor 1 ,floor 2. Again there could be multiple floor 1 for every building. There is a third column which has object present such as television, bricks, fans.
I want to check for every combination of building with corresponding floors, such as Building A - floor 1, Building A- floor 2, if an object 'brick' exists then 'wall' must exist.
EXAMPLE DATA:
For every building and location, if 'WALL' exists , 'WINDOW', 'WINDOW1' or 'WINDOW2' must exist, ELSE FLAG
BUILDING LOCATION OBJECT
A FLOOR1 WALL
A FLOOR1 WINDOW
A FLOOR2 WALL
B FLOOR1 WALL
C FLOOR1 WALL
C FLOOR1 WINDOW
DESIRED OUPUT
BUILDING LOCATION ACTION
A FLOOR2 FLAG
B FLOOR1 FLAG
I have tried using GROUP BY, DISTINCT, WHERE EXISTS, but I cant seem to come up with the right logic.
You can group by building, location for the rows where object in ('WALL', 'WINDOW'):
select building, location, 'FLAG' action
from tablename
where object in ('WALL', 'WINDOW')
group by building, location
having count(distinct object) < 2
The condition count(distinct object) < 2 in the having clause returns combination of building, location where 'WALL' and 'WINDOW' do not both exist.
See the demo.
Results:
| building | location | action |
| -------- | -------- | ------ |
| A | FLOOR2 | FLAG |
| B | FLOOR1 | FLAG |
Or with NOT EXISTS:
select t.building, t.location, 'FLAG' action
from tablename t
where object in ('WALL', 'WINDOW')
and not exists (
select 1 from tablename
where building = t.building and location = t.location and object <> t.object
)
See the demo.
nested tables is what you want. Something like
select column_3
from (
select *
from table
where table.column_3="brick"
) as e
join table t on t.id = e.id
where table.column_3="window"
fyi: I reccomend you use this as a start, but for your exact case, id imagine this needs to be modified

MySQL retrieve data based on two matching row column [duplicate]

Project Aim :
We are developing bus timing Api where user will search for buses.
Following are my table structure
I have following tables
buses
id | bus_name
Description of table: Store all buses Names
routes
id | route_name
Description of table: Store All city names
stops
id | stop_name
Description of table: All stop names
stop_orders
id | route_id | stop_id | stop_order
Description of table: here i will assign stops for city and stop_order column help to identify which stop next to each other
bus_timing
id | stop_order_id | bus_id | bus_timing | trip | trip_direction
Description of table: Here i will assign buses for route stops along with time and trip and direction
Output Expecting:
When user search between source to destination with time then Api must return all buses list with time
if direct buses not there then interconnected buses should show
For example if user search between stop_8 to stop_18 with 01:00:00 to 12:00:00 then all buses list with time should show.if direct buses not there to travel between two stops then interconnected link buses list should show
Output what i got is
PHP compare associative array based on condition
Present return result issue is
It will return all buses even though if bus is only travel to stop_8 but not stop_18.But my result must return only those buses which will travel between two stops i mean it must fall between both stops .
Even i have no idea how to find interconnected buses list
When time range is long then there is chance of same bus will travel(trip and direction) multiple times
Updates
Still looking for answer .Right now given answer has some points so offered bounty
Because stop_id cannot be two different values in the same row.
Aggregation is one way to do what you want:
SELECT b.bus_name
FROM buses b JOIN
route_connect rc
ON rc.busid = b.id JOIN
stops s
ON s.id = rc.stop_id
GROUP BY b.bus_name
HAVING SUM( s.stop_name = 'Sydney' ) > 0 AND
SUM( s.stop_name = 'Melbourne' ) > 0;
This returns buses that have stops with the name of both cities.
Given that buses can have lots of stops, it might be more efficient to do:
SELECT b.bus_name
FROM buses b JOIN
route_connect rc
ON rc.busid = b.id JOIN
stops s
ON s.id = rc.stop_id
WHERE s.stop_name in ('Sydney', 'Melbourne')
GROUP BY b.bus_name
HAVING COUNT(DISTINCT s.stop_name) = 2;
Also if buses are not directly travel between two city then i need to show inter connected buses.
That's a massive problem in a class of problems called routing problems.. For it, you need a better tool: consider migrating or integrating PostgreSQL, and examining PgRouting specifically you'll likely want Dijkstra's Shortest Path. PgRouting runs atop the PostGIS extension.
Or, consider working on integrating with Esri.
Alternatively you can mess around with this, but I wouldn't advise it.
OQgraph (update)
From symcbean in the comments, you could use the "OQgraph database engine" to do this too. There is an example of shortest path here.

Better solution to MySQL nested select in's

Currently I have two MySQL tables
Properties
id name
1 Grove house
2 howard house
3 sunny side
Advanced options
prop_id name
1 Wifi
1 Enclosed garden
1 Swimming pool
2 Swimming pool
As you can see table two contains specific features about the properties
When I only have max 3 options the query below worked just fine. (maybe a little slow but ok) now things have expanded somewhat and i have a max of 12 options that it is possible to search by and its causing me some major speed issues. The query below is for 8 options and as you can see its very messy. Is there a better way of doing what I'm trying to achieve?
SELECT * FROM properties WHERE id in (
select prop_id from advanced_options where name = 'Within 2 miles of sea or river' and prop_id in (
select prop_id from advanced_options where name = 'WiFi' and prop_id in (
select prop_id from advanced_options where name = 'Walking distance to pub' and prop_id in (
select prop_id from advanced_options where name = 'Swimming pool' and prop_id in (
select prop_id from advanced_options where name = 'Sea or River views' and prop_id in (
select prop_id from advanced_options where name = 'Pet friendly' and prop_id in (
select prop_id from advanced_options where name = 'Open fire, wood burning stove or a real flame fire-place' and prop_id in (
select prop_id from advanced_options where name='Off road parking')
)
)
)
)
)
)
)
Like Mike Brant suggest I would consider altering your datamodel to a limit to set and creating a column for each of these in your properties table. But some times the boss comes: "We also need 'flatscreen tv'" and then you have to go back to the DB and update the scheme and your data access layer.
A way to move this logic somehow out if the database it to use bitwise comparison. This allows you to make simple queries, but requires a bit of preprocessing before you make your query.
Judge for yourself.
I've put everything in a test suite for you here sqlfiddle
The basic idea is that each property in your table has an id that is the power of 2. Like this:
INSERT INTO `advanced_options` (id, name)
VALUES
(1, 'Wifi'),
(2, 'Enclosing Garden'),
(8, 'Swimming Pool'),
(16, 'Grill');
You can then store a single value in your properties table buy adding up the options:
Wifi + Swimming Pool = 1 + 8 = 9
If you want to find all properties with wifi and a swimming pool you then do like this:
SELECT * FROM `properties` WHERE `advanced_options` & 9 = 9
If you just wanted swimming pool this would be it:
SELECT * FROM `properties` WHERE `advanced_options` & 8 = 8
Go try out the fiddle
You really need to consider a schema change to your table. It seems that advanced options in and of themselves don't have any properties, so instead of an advanced_options table that is trying to be a many-to-many JOIN table, why not just have a property_options table with a field for each "options". Something like this
|prop_id | wifi | swimming_pool | etc..
-----------------------------------
| 1 | 0 | 1 |
| 2 | 1 | 0 |
Here each field is a simple TINYINT field with 0/1 boolean representation.
To where you could query like:
SELECT * FROM properties AS p
INNER JOIN property_options AS po ON p.id = po.prop.id
WHERE wifi = 1 AND swimming_pool = 1 ....
Here you would just build your WHERE clause based on which options you are querying for.
There actually wouldn't be any need to even have a separate table, as these records would have a one-to-one relationship with the properties, so you could normalize these fields onto you properties table if you like.
Join back to the advanced_options table multiple times. Here's a sample with 2 (lather, rinse, repeat).
select o1.prop_id
from advanced_options o1
inner join advanced_options o2 on o1.prop_id = o2.prop_id and o2.name = "WiFi"
where o1.name = 'Within 2 miles of sea or river'
Could you do something like this?:
select p.*,count(a.prop_id) as cnt
from properties p
inner join advanced_options a on a.prop_id = p.id
where a.name in ('Enclosed garden','Swimming pool')
group by p.name
having cnt = 2
That query would get all the properties that have ALL of those advanced_options...
I would also suggest normalizing your tables by creating a separate table Called Advanced_option (id,name) where you store your unique Option values and then create a junction entity table like Property_x_AdvancedOption (fk_PropertyID, FK_AdvancedOptionID) that way you use less resources and avoid data integrity issues.

MySQL, how to repeat same line x times

I have a query that outputs address order data:
SELECT ordernumber
, article_description
, article_size_description
, concat(NumberPerBox,' pieces') as contents
, NumberOrdered
FROM customerorder
WHERE customerorder.id = 1;
I would like the above line to be outputted NumberOrders (e.g. 50,000) divided by NumberPerBox e.g. 2,000 = 25 times.
Is there a SQL query that can do this, I'm not against using temporary tables to join against if that's what it takes.
I checked out the previous questions, however the nearest one:
is to be posible in mysql repeat the same result
Only gave answers that give a fixed number of rows, and I need it to be dynamic depending on the value of (NumberOrdered div NumberPerBox).
The result I want is:
Boxnr Ordernr as_description contents NumberOrdered
------+--------------+----------------+-----------+---------------
1 | CORDO1245 | Carrying bags | 2,000 pcs | 50,000
2 | CORDO1245 | Carrying bags | 2,000 pcs | 50,000
....
25 | CORDO1245 | Carrying bags | 2,000 pcs | 50,000
First, let me say that I am more familiar with SQL Server so my answer has a bit of a bias.
Second, I did not test my code sample and it should probably be used as a reference point to start from.
It would appear to me that this situation is a prime candidate for a numbers table. Simply put, it is a table (usually called "Numbers") that is nothing more than a single PK column of integers from 1 to n. Once you've used a Numbers table and aware of how it's used, you'll start finding many uses for it - such as querying for time intervals, string splitting, etc.
That said, here is my untested response to your question:
SELECT
IV.number as Boxnr
,ordernumber
,article_description
,article_size_description
,concat(NumberPerBox,' pieces') as contents
,NumberOrdered
FROM
customerorder
INNER JOIN (
SELECT
Numbers.number
,customerorder.ordernumber
,customerorder.NumberPerBox
FROM
Numbers
INNER JOIN customerorder
ON Numbers.number BETWEEN 1 AND customerorder.NumberOrdered / customerorder.NumberPerBox
WHERE
customerorder.id = 1
) AS IV
ON customerorder.ordernumber = IV.ordernumber
As I said, most of my experience is in SQL Server. I reference http://www.sqlservercentral.com/articles/Advanced+Querying/2547/ (registration required). However, there appears to be quite a few resources available when I search for "SQL numbers table".

How to get the sum of a column from combined tables in mySQL?

I've been trying to write a mySQL-statement for the scenario below, but I just can't get it to work as intended. I would be very grateful if you guys could help me get it right!
I have two tables in a mySQL-database, event and route:
event:
id | date | destination | drivers |
passengers | description | executed
route:
name | distance
drivers contains a string with the usernames of the registered drivers in an event on the form "jack:jill:john".
destination contains the event destination (oh, really?) and its value is always the same as one of the values in the field name in the table route (i.e. the destination must already exist in route).
executed tells if the event is upcoming (0) or already executed (1).
distance is the distance to the destination in km from the home location.
What I want is to get the total distance covered for one specific user, only counting already executed events.
E.g., if Jill has been registered as a driver in two executed events where the distances to the destinations are 50km and 100km respectively, I would like the query to return the value 150.
I know I can use something like ...WHERE drivers LIKE '%jill%' AND executed = 1 to get the executed events where Jill was driving, and SUM() to get the total distance, but how do I combine the two tables and get it all to work?
Your help is very much appreciated!
/Linus
I haven't use MySQL for years, so sorry if I've got the syntax wrong, but something like this should do it:
In generic SQL:
select sum(distance) from route
join event on route.name = event.destination
where drivers like '%jill%' AND executed = 1
Or not using JOIN:
select sum(distance) from route, event
where drivers like '%jill%' AND executed = 1
and route.name = event.destination
Stuart's answer shows you how to get the sum of the column, but I just want to note that:
...WHERE drivers LIKE '%jill%'...
will return any event with a driver whose name contains the letters 'jill'.
Secondly, this database design doesn't seem to be normalized. You have driver names and route names repeated. If you normalize the database and have something like:
participant
id | name | role
event
id | date | route_id | description | executed
route
id | name | distance
participant_event
id | participant_id | event_id
then it would be a lot easier to work with the data.
Then if you wanted to implement a user search, you could make the query:
SELECT id FROM participant WHERE
name LIKE '%jill%' AND
role='driver';
Then if the query returns more than one result, let the user/application choose the correct driver and then run a SELECT SUM like Stuart's query:
SELECT SUM(r.distance) FROM route r
JOIN event e ON e.route_id=r.id
JOIN participant_event pe ON e.id=pe.event_id
JOIN participant p ON pe.participant_id=p.id
WHERE p.id=?;
Otherwise, the only way to ensure that you're only getting the total distance driven by one driver is to do something like this (assuming drivers is comma-delimited):
...WHERE LCASE(drivers)='jill' OR
drivers LIKE 'jill, %' OR
drivers LIKE '%, jill' OR
drivers LIKE '%, jill,%';