I am searching from 3 tables currently (will search in more after sorting this out). This query brings all the results in the order of the tables listed in query. Whereas I want to get the most relevant search results first.
(Select name, url, text, 'behandlinger_scat' AS `table` from behandlinger_scat where name LIKE '%KEYWORD%' OR text LIKE '%KEYWORD%')
UNION
(Select name, url, text, 'hudsykdommer_scat' AS `table` from hudsykdommer_scat where name LIKE '%KEYWORD%' OR text LIKE '%KEYWORD%')
UNION
(Select name, url, text, 'om_oss' AS `table` from om_oss where name LIKE '%KEYWORD%' OR text LIKE '%KEYWORD%')
Any help would be appreciated.
You can use a method to order by the points you dynamically give the results, as in this example (you will need to alias your tables so SQL will understand what column you're referring to):
ORDER BY
CASE WHEN name LIKE table.keywords THEN 100 ELSE 0 END +
CASE WHEN name LIKE table2.keywords THEN 10 ELSE 0 END +
CASE WHEN text LIKE table2.keyword THEN 1 ELSE 0 END
DESC
This is merely an example, but the concept is the following:
You decide how many "points" each "match" will receive (e.g name matches keyword is 100 points, text matches it - a little less) then, each row "accumulates" points with correlation to its matches, and the row with the most points shows first.
Related
I have a SQL database with music songs. Each song of course has an artist, an album and a genre. They also have a general 'popularity' counter, which was obtained from an external source. However, I want to give users the opportunity to vote on the songs as well. In the end, the search results should be ordered on this popularity, as well as the accuracy of the results with the original query.
The current query I use is as follows:
SELECT *
FROM p2pm_tracks
WHERE
`artist` LIKE '%$searchquestion%' OR
`genres` LIKE '%$searchquestion%' OR
`trackname` LIKE '%$searchquestion%' OR
`album_name` LIKE '%$searchquestion%'
ORDER BY `popularity` DESC
LIMIT $startingpoint, $resultsperpage
I struggle with the following:
Users search for something. I look in all fields: song title, artist, album and genre. However, usually a certain search query contains (parts of) multiple of these tracks.
For instance, a user might search for Opening Philip Glass.
In this case, the first word is the name of the song, and the second and third words are the artist name.
Another example:
If I split the query on spaces, the correct tracks are found. However, if another track that matches only one of these words has a higher popularity, it will be returned before the one that actually accurately matches the search query.
I still want to sort the results in a way that things that match bigger parts of the query at once are at the top. How can I do that using SQL?
I have the static popularity and want to create a new one. Therefore, I want to use the average of all votes on a certain track (these votes are stored in another table), except in the cases where there are no votes yet. How can I construct a SQL query that does this?
My application is built in PHP, but I would like to do as much as possible of this in SQL, preferably in as few queries as possible to reduce latency.
Any help would be appreciated.
You can add a weight for every column in your search results.
Here's the code:
SELECT *,
CASE WHEN `artist` LIKE '%$searchquestion%' THEN 1 ELSE 0 END AS artist_match,
CASE WHEN `genres` LIKE '%$searchquestion%' THEN 1 ELSE 0 END AS genres_match,
CASE WHEN `trackname` LIKE '%$searchquestion%' THEN 1 ELSE 0 END AS trackname_match,
CASE WHEN `album_name` LIKE '%$searchquestion%' THEN 1 ELSE 0 END AS album_name_match,
FROM p2pm_tracks
WHERE
`artist` LIKE '%$searchquestion%' OR
`genres` LIKE '%$searchquestion%' OR
`trackname` LIKE '%$searchquestion%' OR
`album_name` LIKE '%$searchquestion%'
ORDER BY
`artist_match` DESC,
`genres_match` DESC,
`trackname_match` DESC,
`album_name_match` DESC,
`popularity` DESC,
LIMIT $startingpoint, $resultsperpage
This query will gather the results related to:
the artist FIRST,
THEN the genre,
THEN the track's title,
THEN the album's name,
THEN the popularity of the song
To optimize this query, you should avoid using "LIKE" and use "FULLTEXT SEARCH" instead.
The optimized code will be:
SELECT *,
CASE WHEN MATCH (artist) AGAINST ('$searchquestion') THEN 1 ELSE 0 END AS artist_match,
CASE WHEN MATCH (genres) AGAINST ('$searchquestion') THEN 1 ELSE 0 END AS genres_match,
CASE WHEN MATCH (trackname) AGAINST ('$searchquestion') THEN 1 ELSE 0 END AS trackname_match,
CASE WHEN MATCH (album_name) AGAINST ('$searchquestion') THEN 1 ELSE 0 END AS album_name_match,
FROM p2pm_tracks
WHERE
MATCH (artist) AGAINST ('$searchquestion') OR
MATCH (genres) AGAINST ('$searchquestion') OR
MATCH (trackname) AGAINST ('$searchquestion') OR
MATCH (album_name) AGAINST ('$searchquestion')
ORDER BY
`artist_match` DESC,
`genres_match` DESC,
`trackname_match` DESC,
`album_name_match` DESC,
`popularity` DESC,
LIMIT $startingpoint, $resultsperpage
And make sure that you're using the MyISAM engine for the MySQL table and that you created indexes for the columns you want to search.
The code for your MySQL table should look like:
CREATE TABLE p2pm_tracks (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
artist VARCHAR(255) NOT NULL,
trackname VARCHAR(255) NOT NULL,
...
...
FULLTEXT (artist,trackname)
) ENGINE=MyISAM;
For more info, check the following:
- http://dev.mysql.com/doc/refman/5.0/en/fulltext-natural-language.html
- http://dev.mysql.com/doc/refman/5.5/en/fulltext-boolean.html
If you're looking for something more advanced, then look into Solr (based on Lucene), Sphinx, ElasticSearch (based on Lucene) etc.
MySQL is not that good in searching for text :(
What you could try to do is take a look at full text search functionality (http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html)
With the match against function you can get a relevance where you can order on.
SELECT p2pm_tracks.*,
MATCH (artist, genres) AGAINST ('some words') AS relevance,
MATCH (artist) AGAINST ('some words') AS artist_relevance
Please don't use LIKE. It's very slow. You can use full text search in mysql but you can not determinate which column is more important.
Better solution is mysql with sphinx.
Hmm, to match your 1. example is difficult in SQL, I´m not sure if there is a function.
what you need is something like this funktion in php
http://php.net/manual/function.similar-text.php
Or you select in your sql query only per average vote and calculate how "good" the results match via php and the similar-text function.
In my MSSQL table I have two fields, first one is post_name (position at job) and another filed is org_name (names or location of organizations). I'm writting a PHP script to seach through those two fields. I use Select2 Bootstrap plugin which is basically a seach line with dropdown options fetched from the database based on what user is typing in.
A user usually search for full job title including post_name and org_name. Let's say "Chief Sales Manager Toronto" where first 3 words are from first field and the last word is from second field. When a user start typing Chief Sales Man..." he should be alble to get the whole list of such managers in Toronto, Cupertino or whatever.
The SELECT query I use for this is:
SELECT post_name, org_name
FROM table
WHERE post_name LIKE 'searchTerm%';
That gives me the needed job titles only if I didn't start typing the org name as obviously then it tries to find the whole sentence in post_name filed and gives an empty string.
I also tried:
SELECT post_name, org_name
FROM table
WHERE post_name LIKE 'searchTerm%' OR org_name LIKE '%searchTerm%';
or I was trying to split the searchTerm and try to seach in org_name field by using end of searchTerm:
SELECT post_name, org_name
FROM table
WHERE post_name LIKE 'searchTerm%'
AND org_name LIKE '%.substr(searchTerm, -6).%';
But the last ones are even more pathetic. I was thinking of using CONCAT to unite those two fields so I can search in those two as one, something like
SELECT CONCAT(post_name, org_name) as full_title
FROM table
WHERE full_title LIKE 'searchTerm%';
But in MSSQL Server Express 2005 I get an error of non existing function.
Is there any way I could search such a one sentence in those two fields simultaniously?
The query you want in SQL Server seems to be:
SELECT post_name, org_name
FROM table
WHERE post_name + org_name LIKE 'searchTerm%';
You might want to include a space between the columns as well.
That said, you might want to look into full text search capabilities. It might be a better solution to your problem.
Your last query would work like this
SELECT post_name, org_name
FROM (
SELECT post_name, org_name,
CONCAT(post_name,' ', org_name) as full_title
FROM table
) z
WHERE full_title LIKE 'searchTerm%';
I have a table that contains 3 text fields, and an ID one.
The table exists solely to get collection of ID's of posts based on relevance of a user search.
Problem is I lack the Einsteinian intellect necessary to warp the SQL continuum to get the desired results -
SELECT `id` FROM `wp_ss_images` WHERE `keywords` LIKE '%cute%' OR `title` LIKE '%cute%' OR `content` LIKE '%cute%'
Is this really enough to get a relevant-to-least-relevant list, or is there a better way?
Minding of course databases could be up to 20k rows, I want to keep it efficient.
Here is an update - I've gone the fulltext route -
EXAMPLE:
SELECT `id` FROM `wp_ss_images` WHERE MATCH (`keywords`,`title`,`content`) AGAINST ('+cute +dog' IN BOOLEAN MODE);
However it seems to be just grabbing all entries with any of the words. How can I refine this to show relevance by occurances?
To get a list of results based on the relevance of the number of occurrences of keywords in each field (meaning cute appears in all three fields first, then in 2 of the fields, etc.), you could do something like this:
SELECT id
FROM (
SELECT id,
(keywords LIKE '%cute%') + (title LIKE '%cute%') + (content LIKE '%cute%') total
FROM wp_ss_images
) t
WHERE total > 0
ORDER BY total DESC
SQL Fiddle Demo
You could concatenate the fields which will be better than searching them individually
SELECT `id` FROM `wp_ss_images` WHERE CONCAT(`keywords`,`title`,`content`) LIKE '%cute%'
This doesn't help with the 'greatest to least' part of your question though.
I can't seem to grasp how I can select records when the records of one user span multiple rows.
Here is the schema of the table.
user_id key value
------------------------------------------
1 text this is sample text
1 text_status 0
2 text this is sample text
2 text_status 1
from the above table/row you can see that each user has info that has multiple rows. So in this case how do I select say "All the IDs, text value where text_status is "1"?
And to complicate it 1 step further, I need the email address of these accounts which is on another table. How can I write 1 select statement to pull in the email address as well? I know there is a JOIN statement for this but it's a bit complicated for me especially I can't even figure out the first part.
Added Note I must state that this table schema is a Wordpress default table wp_usermeta..
SELECT t1.*
FROM tbl t2
INNER JOIN tbl t1 ON t1.user_id = t2.user_id
AND t1.key = 'text'
WHERE t2.key = 'text_status'
AND t2.value = '1'
I think you've set up your table incorrectly. Make text_status and value exist within the same row.
The way it is right now, you would have to conduct two queries to get to your end result. Where as, the correct way needs only one.
This arbitrary key:value list scheme is alluring because of its flexibility. But it complicates queries obviously. Depending on the structure of your second table you could get away with:
SELECT key, value FROM user_table WHERE user_id=123
UNION ALL
SELECT 'email' as key, email as value FROM email_table WHERE user_id=123
But that pretty much only returns a list still, not a set of fields.
key and value looks wrong. SQL already gives you "key" (in the column name) and multiple "values" (in the values given per column in each row).
You've designed your table in a way that contravenes the way Database Management Systems are designed to work, which is leading to your problem. Read about database normalization.
Ideally your table would look something like this:
user_id text_status text
------------------------------------------
1 0 this is sample text
2 1 this is sample text
Then your query looks like:
SELECT `user_id`, `text` FROM `table` WHERE `text_status` = '1';
As your table stands now, you'll need something like (untested):
SELECT `table`.*
FROM `table` LEFT JOIN
(SELECT `user_id`
FROM `table`
WHERE `key` = "text_status"
AND `value` = "1"
) AS `_` USING(`user_id`)
WHERE `table`.`key` = "text"
I have two tables in one DB, one called Cottages and one called Hotels.
In both tables they have the same named fields.
I basically have a search bar that i want it to search in both of the fields in both of the tables. (the two fields being called "Name" and "Location"
SO far I have
$sql = mysql_query("SELECT * FROM Cottages WHERE Name LIKE '%$term%' or Location LIKE '%$term%' LIMIT 0, 30");
But this only searches the Cottages table, how can I make it search both the cottages and hotel tables?
Would be better if you merge both tables in only one and add a new field like type (with values like cottage or hotel) to identiy each record.
That's called normalization and it's exactly what WordPress do when it save posts, categories, attachments and pages on the sabe database table.
TiuTalk's answer is right - if the columns are the same in both tables you should probably only use one table for this data, and add a type column.
In addition using LIKE '%foo%' is slow. You should look into full text search. It also handles multiple tables in the same way as with LIKE plus you can sort by relevance. When sorting by relevance you get the most relevant rows first regardless of which table they came from.
If you can't change your design and you want to get exactly half the results from each table you can query each separately and use UNION ALL to combine the results:
(SELECT * FROM Cottages WHERE Name LIKE '%$term%' or Location LIKE '%$term%' LIMIT 15)
UNION ALL
(SELECT * FROM Hotels WHERE Name LIKE '%$term%' or Location LIKE '%$term%' LIMIT 15)
Obviously the columns must be the same in both tables for this to work. Don't use SELECT * though - you should explicitly list the column names otherwise reordering the columns could cause this query to break.