I am using php phalcon5 framework. I have 2 columns on a product table. Product title and keyword. The product title contains 1 complete sentence and the keyword contains multiple comma separated words. I have done different queries but am getting unexpected results. How do I get the right results?
[Table]
table -> products
column -> product_title, keywords
-----------------------------------------------
product_title | keywords
-----------------------------------------------
three piece | women,cloth,three,piece
shari | women,cloth
Kurti | cloth,women
-----------------------------------------------
[Query Results]
Query1: women cloth
Result: three piece, shari
Expected Result: three piece, shari, Kurti
Query2: cloth women
Result: Kurti
Expected Result: three piece, shari, Kurti
Query3: women/cloth
Result: Three piece, shari, Kurti
Expected Result: three piece, shari, Kurti
Query4: cloths
Result: No Data Found
Expected Result: three piece, shari, Kurti
[Controller]
$data = trim($this->request->getPost('q'));
$query = Products::find(["keywords LIKE :key: OR product_title LIKE :data:",
"bind" => ["data" => '%'. $data .'%',"key" => '%'.$data.'%'],
]);
if($query->count() > 0)
{
$this->view->setVar('pro',$query);
$this->view->pick('index/query');
}
One thing to take in account is that you can use only one bind param, ex. data, and reemploy it in either keywords and product_title comparison:
$query = Products::find(["keywords LIKE :data: OR product_title LIKE :data:",
"bind" => ["data" => '%'. $data .'%'],
]);
In the other hand, you should to explode your query string to prevent it from affecting the words order, and repeat your keywords/product_title comparison with each of them:
$words = array_map('trim', preg_split('|\s+|', $this->request->getPost('q')));
$params = [];
foreach ($words as $key => $word) {
$params['conditions'] .= isset($params['conditions']) ? ' AND ' : '';
$params['conditions'] .= "(keywords LIKE :word".$key.": OR product_title LIKE :word".$key.":)";
$params['bind']['word'.$key] = '%'. $word .'%';
}
$query = Products::find($params);
Related
I have a b_topics table with tags column in multiple rows
id | tags
1 | Joshua, Janet, Hannah
2 | Glory, Jon, Celina, Johanna
3 | Bridge,Terry, Sterling
4 | Daniel, Florence, Joanne
I want to check for the related tags with the input Jo so i have the below sql select
$query = Jo;
$sql = mysql_query("SELECT DISTINCT tags FROM b_topics WHERE tags LIKE '%{$query}%'");
while ($row = mysql_fetch_array($sql)) {
$array[] = array ( 'label' => $row['tags'], 'value' => $row['tags'], );
}
echo json_encode ($array);
This is the output:
[{"label":"Joshua, Janet, Hannah","value":"Joshua, Janet, Hannah"},{"label":"Glory, Jon, Glory","value":"Glory, Jon, Glory"},{"label":"Daniel, Florence, Joanne","value":"Daniel, Florence, Joanne"}]
I want the matched words to be on foreach();
Expected output:
[{"label":"Joshua","value":"Joshua"},{"label":"Jon","value":"Jon"},{"label":"Johanna","value":"Johanna"},{"label":"Joanne","value":"Joanne"}]
Fix your data structure! You should not be storing multiple values in a string delimited list. This is simply not the right way to store the data. SQL has this great data structure for storing lists. It is not called "string". It is called "table". You want a table called TopicTags with one row per topic and per tag.
Although you can do nasty string functions to get what you want, this would be much simpler with the right data structure:
select topic_id, tag
from TopicTags tt
where tag like '%Jo%';
You can aggregate if you want the results in a particular format.
set your tables because when your query return a row then you can store that row not only a single tag.
$array[] = array ( 'label' => $row['tags'], 'value' => $row['tags'], );
where $row['tags']=Joshua, Janet, Hannah so its gives result like
'label' ="Joshua, Janet, Hannah"
$sql = "select * from test where tags like '%Jo%'";
$res = mysqli_query($con,$sql);
$string = '';
while($row=mysqli_fetch_array($res)){
$string .= ','.$row['tags'];
}
$array = explode(',', $string);
$search = preg_quote('Jo', '~'); // don't forget to quote input string!
$result = preg_grep('~' . $search . '~', $array);
print_r($result);
I hope you can help :)
This is how the table looks:
+------------+----------------+------------------+---------+
| firstName | lastName | email | etc... |
+------------+----------------+------------------+---------+
| John | Doe | john#doe.com | etc... |
+------------+----------------+------------------+---------+
| John | Michaels | john#michaels.es | etc... |
+------------+----------------+------------------+---------+
This is how the code looks:
if($_GET['search-customers'] != '') {
$busqueda = $_GET['search-customers'];
$query->andWhere("(c.firstName LIKE '%$busqueda%' OR c.lastName LIKE '%$busqueda%' OR c.email LIKE '%$busqueda%')");
}
With that QUERY:
If I input: John in the search box, it gives me the 2 results.
OK
If I input: John D in the search box, it doesn't give me any result. FAIL
All right, I understand, When I type "John D", it try to find first in firstName (doesn't match) and also it doesn't match lastName or email.
How can I combine them?
The idea its to find the complete string in all possibilities.
Thanks!
I will provide you a different alternative using MySQL's Full-Text Search Functions. Lets begin to prepare the table:
ALTER TABLE persons ADD FULLTEXT (`firstname`, `lastname`);
Now, firstname and lastname are columns to be used by full-text in order to search for matches:
SELECT * FROM persons
WHERE MATCH (firstname,lastname)
AGAINST ('John D' IN NATURAL LANGUAGE MODE);
The result will be:
+------------+----------------+------------------+---------+
| firstName | lastName | email | etc... |
+------------+----------------+------------------+---------+
| John | Doe | john#doe.com | etc... |
+------------+----------------+------------------+---------+
| John | Michaels | john#michaels.es | etc... |
+------------+----------------+------------------+---------+
Why both? Because John (as a word) was found, however John Doe is in the first row because has much similitude with the term of search.
Say, that lets apply this tool with Doctrine. I will assume that your model looks like this:
class Person{
/** #column(type="string", name="firstname")*/
protected $firstName;
/** #column(type="string", name="lastname")*/
protected $lastName;
/** #column(type="string")*/
protected $email;
}
Lets create the search function:
public function search($term){
$rsm = new ResultSetMapping();
// Specify the object type to be returned in results
$rsm->addEntityResult('Models\Person', 'p');
// references each attribute with table's columns
$rsm->addFieldResult('p', 'firstName', 'firstName');
$rsm->addFieldResult('p', 'lastName', 'lastname');
$rsm->addFieldResult('p', 'email', 'email');
// create a native query
$sql = 'select p.firstName, p.lastname, p.email from persons p
where match(p.firstname, p.lastname) against(?)';
// execute the query
$query = $em->createNativeQuery($sql, $rsm);
$query->setParameter(1, $term);
// getting the results
return $query->getResult();
}
Finnally, and example:
$term = 'John D';
$results = search($term);
// two results
echo count($results);
Additional notes:
Before mysql 5.7, Full-Text only can be added just to MyISAM tables.
Only can be indexed CHAR, VARCHAR, or TEXT columns.
When using IN NATURAL LANGUAGE MODE in a search, mysql returns an empty resultse when the results represent < 50% of the records.
Maybe you could use the explode function like this:
$busqueda = $_GET['search-customers'];
$names = explode(' ',$busqueda);
if(count($names)>1){
$query->andWhere("(c.firstName LIKE '%{$names[0]}%' AND c.lastName LIKE '%{$names[1]}%')");
}else{
$query->andWhere("(c.firstName LIKE '%$busqueda%' OR c.lastName LIKE '%$busqueda%' OR c.email LIKE '%$busqueda%')");
}
but, using like %word% is inefficient, because it can't use index.
Finally, I decided to concat firstName and lastName. I excluded the email then the query looks like that:
$busqueda = $_GET['search-customers'];
$names = explode(' ',$busqueda);
$hasemail = strpos('#', $busqueda);
if ( $hasemail ) {
$query->andWhere("c.email LIKE '%$busqueda%'");
} else {
$query->andWhere("( CONCAT(c.firstName,' ',c.lastName) LIKE '%$busqueda%' OR c.email LIKE '%$busqueda%')");
}
In your repository you could do something like this:
public function findByTerms($terms) : array
{
$alias = "d";
$qb = $this->createQueryBuilder($alias);
foreach (explode(" ", $terms) as $i => $term) {
$qb
->andWhere($qb->expr()->orX( // nested condition
$qb->expr()->like($alias . ".name", ":term" . $i),
$qb->expr()->like($alias . ".description", ":term" . $i)
))
->setParameter("term" . $i, "%" . $term . "%")
;
}
return $qb->getQuery()->getResult();
}
The Query Builder is able to generate complex queries that scale to your needs
I have an array of search words.
$unprocessed=array("language1 word1", "language1 word2", "language1 word3");
Each word in $unprocessed has an unique entry in a column called "language1"
If found, return row column "language2" else return false. It is important that if the word is not found, false is returned.
1. $unprocessed=array("language1 word1", "language1 word2", "language1 word3");
2. MySQL Query (what is best way?)
3. $processed=array(false,"language2 word2","language2 word3");
Is it possible to do this without looping a query?
What is best way: using WHERE="word", IN("word"), LIKE="word" or something else?
Each word in $unprocessed has an unique entry in a column called "language1"
This means that you do not want to use LIKE. You could run a single query like this and return a single tuple with as many columns as requested words:
SELECT MAX(IF(language1=':word1', 1, 0)) AS hasWord1,
MAX(IF(language1=':word2', 1, 0)) AS hasWord2,
MAX(IF(language1=':word3', 1, 0)) AS hasWord3
FROM table WHERE language1 IN (':word1',':word2',':word3');
This is slightly more performant that three one-word query, especially if you have an index on language1. If you need more columns from the language table (i.e. not only the word but, say, its weight or its SVO status or...), you'd better ask for a tuple for each word. In this case, though, missing words will not be returned (anyway, you would have no data for them). A judicious use of LEFT JOIN plus the first query can be used to avoid this.
SELECT * FROM table WHERE language1 IN (':word1',':word2',':word3');
or
SELECT table2.* FROM table LEFT JOIN table AS table2 USING(primaryKey)
WHERE table.language1 IN (':word1',':word2',':word3');
You can also get one column only to identify the word by using binary representation:
SELECT MAX(IF(language1=':word1', 1, 0))
+ MAX(IF(language1=':word2', 2, 0))
+ MAX(IF(language1=':word3', 4, 0))
AS wordMask, ...
or, in the case of a JOIN, it's enough to get an index:
SELECT MAX(IF(language1=':word1', 1, 0)
+ MAX(IF(language1=':word2', 2, 0)
+ MAX(IF(language1=':word3', 3, 0)
AS wordIndex, ...
To build the query, you can use a PHP foreach loop.
In case of a simple translate table:
SELECT MAX(IF(language1=':word1', language2, ':word1')) AS word1,
MAX(IF(language1=':word2', language2, ':word2')) AS word2,
MAX(IF(language1=':word3', language2, ':word3')) AS word3,
FROM table WHERE language1 IN (':word1',':word2',':word3');
will return wordn containing either the original word, or the "translated" word from the language2 column:
select MAX(IF(language1='computer', language2, '-')) AS hasWord1, MAX(IF(language1='ouijamaflip', language2, '-')) AS hasWord2 FROM tbl WHERE language1 IN ('computer', 'ouijmaflip');
+------------+----------+
| hasWord1 | hasWord2 |
+------------+----------+
| ordinateur | - |
+------------+----------+
You can also have language priority, again building this in PHP:
SELECT
MAX(IF (language1=':word1',
COALESCE(language2, language3, language4, ':default1')))
AS word1,
You can set $default equal to $word, or maybe "?{$word}?" or similar.
$words = array( 'computer', 'bytes', 'processor' /*, 'other'... */);
$langs = array( 'language2', 'language3' /*, 'language4', ... */ );
$rets = array();
$whrs = array();
$defs = array();
foreach ($words as $ndx => $word) {
$rets[] = "COALESCE(IF (language1 = ':word{$ndx}', COALESCE("
. implode(", ", $langs) . "), ':default{$ndx}')) AS word{$ndx}";
$whrs[] = "':word{$ndx}'";
$defs[] = "MISSING_{$word}";
}
$SQL = "SELECT "
. implode(", ", $rets) // All IFs
. " FROM table WHERE language1 IN("
. implode(", ", $whrs) . ");"; // All WHEREs
This will return a query such as:
SELECT
COALESCE(
IF (language1='computer',
COALESCE(language2, language3),
'MISSING_computer')
) AS word1,
COALESCE(
IF(language1='bytes',
COALESCE(language2, language3),
'MISSING_bytes')
) AS word2,
COALESCE(
IF(language1='processor',
COALESCE(language2, language3),
'MISSING_processor')
) AS word3
FROM tbl
WHERE language1 IN ('computer', 'bytes', 'processor');
and finally, with this tbl:
+-----------+------------+-----------+
| language1 | language2 | language3 |
+-----------+------------+-----------+
| computer | ordinateur | NULL |
| bytes | NULL | ottetti |
+-----------+------------+-----------+
it will return
+------------+---------+-------------------+
| word1 | word2 | word3 |
+------------+---------+-------------------+
| ordinateur | ottetti | MISSING_processor |
+------------+---------+-------------------+
ordinateur will be taken from the first language because it's there, ottetti comes from the second language since the first is NULL, and processor returns an error because it's completely missing or has all relevant columns NULL. You can distinguish these cases by adding "language1" (or a string such as 'EMPTY ROW') as the least priority language.
As far as I know, and according to the comments you can't do this with just MySQL, you'll need PHP to process it.
Assuming you're using pdo.. this pseudo code should do it for you
function whatever($unprocessed){
global $db;
$processed = array();
foreach($unprocessed as $u){
$q = $db->prepare("SELECT * FROM language1 WHERE word = :wrd");
$q->execute(array(":wrd"=>$u));
$processed[] = $q->rowCount() ? $q->fetchColumn() : false;
}
return $processed;
}
I have a table called "car_owners", it has three columns known as:
id owner cars
1 Greg 1
2 Gary 3
3 Aaron 2
4 Farb 3
5 REX 1
6 Fred 2
In the following code I get it into array and print it:
$exc = $conn->prepare("SELECT name,state from current_state");
$exc->execute();
while($finalResult = $exc->fetch(PDO::FETCH_ASSOC))
{
$tables[] = $finalResult;
}
var_dump($tables);
once i get this into an array, is there a way i could sort it in a custom order where i could get the out put like follows,
first the owners with 2 cars, then with 1 car and who has 3
owner cars
Aaron 2
Fred 2
Greg 1
REX 1
Farb 3
Gary 3
P.S doing it from the table is not going to work, because im using a loop above the code which makes it impossible to do it from the SQL, can anybody tell me a way to do it from the php
select * from your_table
order by case when cars = 2 then 1
when cars = 1 then 2
when cars = 3 then 3
else 4
end
You can use usort to sort the values. This will also sort by name if two owners have the same number of cars. I have changed the SELECT statement to match the given database definition.
$exc = $conn->prepare("SELECT owner, cars from current_state");
$exc->execute();
while ($finalResult = $exc->fetch(PDO::FETCH_ASSOC))
{
$tables[] = $finalResult;
}
usort(
$tables,
function($a, $b) {
// If same number of cars, sort by name
if ($a['cars'] == $b['cars']) return strcmp($a['owner'], $b['owner']);
// If owner a has two cars, place before b
if ($a['cars'] == 2) return -1;
// If owner b has two cars, place below a
if ($b['cars'] == 2) return 1;
// Neither owner a nor owner b has two cars, sort by number of cars
return ($a['cars'] < $b['cars']) ? -1 : 1;
}
);
foreach ($tables as $row) {
echo $row['owner'], ' => ', $row['cars'], PHP_EOL;
}
Output:
Aaron => 2
Fred => 2
Greg => 1
REX => 1
Farb => 3
Gary => 3
If you have the array prepared from the mysql table, then you can use the following code-
$car_array=array(
"Aaron"=>2,
"Fred"=>2,
"Greg"=>1,
"REX"=>1,
"Farb"=>3,
"Gary"=>3,
);
$sort_array=array("2","1","3");
$new_array=array();
foreach ($sort_array as $key => $value)
{
foreach ($car_array as $key1 => $value1)
{
if ($value1 == $value )
$new_array[$key1]=$value1;
}
}
print_r($new_array);
Consider sorting the resultset through sql itself. The sql provided by #juergen would meet the purpose. The only change I would like to do in the query is that 'add owner field in the order by clause'. Consider the below code snippet
select * from car_owners
order by (case when cars = 2 then 1
when cars = 1 then 2
when cars = 3 then 3
else 4
end), owner
This should meet the purpose and give you the resultset exactly as you needed.
Also, if you explicitly need to sort it through php then you use the php usort() function and sort the array writing a custom defined function.
Using PHP only, you can use uksort function to sort the array using a user-defined comparison function. The following code requires a copy of your $tables variable.
<?php
$tables2=$tables;
uksort($tables2, function($r1, $r2) use ($tables) {
return ($tables[$r1]["cars"]%3) < ($tables[$r2]["cars"]%3);
});
print_r($tables2);
I'm working through this tutorial online: http://goo.gl/qnk6U
The database table: ajax_search
Firstname | Lastname | Age | Hometown | Job
-------------------------------------------
Joe | Smith | 35 | Boulder | CIA
Steve | Apple | 36 | Denver | FBI
(Types are all varchar except age is an int.)
My question. Is the sql select statement below written correctly to query "Joe 35"? For some reason, I can only query "Joe" and it works, but not combining search terms.
$sql = "select * from ajax_search where FirstName like '%$rec%' or LastName like '%$rec%' or Age like '%$rec%' or Hometown like '%$rec%'";
Assuming your query is "Joe 35", then no. Your query matches any row where any of the four fields contains "Joe 35" (in a single field). To query for a user with name Joe and age 35, you'd need to split the search query and do something like:
WHERE Firstname LIKE "$firstname" AND Age LIKE "$age"
You need to split that sting from search query:
$columns = array("Firstname", "Lastname", "Age", "Hometown", "Job");
$string = ""; // acquire query string
$split = explode(" ", $string);
$search_words = array();
foreach ($split as $word) {
$search_words[] = $word;
}
Create query to search all over the fields:
$first = true;
$sql = "select * from ajax_search where";
foreach ($search_words as $word) {
foreach ($columns as $col) {
$sql .= (($first) ? " " : " OR") . "`$col` LIKE \"%" . $word . "%\"";
$first = false;
}
}
Then run this query.
Another and also a better solution would be more complicated word(tag) based indexing, because this can generate morbid queries when used with more words.