Search with multiple tables & ORDER BY php mysql - php

I have a script i want to search multiple tables how can i do that.
**Also add ORDER BY function in Mysql query **
help is appreciated.
if(isset($_GET["search"]))
{
$condition = '';
//$query = explode(" ", $_GET["search"]);
$query = explode(" ", $_GET["search"]);
foreach($query as $text)
{
$condition .= "title LIKE +'%".mysqli_real_escape_string($connect, $text)."%' OR ";
}
$condition = substr($condition, 0, -4);
$sql_query = "SELECT * FROM countries WHERE " . $condition;
$result = mysqli_query($connect, $sql_query);
if(mysqli_num_rows($result) > 0)
{
while($row = mysqli_fetch_array($result))
{
echo '<tr><td>'.$row["title"].'</td></tr>';
}
}
else
{
echo '<label>Data not Found</label>';
}
}

SELECT * FROM (
(SELECT title FROM countries WHERE title LIKE '%mystring%')
UNION
(SELECT title FROM locations WHERE title LIKE '%mystring%')
) ta
That's the sql, but would need to point out the drawbacks of using union as a search tool.
a) The longer the tables get the longer the search will get, you can add in Limits on each query and then on the union as a whole - but it's not a winner.
b) The table columns have to match up so you'll need perhaps to do myID as ID, then you will need an extra column to say which each is (0=country, 1= location)
c) I guess you are trying to do a site search of sorts, in which case there isn't a relevance in this instance.
Hence I would use something like http://sphinxsearch.com/ bit tricky to get started, but a really quick search engine. If you have a large site. Otherwise look into mysql full text searches which also have relevence and are built in so easier to get started with.

Related

How to return only items that occur in 2 sql select statemnts

I have two different sql statements. $sql grabs all the items whose title matches a certain search text. $cat_sql grabs all the category_items that are in a certain category. An item has an ID. A category_item has a field called item_id which is a foreign key to IDs in the items table
...
mysqli setup code
...
$title = $_POST["title"];
$cat_id = $_POST["cat_id"];
$cat_sql = "SELECT * FROM category_items WHERE category_id = '".$cat_id."'";
$sql = "SELECT * FROM items where title LIKE '%". $title ."%' Limit 70";
if (!$result_cat = $mysqli->query($cat_sql)) {
// The query failed.
echo "<h2 >ERROR</h2>";
exit;
}
if (!$result = $mysqli->query($sql)) {
// The query failed.
echo "<h2 >ERROR</h2>";
exit;
}
Then I display all items:
while ($item = $result->fetch_assoc()) {
include 'item_card.php';
}
Currently this just displays all items fetched in the $sql query. Is there some way to remove all items from $result that do not have their ID represented as an item_id in $result_cat?
NOTE:
I would strongly prefer not to do just combine both SELECT statements into a table join because the actual $sql and $cat_sql are not nearly as simple as I have represented here. Also, they vary depending on which if statement they are in.
My question is: given $result and $result_cat, can I remove items from $result?
EDIT 1
As suggested by comments I am making an array if item_ids then doing an in_array query. Progress thus far:
$result_cat_ids = [];
while ($cat_item = $result_cat->fetch_assoc()) {
$result_cat_ids[] = $cat_item['item_id'];
}
EDIT 2 Here is the working code following the suggestions in the comments
if (in_array($item['id'], $result_cat_ids)) {
include 'item_card.php';
}
You may also use 'INTERSECT' sql clause.
$sql = "SELECT * FROM items WHERE id IN (SELECT item_id FROM category_items WHERE category_id = '".$cat_id."' INTERSECT SELECT id FROM items where title LIKE '%". $title ."%')";
This way, you can query for items that accomplish both conditions.
Note: I'm not using "limit 70" but you may add it as well.

What is faster? SQL query with wildcards and ORs, or catch-all query with php loop for filtering?

I'm running a query on a single table. I need the end result to be an array of rows where the current user is listed as a participant in the message that the DB row records. The two participants columns are staff and clients, and the contain serialized arrays of user IDs and/or role name (e.g., a:3:{i:0;s:8:"staffman";i:1;s:3:"203";i:2;s:3:"170";}).
Would it be faster to try to filter out any non-matches for the current user all in the SQL query, or to do a catch-all query (maybe on several hundred rows), then loop through them to filter out the ones where the user is not a participant?
SQL option:
$query = "SELECT * FROM ".self::$messages;
if($is_client) $query .= " WHERE type = 'clients'";
elseif($is_staff) $query .= " WHERE type = 'staff' AND (staff LIKE '%\"".$user->ID."\"%' OR staff LIKE 'staff\"%')";
elseif(!$is_admin)
{
$query .= " WHERE type = 'staff' AND (staff LIKE '%\"".$user->ID."\"%' OR staff LIKE 'staff\"%' OR staff LIKE 'clientman%'";
if($is_staffman) $query .= " OR staff LIKE 'staffman%'";
if($is_accountant) $query .= " OR staff like 'accountant%'";
$query .= ")";
}
$query .= " ORDER BY updated DESC";
LOOP Option:
$col = $is_client ? 'clients' : 'staff';
$query = "SELECT * FROM ".self::$messages." WHERE type = {$col} ORDER BY updated DESC";
// do the query, then check if there are results. if results...
foreach($results as $msg)
{
if(empty($msg->$col)) continue;
$users = unserialize($msg->$col);
if($is_client)
{
if(!in_array($user->ID, $users) && !in_array('client', $users)) continue;
}
elseif(!$is_admin && $msg->created_by != $user->ID)
{
if(!in_array($user->ID, $users))
{
if(!in_array('staff', $users))
{
if(!$is_clientman || !in_array('clientman', $users))
{
if(!$is_staffman || !in_array('staffman', $users))
{
if(!$is_accountant || !in_array('accountant', $users)) continue;
}
}
}
}
}
// match found, do stuff with it here
}
You should always use SQL option in such case.
$query = "SELECT * FROM ".self::$messages;
if($is_client) $query .= " WHERE type = 'clients'";
elseif($is_staff) $query .= " WHERE type = 'staff' AND (staff LIKE '%\"".$user->ID."\"%' OR staff LIKE 'staff\"%')";
elseif(!$is_admin)
{
$query .= " WHERE type = 'staff' AND (staff LIKE '%\"".$user->ID."\"%' OR staff LIKE 'staff\"%' OR staff LIKE 'clientman%'";
if($is_staffman) $query .= " OR staff LIKE 'staffman%'";
if($is_accountant) $query .= " OR staff like 'accountant%'";
$query .= ")";
}
$query .= " ORDER BY updated DESC";
Why ?
1.See you already get less records(those are Needed only) by filtering thru WHERE clause.There is no point in fetching all the records and filtering it in php.
2.See its headache to manage code in PHP as it got complicated.
3.Less no.of lines.
4.It is managed by SQL most of the time, php logic will make it slow.
I've accepted the answer above because it was a clear and definitive answer to the question I posed. However, Stuart (in the comments) suggested I try to normalize my structure rather than scan serialized arrays with LIKES and the like. So that's what I've done.
Instead of saving my participants array in serialized form in the same table as the messages table, I created a new table called message_recipients with just three columns (id,mid,user), where mid refers to the id column from the messages table, and where user stores the individual user id or role name.
So the trickiest part (which wasn't really that tricky) was how to manage adding and deleting rows from the message_recipients table when the user changes the allowed recipients for a given message. (In the form, they have two multiselect dropdowns—one for staff, one for clients).
So here's how I managed the updated/changing participants part:
For each field (staff and clients), I pass my function two arrays, one with the new participants and one with the old participants. (If the message is being created, then the old participants array is simply empty.)
Then here's the function (basically just four lines of actual work):
public function update_participants($mid = false, $new_staff = array(), $old_staff = array(), $new_clients = array(), $old_clients = array())
{
global $wpdb;
if(empty($mid)) return array();
$add = array_filter(array_unique(array_merge(array_diff($new_staff, $old_staff), array_diff($new_clients, $old_clients))));
$delete = array_filter(array_unique(array_merge(array_diff($old_staff, $new_staff), array_diff($old_clients, $new_clients))));
if(!empty($add)) $wpdb->query("INSERT INTO ".self::$participants." (mid,user) VALUES(".$mid.",\"".implode("\"),(".$mid.",\"", $add)."\")");
if(!empty($delete)) $wpdb->query("DELETE FROM ".self::$participants." WHERE mid = {$mid} AND user IN (\"".implode("\", \"", $delete)."\")");
return array('added'=>$add, 'deleted'=>$delete);
}
Then I return the resulting arrays of people being added or deleted, so I can play with that in my email notifications stuff.
Then, now that we're normalized, the original problem of querying the table to get all the messages in which the current user is a participant is much faster and simpler:
$query = 'SELECT DISTINCT a.* FROM '.self::$messages.' a, '.self::$participants.' b';
if(!$is_admin)
{
$query .= ' WHERE (b.user = '.$user->ID;
if($is_client) $query .= ' OR b.user = "client"';
elseif($is_staff) $query .= ' OR b.user = "staff"';
else
{
if($is_clientman) $query .= ' OR b.user = "clientman"';
if($is_staffman) $query .= ' OR b.user = "staffman"';
if($is_accountant) $query .= ' OR b.user = "accountant"';
$query .= ' OR a.created_by = '.$user->ID;
}
$query .= ') AND b.mid = a.id';
}
$query .= ' ORDER BY a.updated DESC';
Admins get everything, and everyone else only get any messages where their user id or their user role is found in the participants table. (Some roles have multiple roles, hence the triple if statements, rather than elseifs.
I've tested it with every possible configuration with different users. Works like a charm.
And no LIKEs, no wildcards, and no PHP loops.
Thanks everyone for the help.

Search query and ordering by matches with my sql

Hi am currently stuck here, been doing research on how to write a mysql statement for a flexible search for products and order them by relevance on a project am working on have seen a few but wasn't helpful please i need help on how to make it work, my current method doesn't work, here it is.
User types in search field and submits "iPad 3rd Generation".
My script breaks the string into words like so.
$termsExploded = array_unique(explode(' ', $term));
No i use php to create an sql query based on the number of words found.
$i = 0;
foreach ($termsExploded as $word) {
if (strlen($word)>1) {
if ($i == 0) {
$where_query = $where_query." name LIKE '%".$word."%'";
}
else{
$where_query = $where_query." OR name LIKE '%".$word."%'";
}
$i++;
}
}
The where query variable now looks like this.
name Like '%ipad%' Or name Like '%3rd%' Or name Like '%Generation%'
Now search for the products ids like so.
$IDs = "SELECT DISTINCT id FROM store_items WHERE".$where_query;
I now create a second where query based on the IDs returned like so
$where_query_s = null;
$i = 0;
foreach ($IDs as $result) {
$returnID = $result->id;
if ($i == 0) {
$where_query_s = $where_query_s." id = ".$returnID."";
}
else{
$where_query_s = $where_query_s." OR id = ".$returnID."";
}
$i++;
};
Now i select the products again based on the distinct IDs returned like so
$items = "SELECT * FROM store_items WHERE".$where_query_s;
Now this works to get the products but how can i sort it based on best match?
Assuming you want to order by the number of matches then build up another string as follows:-
ORDER BY IF(name Like '%ipad%', 1, 0) + IF(name Like '%3rd%', 1, 0) + IF(name Like '%Generation%', 1, 0) DESC
But this will be slow, and takes no account of indexing to improve performance nor of plural / singular (ie, it someone searches for 'flies' it won't rank 'fly' properly).
To put that more into code:-
$where_query = array();
$order_query = array();
foreach ($termsExploded as $word)
{
if (strlen($word)>1)
{
$where_query[] = " name LIKE '%".$word."%'"
$order_query[] = " IF(name Like '%".$word."%', 1, 0)"
}
}
$IDs = "SELECT DISTINCT id FROM store_items WHERE ".implode(' OR ', $where_query)." ORDER BY ".implode(' + ', $order_query)." DESC";
Arrange for your query to look like this:
select field1, field2, etc, count(*) records
from store_items
where blah blah blah
group by field1, field2, etc
order by records desc
If the table is MyISAM based or if it is InnoDB and the version is Mysql 5.6 or greater, then you can use full text search (see http://dev.mysql.com/doc/refman/5.6/en/fulltext-search.html)
effectively you want a query similar to
SELECT * FROM store_items WHERE MATCH (name) AGAINST ('iPad 3rd Generation')
ORDER BY MATCH (name) AGAINST ('iPad 3rd Generation')

MySQL Search by Relevance of all fields

I am sure this is possible but I think it maybe just very complex to write. I want to search every field by:
='SearchTerm'
then
Like %SearchTerm
then
like SearchTerm%
and finally
like %SearchTerm%. I want to run this on every field in my table which there is around 30 or 40. Is there an easy way to run this over multiple fields or will I have to declare every single one?
I think I have seen a query before where different matches between %query %query% etc are ranked by assigning an integer value and then ordering by this. Would that be possible on a query like this?
Any advice and help in the right direction is much appreciated.
You should use fulltext indexing on the fields you want searched and use MATCH AGAINST instead of LIKE %%. It's much faster and returns results based on relevancy. More info here:
Mysql match...against vs. simple like "%term%"
I do something very similar to what you're describing (in php and mysql)
Here's my code:
$search = trim($_GET["search"]);
$searches = explode(" ",$search);
$sql = "SELECT *,wordmatch+descmatch+usagematch+bymatch as `match` FROM (SELECT id,word,LEFT(description,100)as description,
IFNULL((SELECT sum(vote)
FROM vote v
WHERE v.definition_id = d.id),0) as votecount,
";
$sqlword = "";
$sqldesc = "";
$sqlusage = "";
$sqlby = "";
foreach ($searches as $value) {
$value = mysqli_real_escape_string($con,$value);
$sqlword = $sqlword . "+ IFNULL(ROUND((LENGTH(word) - LENGTH(REPLACE(UPPER(word), UPPER('$value'), '')))/LENGTH('$value')),0)";
$sqldesc = $sqldesc . "+ IFNULL(ROUND((LENGTH(description) - LENGTH(REPLACE(UPPER(description), UPPER('$value'), '')))/LENGTH('$value')),0)";
$sqlusage = $sqlusage . "+ IFNULL(ROUND((LENGTH(`usage`) - LENGTH(REPLACE(UPPER(`usage`), UPPER('$value'), '')))/LENGTH('$value')),0)";
$sqlby = $sqlby . "+ IFNULL(ROUND((LENGTH(`by`) - LENGTH(REPLACE(UPPER(`by`), UPPER('$value'), '')))/LENGTH('$value')),0)";
}
$sql = $sql . $sqlword ." as wordmatch,"
. $sqldesc ." as descmatch,"
. $sqlusage ." as usagematch,"
. $sqlby ." as bymatch
FROM definition d
HAVING (wordmatch > 0 OR descmatch > 0 OR usagematch > 0 OR bymatch > 0)
ORDER BY
wordmatch DESC,
descmatch DESC,
usagematch DESC,
bymatch DESC,
votecount DESC)T1";
$queries[] = $sql;
$result = mysqli_query($con,$sql);
You can see this at work http://unurbandictionary.comule.com/view_search.php?search=George+Miley+Cyrus this is when I search for "George Miley Cyrus"
What it does is it explodes the search string to find each word and returns the number of occurences of each word in each of my column, and then i do an ORDER BY to have relevance (priority) to come back first. So in my case word field has the highest relevance, then description field, then usage field, then by field.
Before this version of my code I was using LIKE but it didn't give me a count of occurences, since I want the row with the most occurences of my search word to return first before other rows.
You should really have some sort of id to select the rows in your table.
You should have put a column with
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
Then you could use
SELECT * FROM table WHERE column1 LIKE "%SearchTerm%" AND id BETWEEN 1 AND 40

Excluding a variable when its value is blank

The code below works great. I have a MySQL database that contains book titles classified in different categories. In the code below, the variable "site" represents a book title. Each category is represented by a different table in the MySQL database.
The code below ranks the top 25 book titles (site) by total votes across all categories (MySQL tables). I am trying to exclude blank book titles (i. e. when site = ''). How can I do this?
I have tried inserting WHERE site != '' in a few places but I get an error message. So I guess I'm asking, where can I insert WHERE site != ''?
Thanks in advance,
John
<?
mysql_connect("mysqlv10", "username", "password") or die(mysql_error());
mysql_select_db("database") or die(mysql_error());
$result = mysql_query("SHOW TABLES");
$tables = array();
while ($row = mysql_fetch_assoc($result)) {
$tables[] = '`'.$row["Tables_in_bookfeather"].'`';
}
$subQuery = "SELECT site, votes_up FROM ".implode(" UNION ALL SELECT site, votes_up FROM ",$tables);
// Create one query that gets the data you need
$sqlStr = "SELECT site, sum(votes_up) sumVotesUp
FROM (
".$subQuery." ) subQuery
GROUP BY site ORDER BY sum(votes_up) DESC LIMIT 25";
$result = mysql_query($sqlStr);
$arr = array();
echo "<table class=\"samples2\">";
while ($row = mysql_fetch_assoc($result)) {
echo '<tr>';
echo '<td class="sitename2">'.$row["site"].'</td>';
echo '<td>'.$row["sumVotesUp"].'</td>';
echo '</tr>';
}
echo "</table>";
?>
You shouldn't have separate tables for each book category. I can't believe you have so many books that any of these tables would grow too large. Any scalability benefits you might gain by splitting the table are offset by the complexity of having to do these UNION queries.
Here's what I'd do:
Unify the tables into one table.
Add a Categories table.
Relate books to categories with a many-to-many table.
Then your SQL query becomes much simpler:
$sqlStr = "SELECT site, votes_up FROM Books
WHERE site IS NOT NULL AND site <> ''
ORDER BY votes_up DESC LIMIT 25";
It's probably safest to put it in the subquery:
$subQueryParts = array();
foreach($tables as $table)
$subQueryParts[] = "SELECT site, votes_up FROM $table WHERE LENGTH(site)";
$subQuery = implode(" UNION ALL ", $subQueryParts);
If possible, you should follow Bill Karwin's advice and store all your books in one table. Dynamic table names are very hard to search and manage, and they do not optimize well.

Categories