Ruby - Rails - SQL query - reordering words in a search term - mysql

I'm working on a project with a search function that is set up as follows:
if params[:search_term].present? && params[:search_term].length > 1
#candidates = #candidates.where("title like ?","%#{params[:search_term]}%")
end
The client has asked me to 'loosen up' the search - specifically the word order. At the moment if there is a candidate with a title of White bar stool and one searches for White stool bar, it returns no results.
Is there any way for me to perform a query where word order is ignored? Or would it be better for me to make new search term params with a different word order, do multiple searches, and combine the results?

You may consider using Arel for this. Arel is the underlying query assembler for rails/activerecord (so no new dependencies) and can be very useful when building complex queries because it offers far more depth than the high level ActiveRecord::QueryMethods.
Arel offers a large selection of predication matchers including in your case matches_any and matches_all. These methods take an Array of Strings and split them into individual search conditions using Like.
For Example to search for all candidates that contain any of the words searched for you can use:
class Candidate < ActiveRecord::Base
def self.search_for(term)
candidates = Candidate.arel_table
where(
candidates[:title].lower.matches_any(
term.split.map { |t| "%#{t.downcase}%" }
)
)
end
end
The end result of search_for (given a search term of 'White stool bar') is:
SELECT [candidates].*
FROM [candidates]
WHERE (
LOWER([candidates].[title]) LIKE '%white%'
OR LOWER([candidates].[title]) LIKE '%stool%'
OR LOWER([candidates].[title]) LIKE '%bar%')
Which appears to be what you are looking for. If it must match all the terms you can instead use matches_all which will result in:
SELECT [candidates].*
FROM [candidates]
WHERE (
LOWER([candidates].[title]) LIKE '%white%'
AND LOWER([candidates].[title]) LIKE '%stool%'
AND LOWER([candidates].[title]) LIKE '%bar%')
See Here for all the available Arel predications.
This has added benefits of basic escaping to avoid things like SQL injection.

You can use the MySQL RLIKE operator, to match with an specific pattern you can create with your sentence.
sentence = 'White stoll bar'
#candidates = #candidates.where('title RLIKE ?', "(#{sentence.tr(' ', '|')})")

Related

How do I create a MySQL query using an array?

I need to take an array and use it for a MySQL query.
I tried looking for methods but all of them seem to be related to PHP arrays and not Ruby.
For example, I have terms = ['genetics', 'elderly', 'health'] and I want to use:
con.query "SELECT col1 FROM data1 WHERE MATCH (col2) AGAINST (terms)"
Is this possible?
You can just join your terms in your against clause:
terms = ['genetics' , 'elderly', 'health']
con.query "SELECT col1 FROM data1 WHERE MATCH col2 AGAINST ('#{terms.join(' ')}')"
Note that using match/against will almost certainly be more performative than using a series of like clauses. See this StackOverflow answer for more information: Which SQL query is better, MATCH AGAINST or LIKE?.
Check out the MySQL documentation for more information on full text searching (including possible operators): http://dev.mysql.com/doc/refman/5.5/en/fulltext-search.html.
I'd highly recommend looking into an ORM, such as Sequel, which makes it very easy to generate the proper query in a DBM independent way.
It allows us to use arrays and hashes conveniently. Here's an example using an array to generate the "where" clause in SQL:
my_posts = posts.where(:category => ['ruby', 'postgres', 'linux'])
# WHERE category IN ('ruby', 'postgres', 'linux')
That particular example is part of the "Filtering Records" section.
In a comment, the OP said:
col2 is text with each row having a paragraph, not just one word.
Then you want a LIKE or regex clause that allows each word to be tested. See the "String search functions" section of "Dataset Filtering " for how Sequel allows you to search inside strings.
The code would be something like:
data1.select(:col1).where(Sequel.like(:col2, terms.map{ |t| "%#{ t }%" } ))
which would generate something like:
SELECT col1 FROM data1 WHERE ((col2 LIKE '%genetics%') OR (col2 LIKE '%elderly%') OR (col2 LIKE '%health%'))

ORDERBY "human" alphabetical order using SQL string manipulation

I have a table of posts with titles that are in "human" alphabetical order but not in computer alphabetical order. These are in two flavors, numerical and alphabetical:
Numerical: Figure 1.9, Figure 1.10, Figure 1.11...
Alphabetical: Figure 1A ... Figure 1Z ... Figure 1AA
If I orderby title, the result is that 1.10-1.19 come between 1.1 and 1.2, and 1AA-1AZ come between 1A and 1B. But this is not what I want; I want "human" alphabetical order, in which 1.10 comes after 1.9 and 1AA comes after 1Z.
I am wondering if there's still a way in SQL to get the order that I want using string manipulation (or something else I haven't thought of).
I am not an expert in SQL, so I don't know if this is possible, but if there were a way to do conditional replacement, then it seems I could impose the order I want by doing this:
delete the period (which can be done with replace, right?)
if the remaining figure number is more than three characters, add a 0 (zero) after the first character.
This would seem to give me the outcome I want: 1.9 would become 109, which comes before 110; 1Z would become 10Z, which comes before 1AA. But can it be done in SQL? If so, what would the syntax be?
Note that I don't want to modify the data itself—just to output the results of the query in the order described.
This is in the context of a Wordpress installation, but I think the question is more suitably an SQL question because various things (such as pagination) depend on the ordering happening at the MySQL query stage, rather than in PHP.
My first thought is to add an additional column that is updated by a trigger or other outside mechanism.
1) Use that column to do the order by
2) Whatever mechanism updates the column will have the logic to create an acceptable order by surrogate (e.g. it would turn 1.1 into AAA or something like that).
Regardless...this is going to be a pain. I do not evny you.
You can create function which have logic to have human sort order like
Alter FUNCTION [dbo].[GetHumanSortOrder] (#ColumnName VARCHAR(50))
RETURNS VARCHAR(20)
AS
BEGIN
DECLARE #HumanSortOrder VARCHAR(20)
SELECT #HumanSortOrder =
CASE
WHEN (LEN(replace(replace(<Column_Name>,'.',''),'Figure ',''))) = 2
THEN
CONCAT (SUBSTRING(replace(replace(<Column_Name>,'.',''),'Figure ',''),1,1),'0',SUBSTRING(replace(replace(<Column_Name>,'.',''),'Figure ',''),2,2))
ELSE
replace(replace(<Column_Name>,'.',''),'Figure ','')
END
FROM <Table_Name> AS a (NOLOCK)
WHERE <Column_Name> = #ColumnName
RETURN #HumanSortOrder
END
this function give you like 104,107,119,10A, 10B etc as desired
And you can use this function as order by
SELECT * FROM <Table_Name> ORDER BY GetHumanSortOrder(<Column_Name>)
Hope this helps

How to search for multiple strings in a database table column?

I am using Rails 3.2.2 and MySQL. I am searching by user name (for instances, John, Anthony, Mark) a database table column this way:
# User controller
User.search_by_name(params[:search]).order(:name)
# User model
def self.search_by_name(search)
if search
where('users.name LIKE ?', "%#{search}%")
else
scoped
end
end
However, since a name can be composed from two or more strings (for instances, John Henry or Henry John, Anthony Maria or Maria Anthony, Mark Alfred or Alfred Mark), I would like to search users also when in the params[:search] are provided more than one name. I tried to use
def self.search_by_name(search)
if search
search.split(' ').each do |string|
where('users.name LIKE ?', "%#{string}%")
end
else
scoped
end
end
but I get the following error (for instance, given I am searching for John Henry):
NoMethodError (undefined method `order' for ["John", "Henry"]:Array).
How can I properly search for multiple names?
I totally think you should do one of the following,
Mysql full text search:
http://devzone.zend.com/26/using-mysql-full-text-searching/
SphinxSearch:
http://www.ibm.com/developerworks/library/os-php-sphinxsearch/
I recommend sphinxsearch since I use it with various cool features
built in with it.
Support for sphinx is amazing too!
Using the squeel gem.
def self.search_by_name(search)
if search
where(name.like_any search.split)
else
scoped
end
end
Note that this will NOT order by matching level. For that you need a search-engine implementation, like gems for sphinx, solr, xapian
also note that your original use of each is incorrent since you meant to 'OR' the conditions. If you do not mind to issue as many db queries as search terms you could even fake the match level ordering.
def self.search_by_name(search)
if search
results = search.split.map do |string|
where('users.name LIKE ?', "%#{string}%").all
end.flatten
ids = results.map(&:id)
# desc order by count of matches
ordered_results = results.uniq.order { |result| -ids.count(result.id) }
else
scoped.all
end
end
This is not an Arel relation that can be further scoped but a plain array though.
Note the 'all' call, so do not even attempt this on a big db.
Also note that this will not order 'a b' above 'b a' if search is 'a b' etc.
So I'm kinda just having fun.

Cannot get information in mysql result with rails

I'm using Rails with ActiveAdmin gem. And I want to select some information from mysql database.
sql = ActiveRecord::Base.connection();
s="SELECT word FROM dics WHERE word LIKE 'tung%'";
ten = sql.execute(s);
But when I printed out "ten" to screen, it showed that:
#<Mysql2::Result:0x4936260>
How can I get the information of records?
I suggest that you don't use ActiveRecord::Base.connection directly. Sticking with ARel syntax should work for most cases, and your example doesn't seem like an edge case.
As stated in the comments above, try the following:
dics = Dic.select(:word).where(["word LIKE ?", "tung%"]).all
In order to pluck some special field of object, not objects themselves, use pluck instead of all:
# instead of .pluck(:word) use real field identifier
dics = Dic.where(["word LIKE ?", "tung%"]).pluck(:word)

Combine 'like' and 'in' in a SqlServer Reporting Services query?

The following doesn't work, but something like this is what I'm looking for.
select *
from Products
where Description like (#SearchedDescription + %)
SSRS uses the # operator in-front of a parameter to simulate an 'in', and I'm not finding a way to match up a string to a list of strings.
There are a few options on how to use a LIKE operator with a parameter.
OPTION 1
If you add the % to the parameter value, then you can customize how the LIKE filter will be processed. For instance, your query could be:
SELECT name
FROM master.dbo.sysobjects
WHERE name LIKE #ReportParameter1
For the data set to use the LIKE statement properly, then you could use a parameter value like sysa%. When I tested a sample report in SSRS 2008 using this code, I returned the following four tables:
sysallocunits
sysaudacts
sysasymkeys
sysaltfiles
OPTION 2
Another way to do this that doesn't require the user to add any '%' symbol is to generate a variable that has the code and exceute the variable.
DECLARE #DynamicSQL NVARCHAR(MAX)
SET #DynamicSQL =
'SELECT name, id, xtype
FROM dbo.sysobjects
WHERE name LIKE ''' + #ReportParameter1 + '%''
'
EXEC (#DynamicSQL)
This will give you finer controller over how the LIKE statement will be used. If you don't want users to inject any additional operators, then you can always add code to strip out non alpha-numeric characters before merging it into the final query.
OPTION 3
You can create a stored procedure that controls this functionality. I generally prefer to use stored procedures as data sources for SSRS and never allow dynamically generated SQL, but that's just a preference of mine. This helps with discoverability when performing dependency analysis checks and also allows you to ensure optimal query performance.
OPTION 4
Create a .NET code assembly that helps dynamically generate the SQL code. I think this is overkill and a poor choice at best, but it could work conceivably.
Have you tried to do:
select * from Products where Description like (#SearchedDescription + '%')
(Putting single quotes around the % sign?)
Dano, which version of SSRS are you using? If it's RS2000, the multi-parameter list is
not officially supported, but there is a workaround....
put like this:
select *
from tsStudent
where studentName like #SName+'%'
I know this is super old, but this came up in my search to solve the same problem, and I wound up using a solution not described here. I'm adding a new potential solution to help whomever else might follow.
As written, this solution only works in SQL Server 2016 and later, but can be adapted for older versions by writing a custom string_split UDF, and by using a subquery instead of a CTE.
First, map your #SearchedDescription into your Dataset as a single string using JOIN:
=JOIN(#SearchedDedscription, ",")
Then use STRING_SPLIT to map your "A,B,C,D" kind of string into a tabular structure.
;with
SearchTerms as (
select distinct
Value
from
string_split(#SearchedDescription, ',')
)
select distinct
*
from
Products
inner join SearchTerms on
Products.Description like SearchTerms.Value + '%'
If someone adds the same search term multiple times, this would duplicate rows in the result set. Similarly, a single product could match multiple search terms. I've added distinct to both the SearchTerms CTE and the main query to try to suppress this inappropriate row duplication.
If your query is more complex (including results from other joins) then this could become an increasingly big problem. Just be aware of it, it's the main drawback of this method.