ActiveRecord (SQL) query multiple columns only if search string not empty - sql

Using a PG database filled with registered voters.
Trying to set it up so I can search by first name, last name, zip or city. I want to be able to find all voters that match all of the entered params, but having trouble dealing with empty search fields.
where("zip LIKE ? OR city LIKE ? OR last_name LIKE ? OR first_name LIKE ?",
"#{params[:zip]}","#{params[:city]}","#{params[:last_name]}","#{params[:first_name]}")
Is there a better way to build it out so that it matches ALL entered parameters, but ignores empty string parameters? Right now if I enter a first and last name 'John Smith' I will get 'John Jones' and 'Jane Smith'.

This can do the trick:
attrs_name_to_search = %w( zip city last_name first_name )
fitlered_params = params.slice(*attrs_name_to_search).select { |_,v| v.present? }
sql_cond = filtered_params.map { |k,_| "#{k} LIKE :#{k}" }.join(' OR ')
YourModel.where(sql_cond, filtered_params)
But it should return all the records if no zip/city/last_name/first_name is given.

Related

Conditional Doobie query with Option field

I have following
case class Request(name:Option[String], age: Option[Int], address: Option[List[String]])
And I want to construct a query like this the conditions should apply if and only if the field is defined:
val req = Request(Some("abc"), Some(27), Some(List["add1", "add2"])
select name, age, email from user where name = "abc" AND age = 27 AND address in("add1", "add2");
I went through doobies documentation and found about fragments which allow me to do the following.
val baseSql: Fragment = sql"select name, age, email from user";
val nameFilter: Option[Fragment] = name.map(x => fr" name = $x")
val ageFilter: Option[Fragment] = age.map(x => fr" age = $x")
val addressFilter: Option[Fragment] = address.map(x => fr " address IN ( " ++ x.map(y => fr "$y").intercalate(fr",") ++ fr" )"
val q = baseSql ++ whereAndOpt(nameFilter, ageFilter, addressFilter)
from my understanding the query should look like this if all the fields are defined:
select name, age, email from user where name = "abc" AND age = 27 AND address in("add1","add2");
but the query looks like this:
select name, age, email from user where name = ? AND age = ? AND address in(?);
What is wrong here I am not able to find that.
Thanks in advance !!!!
Everything is fine.
Doobie prevents SQL injections by SQL functionality where you use ? in your query (parametrized query), and then pass the values that database should put into the consecutive ? arguments.
Think like this: if someone posted name = "''; DROP table users; SELECT 1". Then you'd end up with
select name, age, email from user where name = ''; DROP table users; SELECT 1
which could be a problem.
Since the database is inserting arguments for you, it can do it after the parsing of a raw text, when such injection is impossible. This functionality is used not only by Doobie but by virtually every modern library or framework that let you talk to database at level higher than plain driver.
So what you see is a parametrized query in the way that database will see it, you just don't see the parameters that will be passed to it.

SQL Query to Substitute value in the scanned results and update that field of Table

I have a 1 to many Organization: Users relationship.
I want to fetch the usernames of all User model of an Organization, capture a part of that username and append/substitute it with new value.
Here is how I am doing:
Form the raw SQL to Get the matching usernames and replace them with new value.
raw = "SELECT REGEXP_REPLACE($1::string[], '(^[a-z0-9]+)((\s[a-z0-9]+))*\#([a-z0-9]+)$', m.name[1] || '#' || $2) FROM (SELECT REGEXP_MATCHES($1::string[], '(^[a-z0-9]+)((\s[a-z0-9]+))*\#([a-z0-9]+)$') AS name) m"
Get the matching usernames and replace them with new value.
usernames: list of usernames retrieved from queryable
Repo.query(raw, [usernames, a_string])
Error I am getting
SELECT REGEXP_REPLACE($1::string[], '(^[a-z0-9]+)(( [a-z0-9]+))#([a-z0-9]+)$', m.name[1] || '#' || $2) FROM (SELECT REGEXP_MATCHES($1::string[], '(^[a-z0-9]+)(( [a-z0-9]+))#([a-z0-9]+)$') AS name) m [["tradeboox#trdbx18"], "trdbx17"]
{:error,
%Postgrex.Error{connection_id: 7222, message: nil,
postgres: %{code: :undefined_object, file: "parse_type.c", line: "257",
message: "type \"string[]\" does not exist", pg_code: "42704",
position: "137", routine: "typenameType", severity: "ERROR",
unknown: "ERROR"}}}
FYI: The username field of User model is of type citext
Once I get the replaced values, I want to update the User with something like
update([u], set: [username: new_values])
Any ideas on how to proceed with this?
`
There is no string type in PostgreSQL.
Function regexp_matches accepts as first parameter only text and it can't be array. So what you need to do is first change that type to text, then unnest($1::text[]) your array. Iterate over resulting set of rows with those regexp.
raw = "SELECT REGEXP_REPLACE(m.item, '(^[a-z0-9]+)((\s[a-z0-9]+))*\#([a-z0-9]+)$', m.name[1] || '#' || $2)
FROM (
SELECT item, REGEXP_MATCHES(item, '(^[a-z0-9]+)((\s[a-z0-9]+))*\#([a-z0-9]+)$') AS name
FROM unnest($1::text[]) AS items(item)
) m"
If I understand it correctly, you are trying to replace everything after # with some different string - if that is the case, then your regexp will put anything after spacebar into second element of matches array. You would need this instead: ((^[a-z0-9]+)(\s[a-z0-9]+)*).
If above is true, then you can do all that much easier with this:
SELECT REGEXP_REPLACE(item, '((^[a-z0-9]+)(\s[a-z0-9]+)*)\#([a-z0-9]+)$', '\1' || $2) AS name
FROM unnest($1::text[]) AS items(item)
Best practice however is to simply do replace in UPDATE statement:
UPDATE "User" SET
name = concat(split_part(name, '#', 1), '#', $2)
WHERE organization_id = $3
AND name ~* '^[a-z0-9]+(\s[a-z0-9]+)*\#[a-z0-9]+$'
It will split name by #, take first part, then append # and whatever is assigned to $2 (domain name I guess). It will update only rows that have organization_id matching to some id and have names matching your regexp (you can omit regexp if you want to change all names from organization). Make sure that table in actually named User, case sensitive, or remove double quotes to have case insensitive version.
I sadly do not know how to do this in your ORM.

Rails query join on regex

This is a query run very infrequently (as admin), and it's not worth indexing/ using up RAM with sphinx.
So, with the variables set up something like this:
fn, ln, em = 'John', 'Smith', 'johnsmith#gmail.com'
The query is something like this:
profiles = Profile.joins(:user).where(:users=>{"email" => em}).
where("first_name LIKE '%#{fn}%' AND last_name LIKE '%#{ln}%'")
Except that I want the email to accept a substring (e.g. if em == 'th#gm', then it should match 'johnsmith#gmail.com'). How do you write this to do a join selecting only those users with a variable matching a regex?
(I got my inspiration from here):
Have you tried something like:
profiles = Profile.joins(:user).where("first_name LIKE ? AND last_name LIKE ?", "%#{fn}%", "%#{ln}%")
profiles.select{|p| p.user.email =~ /#{em}/}

concatening rows in a rails style

Suppose I want to check if some string appears as name-surname in the concatenation of two rows name and surname of a table. How do I write valid this sql in a rails style ? And is these syntaxes correct ?
SELECT (name + '-'+ surname) FROM table1 where (name + '-'+ surname = string)
table.select(:name+'-'+:surname).where((:name+'-'+:surname) == string)
I am not sure if I am understanding your question correctly, but I think this is what you are wanting. For the following string variable,
string = "John - Doe"
you want to pull a record like this from the User table
id | name | sur_name
1 | John | Doe
If this is what you want, you can actually massage your string variable like this
parsed_string = string.split('-')
name = parsed_string[0].strip # strip to remove white spaces
sur_name = parse_string[1].strip
Then, you can run the following code to get what you want:
users = User.where(:name => name, :surname => sur_name)
Hope this answers your question.

how do i search an address form a group of columns from a table (zip,state,country)?

scenarioi:-
1) User enters an address into a search field. Address maybe zip,state or country
2) Now i have to take that address which may be in any order or which may just contain the country, state or zip and check the database if the the results exists or not.
Any advice on this topic would be awesome
How about this one?
'SELECT * FROM table WHERE address LIKE \''. addslashes($search) .'\'
OR zip = '. (int)$search .'
OR country LIKE \''. addslashes($search) .'\''
I would do something like :
split address on space
build where clause as
WHERE zip||state||country like '%address_part_one%'
OR zip||state||country like '%address_part_two%'
and so on ...
If you have fulltext enabled for the three fields :
WHERE MATCH (zip,state,country) AGAINST ('address_part_one')
OR MATCH (zip,state,country) AGAINST ('address_part_two')
...