mySQL Search Statement with Multiple filters - php

I've been trying to figure this out for a few days now, but haven't been able to find a solution. Most likely due to the fact that I don't think I'm asking the right question.
Here is goes...
I am trying to create a search on my website of a list of attorneys.
Table 1
ID (primary)
Name
Category1 (fkey)
Category2 (fkey)
Category3 (fkey)
Category4 (fkey)
Category5 (fkey)
Location1 (fkey)
Location2 (fkey)
Location3 (fkey)
Location4 (fkey)
Location5 (fkey)
Table 2 - Locations
ID (primary)
Name
Table 3 - Categories
ID (primary)
Name
So Attorneys have multiple categories and multiple locations -> one to Many Relationship with both Table 2 and Table 3
First Question: Do I have Table 1 set up correctly? Do I need to have multiple location and category columns (ie location1, location2, location3, etc...) Or am I complicating this?
Next...
I want a checkbox style search on my site. A user can search by Location and/or by Category. And with checkbox they can choose multiple locations and/or categories. The checkbox values are the IDs of the locations and categories (not the names)
So three ways this can happen.
Search by locations ONLY
Search by categories ONLY
Search by categories within locations
I have two problems.
I can get scenarios 1 & 2 to work, but only if ONE checkbox is selected.
I have no idea how to even begin to get scenario 3 to work.
Here is what I have for scenario 1 & 2
$AttorneyLocation = $_POST['AttorneyLocation'];
for ($i="0"; $i<count($AttorneyLocation); $i++) {
if (!is_numeric($AttorneyLocation[$i])) {
$AttorneyLocation[$i]="";
}
if (empty($AttorneyLocation[$i])) {
unset($AttorneyLocation[$i]);
}
}
$AttorneyLocation = implode (" OR ", $AttorneyLocation);
$sqlCommand = "SELECT att_id, att_name, att_logo, att_addy, att_town, att_profile_url FROM attorneys WHERE att_location1='$AttorneyLocation' OR att_location2='$AttorneyLocation' OR att_location3='$AttorneyLocation' OR att_location4='$AttorneyLocation' OR att_location5='$AttorneyLocation'";
Again, this works but only when ONE checkbox is selected, it fails when two or more are selected. Basically it seems to only search the LAST checkbox that has been selected, ignoring the ones before it.
For scenario 3 - Again I'm just not sure where to start, how do I join together the category search within the location search?
If someone can point me in the right direction that would be great, thanks so much! This is my first try creating something like this!
Here is my form code if necessary - all created dynamically
<input type='checkbox' name='AttorneyCategory[]' value='$cat_id'> $category<br />
<input type='checkbox' name='AttorneyLocation[]' value='$loc_id'> $location<br />

As pointed out by #JonathanLeffler your structure for Table 1 is a perfect example of what not to do. Your Category* and Location* fields should be removed from Table 1 and replaced with two many-to-many tables - attorney_locations(attorney_id, location_id) and attorney_categories(attorney_id, category_id).
Start by amending your db structure and then we can address the other issues.
Next round -
<?php
foreach ($_POST['AttorneyLocation'] AS $key => $val) {
if (is_numeric($val)) {
$_POST['AttorneyLocation'][$key] = intval($val);
} else {
unset($_POST['AttorneyLocation'][$key]);
}
}
$AttorneyLocations = implode (',', $_POST['AttorneyLocation']);
foreach ($_POST['AttorneyCategory'] AS $key => $val) {
if (is_numeric($val)) {
$_POST['AttorneyCategory'][$key] = intval($val);
} else {
unset($_POST['AttorneyCategory'][$key]);
}
}
$AttorneyCategories = implode (',', $_POST['AttorneyCategory']);
$sqlCommand = 'SELECT DISTINCT att_id, att_name, att_logo, att_addy, att_town, att_profile_url FROM attorneys ';
if ($AttorneyLocations) {
$sqlCommand .= 'INNER JOIN attorney_locations ON attorneys.att_id = attorney_locations.attorney_id ';
}
if ($AttorneyCategories) {
$sqlCommand .= 'INNER JOIN attorney_categories ON attorneys.att_id = attorney_categories.attorney_id ';
}
$sqlCommand .= 'WHERE 1=1 ';
if ($AttorneyLocations) {
$sqlCommand .= "AND attorney_locations.location_id IN ($AttorneyLocations) ";
}
if ($AttorneyCategories) {
$sqlCommand .= "AND attorney_categories.category_id IN ($AttorneyCategories)";
}

You should print out what you get from your implode():
$AttorneyLocation = implode (" OR ", $AttorneyLocation);
It won't be valid SQL syntax, because you need it to read something like:
location = 'location1' OR location = 'location2' OR ...
So, you are not generating valid SQL, as you would surely see if you printed the SQL.
Your Table1 is a relational disaster. CategoryN or LocationN columns are SQL 'code smells'. It isn't clear what that table means; is it documenting 'AND' or 'OR' connections between the categories and locations, and is Category5 associated only with Location5 or is it also relevant to Location1? Without knowing what the data is supposed to mean, we can't reliably provide the correct design.
Given that each attorney may practice up to 5 categories of law, and may practice in up to 5 locations, then a better design for the tables is probably:
Attorney
ID (pkey)
Name
...other details...
AttorneyCategory
AttorneyID (fkey - references Attorney)
Category (fkey - references Category)
(AttorneyID, Category) (pkey)
AttorneyLocation
AttorneyID (fkey - references Attorney)
Location (fkey - references Location)
(AttorneyID, Category) (pkey)
This will actually be significantly easier to query, too, because you won't have to look up the same location name in each of five different columns, nor look up the same category in each of five different columns.

Well, since you are using PHP i just recommend you do an IF statement for each of the filters you want, maybe a few to make 2 or 3 filters work together. your database is set correctly, just create PHP if's with different queries and it should work.

Related

Many to many relationships - Moving data from many tables to a single table

I have a table with users and one with labels
A label can have many users and a user can have many labels, so a Many to Many relationship
A joining table is needed, that's why I have label_user
Below you can see pictures of what they contain with example data:
Users:
https://i.stack.imgur.com/E5E6O.png
Labels:
https://i.stack.imgur.com/1NFjq.png
label_user:
https://i.stack.imgur.com/tW2Uo.png
Let's say I have 5000 users and I can sort them by gender. Let's say 2800 of them are males, how can I assign them all to a label?
Here's some things I tried:
public function add_users_to_label($label_id, $condition, $value)
{
$db = new Database();
$conn = $db->db_connect();
$label_id = escape_string($conn, $label_id);
$query = $conn->query("INSERT INTO `label_user`(`label_id`, `user_id`) SELECT :label_id, psid FROM `iris_messenger_users` WHERE $condition = $value");
$query->bind_param("iss", $label_id, $condition, $value);
if ($query->execute()) {
return true;
}
else {
return "Error inserting data: " . $conn->error . "\n";
}
}
On the user side I have a simple form with select that let's you select a label and then this code:
if(isset($_POST['label-select'])) {
if ($_GET['show_only_gender'] == 'male') {
$condition = 'gender';
$user->add_users_to_label($_POST['label-select'], $condition, $_GET['show_only_gender']);
}
}
Basically, I want to get all users that are male and assign them to a label and put that into label_user with respectively the label_id and the user_id(psid)
Even if this worked I'd still have to do it 2699 times more. What can I do here to optimize and make it to run with 1 query if possible?
I don't think using foreach and running it as much times as there are users is the best option, is it?
Is there any better approach I can take to make this possible?
Although what you are describing does not make sense to have a "label" associated with a person for this specific component, the gender is already on the user table you should be able to get all male based on
select * from user where gender = 'male'
no need to JOIN to a label table on this field. Similarly if you were trying to find people based on a name starting with something... you would not create a label for the name either. Query directly from the table that has that specific component association.
Now, to answer your question, how to insert into the label table for each instance in bulk, you could do something like... I am doing this based on some label ID = 123 as just an example in your labels table that represents gender.
I am doing a LEFT-JOIN in the select so we dont try to add for any user IDs that are already on file do not try to get re-added.
insert into label_user
( label_id,
user_id )
select
123 as label_id,
U.id as user_id
from
users U
left join label_user LU
on U.id = LU.user_id
AND LU.label_id = 123
where
U.gender = 'male'
AND LU.user_id IS NULL
You obviously need to adjust for php.

having trouble converting SELECT results correctly

Not sure if this should be in PHP or MySQL.
I have this query:
SELECT
product.*,
company.company-name
FROM
products
INNER JOIN
company ON products.company-id=company.id
ORDER BY
company.company-name,
product.model
which I am outputting using this:
$group = array();
while ($row = mysqli_fetch_assoc($result)) {
$group[ $row['company-name'] ][] = $row;
}
foreach ($group as $company-name => $models) {
foreach ($models as $model) {
echo "Product Name: $model[model-name]";
echo "Color: $model[color]";
echo "Core Company: $model[core-company-id]"; // this is the line I am trying to fix
}
Obviously in my output above if I output $model[core-company-id] it is only going to be the number. I want the corresponding company from the 'company' table (core-company-id = the correct company.company-name / company.id) to be in its place.
I cant figure out how to do it. Can I adjust my original SELECT query/result somehow, do I need an extra query, or can I modify the php output somehow to fix this?
EDIT: I should add, the Core Company name will be different from the $company-name I am retrieving in the initial SELECT. That company is the product company name, but the maker of the 'core' will be a different company most times
For example, if core-company-id is 20, I want whichever company.company-name has a company.id of 20 to be put there
Thanks for any help.
EDIT 2: Bear with me while I add table data etc. Sorry for not explaining this very well, I've found it difficult to put into words.
EDIT 3: More information.
I have two tables:
Table 'Product' contains: id, main-company-id, model-name, color, core-company-id
Table 'Company contains: id, company-name
My output currently looks like this:
COMPANY NAME (comes from company.company-id)
Product 1 Name: Item name
Color: Blue
Core Company: 27 (comes from product.core-company-id)
Product 2 Name: Item 2 name
Color: Blue
Core Company: 35 (comes from product.core-company-id)
COMPANY NAME
Product 1 Name Item 1 Name
Color: Green
Core company: 27 (comes from product.core-company-id)
Having a hard time posting the output example correctly. Ugh. Not doing so good am I?
That should work:
foreach ($group as $company-name => $models) {
foreach ($models as $model) {
echo "Product Name: $model[model-name]";
echo "Color: $model[color]";
echo "Core Company: " . $company-name; // this is the line
}
}
UPDATE: I just noticed in the SELECT and ORDER you are calling the table 'product' (singular) but in the FROM and JOIN you have 'products' (plural) - that would explain why you're not getting the result you expect!
It looks like you're not retrieving the value you want (core-company-id) with your select, or maybe it's in product but it's not clear to me where 'core-company-id' should come from.
Assuming it's a column in company table, this should work:
SELECT
product.*,
company.company-name
company.core-company-id
FROM
products
INNER JOIN
company ON products.company-id=company.id
ORDER BY
company.company-name,
product.model
you could always see what your array has with print_r and make sure the core-company-id is present;
print_r($group); ## new line for testing here
foreach ($group as $company-name => $models) {
Edit: From reading your edits it sounds as if you may want to join the same table twice, which you can do, but it's still not clear to me where is core-company-id coming from.
Maybe You need something like:
SELECT
field1,
field2,
company.company-id AS com_company-id
company.company-name AS another_field
FROM table
...
product.company-id AS pro_company-id
...
Then You can use another_field in Your PHP code the same way as You use the field1.
As stated at 13.2.8. SELECT Syntax "A select_expr can be given an alias using AS alias_name." You can set an expression and rename the field to some name that would be easy to access using PHP later. Such as $model[another_field], $model[com_company-id], $model[pro_company-id].
If You have the same field names in different tables You can make aliases with AS and use them in Your SQL query as well (on JOINs etc.).
If You need to get some company_names instead of company_ids You can use brackets () to create SQL subqueries. For example, to get a company name instead of "27", "35", etc.
Then there should be something like:
SELECT ... FROM ...
(
SELECT Company.company-name FROM Company
INNER JOIN Product
WHERE Company.id = Product.core-company-id
) AS company_name;
Using a Subquery to get the company name and access that as $model[company_name] in Your PHP code.
Update
Just noted, that You are messing up with "product" and "productS".
Maybe Your query should be like that:
SELECT
products.*, -- You have product.* here
company.company-name
FROM
products
INNER JOIN
company ON products.company-id=company.id
ORDER BY
company.company-name,
products.model -- You have product.model here
In that case, maybe, You will be able to select$model[core-company-id] as You did it earlier in PHP code.

SQL query ORDER BY looping in PHP

EDIT: Added the first SQL query.
A section of my website has two dropdown menus. All the options in both are populated using SQL queries. Dropdown#1 is a list of class sections (like A1 for example). Once the professor selects a section, Dropdown#2 is populated with the student ID's (like 1234567 for example).
Student information is found in table 1. Among this information is the 'professorName' column. In order to associate the student with a class section, I need to match 'professorName' column with an identical column found in table 2, because class sections are only found in table 2.
Till here everything works great, because at the end of my query I put ORDER BY student ID. However, two of the class sections are associated to two different professors. In order to deal with this issue, I used the following code to loop through each professor name.
$from site = $_POST['section'];
$query = ("SELECT professorName FROM Table 2 WHERE classSection='$fromsite'");
$NumberofProfessorNames = $objMSSQL->getAffectedRows();
echo $NumberofProfessorNames;
for ($j=0; $j<$NumberofProfessorNames; $j++)
{
$section= $query[$j][professorName];
$output = $objMSSQL->getTable("SELECT DISTINCT StudentID from table1 WHERE professorName='$section' ORDER BY StudentID");
for ($i=0; $i<$objMSSQL->getAffectedRows(); $i++)
{
echo "<option value='".$output[$i][studentID]."'>".$output[$i][studentID]."</option>";
}
}
The problem is that for the only two sections where this is even necessary (because there are two professorNames), since it is looping like this, it is ending up ordered like this in the dropdown#2:
1234567
2345678
3456789
4567890
1234123
2345765
3456999
4567000
My limited experience in programming is keeping me from understanding how I can fix this seemingly simple issue.
Thank you for your help.
Rather than loop over the professors and query table1 for each, join table1 and table2 in the second query and only query the database once. For example:
$query = [... FROM Table2...];
$NumberofProfessorNames = $objMSSQL->getAffectedRows();
echo $NumberofProfessorNames;
$output = $objMSSQL->getTable("
SELECT DISTINCT StudentID
from table1
join table2
on ...
WHERE [the same clause you used in $query]
ORDER BY StudentID"
);
for ($i=0; $i<$objMSSQL->getAffectedRows(); $i++)
{
echo "<option value='".$output[$i][studentID]."'>".$output[$i][studentID]."</option>";
}
It's more elegant (and almost certainly more efficient) than generating a WHERE IN clause.
Yu can do it this way:
$section = "('";
for ($j=0; $j<$NumberofProfessorNames; $j++)
{
$section.= $query[$j][professorName] . "','";
}
$section = substr($section, 0, -3) . ')'; //$section contains ('prof1','prof2')
$output = $objMSSQL->getTable("SELECT DISTINCT StudentID from table1 WHERE professorName IN $section ORDER BY StudentID");
for ($i=0; $i<$objMSSQL->getAffectedRows(); $i++)
{
echo "<option value='".$output[$i][studentID]."'>".$output[$i][studentID]."</option>";
}
that is querying for all your professors in just one sql with IN() syntax.
UPDATE: I've just noted you use sql server instead of mysql, so I've changed the IN() syntax a bit and change the link to the sql server help docs.
It sounds like your tables aren't normalized. Good form would have a sections table, a students table, and a professors table. Information in each table should be specific to the table's topic.
students
student_id
student_last_name
student_first_name
student_address
etc
sections
section_id
section_name - multiple sections can tend to have the same name but differing content
section_description
section_year - sections can change from year to year
faculty
faculty_id
faculty_name - this is not a key field, more than one person can have the same name.
faculty_address
faculty_type - adjunct, fulltime, etc.
You would then have relational tables so you can associate professors with sections and students with sections.
faculty_2_sections
f2s_id
faculty_id
section_id
student_2_sections
s2s_id
student_id
section_id
This makes it super simple because if a student is logged in, then you already have their student id. If it's a professor, you already have their faculty_id
If you're pulling for students, your sql might look like this:
$sql = "select * from students s,sections sc,faculty f,faculty_2_sections f2s,student_2_sections s2s where student_id='$student_id' and s2s.student_id=s.student_id and s2s.section_id=sc.section_id and f2s.faculty_id=f.faculty_id and f2s.section_id=s2s.section_id";
If you're pulling for faculty you would do this:
$sql = "select * from students s,sections sc,faculty f,faculty_2_sections f2s,student_2_sections s2s where faculty_id='$faculty_id' and f2s.faculty_id=f.faculty_id and f2s.section_id=s2s.section_id and s2s.section_id=sc.section_id and s2s.student_id=s.student_id";
You can then pull a list of sections to populate the section_ids pull-down to only show students or faculty for a specific section.

mysql results sort by array

I think I don't understand how 'sort' works, so please don't judge me. I really searched all day long.
I have a movies table with actors column. A column it's named "actors". The actors are links separated by space " ". The order of the links it's very important.
I explode the links into an array which looks like [0]-> link0, [1]->link1, ...
I have the actors table where every actor also has it's movies links. I don't want to make 20 different sql searches so I made a variable with all the links I want, like this ( WHERE actor_link = link1 OR actor_link = link2 OR .. )
The problem is this. The search will probably find first the link7, and so my sorting it's gone. What can I do to keep that order from the movies table. I want to display the actors by popularity in the movie, not the order of my database.
Can you give me another method to search the actors without making 'x' sql searches and still keeping the order?
$actors[] = explode(" ", $row['actors_link']);
$x=0;
$actors2 = '';
while ($actors[0][$x]) {
$actors2 = $actors2 . "`link_imdb_actor` = " . "'".$actors[0][$x]."' OR ";
$x++;
}
$actors2 = substr($actors2, 0, -3);
$sql = "SELECT * FROM `actors` WHERE $actors2";
$sql_result = mysql_query($sql) or die(" ");
while ($row3 = mysql_fetch_array($sql_result)) {
echo $row3['link_imdb_actor'];
}
So, the movie Hotel Transylvania has Adam Sandler, Andy Samberg and Selena Gomez. My search shows Selena Gomez, Andy Samberg, Adam Sandler because this is the order from my database. How can I sort the sql results by the order of the actors array? Thank you!
To expand on Arjan's comment, if you want to be able to actually use the actor data (e.g. search with it) I would recommend at least two more tables. One called actors with the fields actorID, firstName, and lastName. The other table would be castings with the fields castingID, actorID, movieID, and castingOrder.
Each castingID will then link an actor to a movie - this would make for easy searches of every movie a particular actor has been in or every actor in a particular movie.
The castingOrder field can be used to maintain the order you want.
I need your existing code to really get the gist of what's going on.
I will make one suggestion in your query. Instead of saying WHERE actor_link = a OR actor_link = b OR actor_link = c do this instead:
WHERE actor_link IN (link1, link2, link3)

Items 'like' this one

I'm looking for some general advice on how to go about finding items 'like' the current one.
In my current example I have three tables like so (omitting unrelated data):
games
-game_id
genres
-genre_id
genres_data
-game_id
-genre_id
How can I go about finding games that have genres in common with the current one, from the ones that have all the same genres, descending to ones that only have one in common with it (limited of course to a few rows) with a given row from games?
What's the preferred method of finding like items?
Try this:
SELECT game_id, COUNT(genre_id) AS genres_in_common
FROM genres_data
WHERE
genre_id IN
(
SELECT genre_id
FROM genres_data
WHERE game_id = <game you're searching with>
)
AND
game_id != <game you're searching with>
GROUP BY game_id
ORDER BY genres_in_common DESC
;
The subquery grabs a list of all genre_ids associated with your game, and the main query uses that to search genres_data for any record that matches one of them. In other words, it searches for any game which is associated with any genre your "search game" is associated with.
Because a game can have multiple genres, this query would return the same game_id multiple times, and if you also reported the genre_id on these records they would each show a different genre_id. What we do to find the ones with the most in common is to group the results by each game_id, and we add the COUNT(genre_id) in the main SELECT to show how many different genre_ids there were for each game_id returned in that query.
From there, it's a simple matter of ordering that count of common genres in descending order, so that the games with the most genres in common will be listed first.
I also added a second criterion to the main query to exclude the game you're searching on from the results, otherwise that game would always have the most matches, for obvious reasons.
Hope that helps.
Surely the way to do this for just a single game is to first grab its genres and then loop over them to create a new query:
$query = "SELECT `genre_id` FROM `genres_data` WHERE `game_id` = 'your_game_id_here';"
$genre_id_result = mysql_result($query, $dbconn);
$num = mysql_num_rows($genre_id_result);
if ($num > 0) {
$query = "SELECT `game_id` FROM `games` WHERE ";
for ($i=0;$i<$num;$i++) {
$genre_id = mysql_result($genre_id_result, $i, "genre_id");
if ($WhereSQL == "") {
$WhereSQL = "genre_id = '$genre_id' "
} else {
$WhereSQL .= "AND genre_id = '$genre_id' "
}
}
$GamesInCommonResult = mysql_result($query . $WhereSQL, $dbconn);
}
You could set up a loop to do this for every game in the database and then collate your results. I can't think of how to do this in a single query at the moment.
I'm also a little unsure on your question as either you're looking for the genres that are the most popular (as games with these genres will likely be returned as having the most other games with the same genre in common) or you are looking individually for other game_ids of games in common with another game which might be more useful.

Categories