MySQL Query - Find_in_set on comma separated columns - mysql

I have an issue with a Query I'm conducting to do a search on a Database of events.
The purpose is about sports and the structure is:
id_event event_sport event_city
1 10 153
2 12 270
3 09 135
The table sports is like:
sport_id sport_name
1 Basketball
and the table cities is:
city_id city_name
1 NYC
So things get complicated, because my events table is like:
id_event event_sport event_city
1 10,12 153,270
2 7,14 135,271
3 8,12 143,80
and I have a multi-input search form, so that people can search for events in their city for multiple sports or for multiple cities. I'm using Chosen
The search resultant from Chosen is, for example:
City = 153,270 (if user selected more than one city)
Sport = 12 (if user only selected one sport, can be "9,15")
So what I need is to search for multiple values on cities and sports in the same column, separated by commas, knowing that sometimes we can be searching only for one value, if user didn't input more than one.
My current query is:
SELECT * FROM events e
LEFT JOIN cities c ON e.event_city=c.city_id
LEFT JOIN sports s ON e.event_sport=s.sport_id
WHERE FIND_IN_SET('1CITY', e.event_city) AND FIND_IN_SET('1SPORT', e.event_sport)
;
Which is good to search for one city, but if the user searches for two or more, I don't have way to show it.
Can you please help me?
Thanks in advance.

When the user inputs multiple cities and/or sports, split it on commas, and then the query should look like:
SELECT * FROM events e
LEFT JOIN cities c on e.event_city = c.city_id
LEFT JOIN sports s ON e.event_sport = s.sport_id
WHERE (FIND_IN_SET('$city[0]', e.event_city) OR FIND_IN_SET('$city[1]', e.event_city) OR ...)
AND (FIND_IN_SET('$sport[0]', e.event_sport) OR FIND_IN_SET('$sport[1]', e.event_sport) OR ...)
Using PHP you can build up those OR expressions with:
$city_list = implode(' OR ', array_map(function($x) { return "FIND_IN_SET('$x', e.event_city)"; }, explode(',', $_POST['cities'])));
Do the same to make $sport_list, and then your SQL string would contain:
WHERE ($city_list) AND ($sport_list)
As you can see, this is really convoluted and inefficient, I recommend you normalize your schema as suggested in the comments.

Related

All records not showing when using a WHERE statement with CONCAT

I have a list of plant, which can be filtered with a CONCAT, originally it was just text, but I have converted it to ID's instead. It was showing all records and could be filtered before I converted to ID's.
This involves 4 tables. (with example data) "" are not used in the fields, they are just to show you that it is a word.
plant
idplant example 1
plantname example "001 Forklift"
idplanttype1 example 1
idlocation1 example 1
iddepartment1 example 1
planttypes
idplanttype example 1
planttype example "Forklift Truck"
locations
idlocation example 1
location example "Preston"
departments
iddepartment example 1
department example "Waste Disposal"
Without the WHERE statement, it shows all records, including nulls. (but the filter doesn't work)
But With the WHERE statement, it is only showing complete records (all of which have no Null fields and the filter works) records with nulls do not show
The issue seems to be the CONCAT. (i've cleaned up the parentheses, but had to add a 1 to make the id's different)
if(isset($_POST['search'])) {$valueToSearch = $_POST['valueToSearch'];}
$sql = "
SELECT idplant, plantname, planttype, location, department
FROM plant
LEFT JOIN planttypes ON idplanttype1 = idplanttype
LEFT JOIN locations ON idlocation1 = idlocation
LEFT JOIN departments ON iddepartment1 = iddepartment
WHERE CONCAT(plantname, planttype, location, department) LIKE
'%".$valueToSearch."%'
ORDER BY plantname";
SOLUTION
The above code works, it was just missing.
WHERE CONCAT_WS
I'm new to Joins, so any help would be greatly appreciated.
Edit: Using Linux Server - Apache Version 2.4.46
Thanks in advance!
Your problem is probably blanks.
WHERE CONCAT(plantname, planttype, location, department)
LIKE '%001 Forklift Forklift Truck Preston Waste Disposal%'
won't find anything for example, as the concated strings result in '001 ForkliftForklift TruckPrestonWaste Disposal', not '001 Forklift Forklift Truck Preston Waste Disposal'.
You want blanks between the substrings, which is easiest to achieve with CONCAT_WS:
SELECT p.idplant, p.plantname, pt.planttype, l.location, d.department
FROM plant p
INNER JOIN planttypes pt ON pt.idplanttype = p.idplanttype1
INNER JOIN locations l ON l.idlocation = p.idlocation1
INNER JOIN departments d ON d.iddepartment = p.iddepartment1
WHERE CONCAT_WS(' ', p.plantname, pt.planttype, l.location, d.department)
LIKE '%001 Forklift Forklift Truck Preston Waste Disposal%'

Show multiple unmatched records in SQL

I have two tables. These two tables may have ID's that do not match. However, also they may have names or addresses that do not match as well. I need to be able to filter out not only ID's but first_name, last_name and street_1 from my list. I can do a JOIN on match ID's but sometimes they match but the other columns may have records that do not match which I would need to show.
Find ID's that do not match. If they do match see if any of the other fields do not match.
Here are my expect results:
id first_name_2 last_name_2 street_1 street_2
3 Teresa White 834 Green Ridge Hill 43 Arapahoe Park
6 Rebecca George 39157 Nelson Hill 7467 Acker Center
7 Ann Hawkins 341 Tennessee Street 8 Bunting Street
8 Joyce Moreno 0277 Bunker Hill Drive 6 Nancy Center
9 Kimberly Alvarez 57332 Di Loreto Lane 0437 Waubesa Avenue
ID 3 & 6 is in the list because the Last Name does not match. ID 7 is last name and street_1. ID 8 & 9 ID's do not match.
Here is my sample data for reference: http://sqlfiddle.com#!9/928568/2
I would do the following: Left joining and treating nulls as blank strings. If you have a legitimate empty string, street_2 for example, it may return false positives:
SELECT *
FROM information I1
LEFT JOIN information_2 I2 ON I1.id = I2.id
WHERE ( I1.first_name_2 <> ifnull(I2.first_name_2, '')
OR I1.last_name_2 <> ifnull(I2.last_name_2, '')
OR I1.street_1 <> ifnull(I2.street_1, '')
OR I1.street_2 <> ifnull(I2.street_2, '')
);
Hi I went through the sample data reference and i feel your requirement is To find all the tuples whose exact copy is not there in there in the second table
You can use the following SQL code I tested this on your feedle and it is giving the expected result
SELECT
i.id, i.first_name_2, i.last_name_2, i.street_1, i.street_2
FROM
information i
LEFT JOIN
information_2 i2
ON
i.id=i2.id AND i.first_name_2=i2.first_name_2 AND i.last_name_2=i2.last_name_2
AND i.street_1=i2.street_1 AND i.street_2 = i2.street_2
where
i2.id is null
There is also a simple way to do this if your database supports MINUS set operator just write
SELECT * FROM information
MINUS
SELECT * FROM information_2
and you will get the same answer

MySQL Query Is Too Slow Or Times Out

I am having a complete nightmare with my application. I haven't worked with datasets this big before, and my query is either timing out or taking ages to return something. I've got a feeling that my approach is just all wrong.
I have a payments table with a postcode field (among others). It has 40,000 rows roughly (one for each transaction). It has an auto-inc PRIMARY key and an INDEX on the postcode foreign-key.
I also have a postcodes lookup table with 2,500,000 rows. The table is structured like so;
postcode | country | county | localauthority | gor
AB1 1AA S99999 E2304 X 45
AB1 1AB S99999 E2304 X 45
The postcode field is PRIMARY and I have INDEXes on all the other fields.
Each field (apart from postcode) has a lookup table. In the case of country it's something like;
code | description
S99999 Wales
The point of the application is that the user can select areas of interest (such as "England", "London", "South West England" etc) and be shown payments results for those areas.
To do this, when a user selects the areas they are interested, I then created a temp table, with one row, listing ALL postcodes for the areas they selected. Then I LEFT JOIN it on to my payments table.
The problem is that if the user selects a big region (like "England") then I have to create a massive temp table (or about 1 million rows) and then compare it to the 40,000 payments to decide which to display.
UPDATE
Here is my code;
$generated_temp_table = "selected_postcodes_".$timestamp_string."_".$userid;
$temp_table_data = $temp_table
->setTempTable($generated_temp_table)
->newQuery()
->with(['payment' => function ($query) use ($column_values) {
$query->select($column_values);
}])
;
Here is my attempt to print out the raw query;
$sql = str_replace(['%', '?'], ['%%', "'%s'"], $temp_table_data->toSql());
$fullSql = vsprintf($sql, $temp_table_data->getBindings());
print_r($fullSql);
This is the result;
select * from `selected_postcodes_1434967426_1`
This doesn't look like the right query, I can't work out what Eloquent is doing here. I don't know why the full query is not printing out.
if you have too many result like 1 million, then use offset limit concept. Then it will save you'r time of the query. Also make sure in you select query you are filtering required fields only.( avoid select * from XXXX. use select A, B from XXX).

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 relational database query, correct terminology?

I think my issue with databases stems from not knowing the correct terminology to help me find an answer myself so I'll explain a generic version of what I'm doing and hopefully you can point some tutorials my way or give me some terms to check into.
Let's use an example of an employee directory.
Each employee can have multiple locations, multiple job duties which pull from a separate table. Example tables & some data, let's just focus on the multiple locations.
employees
Main employee data
- id (ex: 400)
- first (ex: John)
- last (ex: Doe)
locations
Unique list of locations
- id (ex: 3000)
- title (ex: FakeCo, LLC)
map_employees_locations
Tie ANY number of locations to an employee
- id
- employee_id (ex: 400)
- location_id (ex: 3000)
I'm struggling with the logic of how a single query would return something like this:
John
Doe
FakeCo, LLC
AnotherCo, LLC
It seems I would have to run a query to get the employee data, then within a nested query, grab locations associated with the employee id, etc... If there was only one location per employee, it would be a simple join, I just don't know how to handle the multiples.
Let me know if I'm way off, I'm just struggling.
You would join all of the tables together like this
select e.id,e.first,e.last,l.id,l.title
from employees e
inner join map_employees_locations el
on el.employee_id = e.id
inner join locations l
on el.location_id = l.id
where e.first = 'John'
AND e.last = 'Doe'
This would return data like this:
e.id e.first e.last l.id l.title
------------------------------------------------
1 John Doe 1 FakeCo, LLC
1 John Doe 2 AnotherCo, LLC
If you want only one line per employee you should maybe use group concat
select id, e.last, e.first
group_concat(l.title separator ',' ) as locations
from employee e
join location l on l.employee_id = e.id
group by e.id
Not sure about the syntax cos i'm more aware of postgres but this should do the job.