This is hard to explain, so I'll start with what I've got.
User has_many Dependents
Users and Dependents have names.
I want a scope on User that will return all of the users whose names, OR whose dependents names match a certain fuzzy text.
It seems to not return all of the expected results. This is my current scope:
scope :by_fuzzy_text, lambda { |text|
text = "%#{text}%"
joins(:dependents).where('name ILIKE ? OR dependents.name ILIKE ?', text, text).uniq
}
The issue is that it's returning unexpected results. It seems to only work on the first 11 Users in my database, despite there being over 100. So no matter how perfectly the scope matches, it will only return the results who are one of the first 11 users in the database. I do NOT have this issue if I'm not using Joins.
Hopefully that makes sense. The uniq on the end is because if the user has multiple dependents, the user is returned multiple times, once for each dependent.
Example:
User1 = "Sam Smith"
-Dependent1.1 = "Ralph Smith"
-Dependent1.2 = "Alex Smith"
User2 = "April Shower"
-Dependent2.1 = "Zach Shower"
-Dependen2.2 = "Sally Smith"
User.by_fuzzy_text('w') => [User2]
(April Shower, Zach Shower)
User.by_fuzzy_text('z') => [User2]
(Zach Shower)
User.by_fuzzy_text('x') => [User1]
(Alex Smith)
User.by_fuzzy_text('smith') => [User1, User2]
(Sam Smith, Ralph Smith, Alex Smith, Sally Smith)
I needed to do a LEFT OUTER JOINS to include the Users who did not have any dependents.
joins('LEFT OUTER JOIN dependents ON users.id = dependents.user_id').where('name ILIKE ? OR name ILIKE ?', text, text).uniq`
Related
Given a database table with these three rows:
first_name
last_name
John
Doe
Will
Smith
John
Smith
Note: I specifically left out the id here because we might be dealing with a case where either we don't have the id or maybe [first_name, last_name] is the composite primary key.
How do I retrieve John Doe and Will Smith without also retrieving John Smith in one database round-trip?
We can do it for one record:
We can use the rom-sql by_pk method to retrieve one record by composite primary keys (if this happens to be a composite primary key).
users.by_pk('John', 'Doe')
We can use where (from pretty much any database adapter) to retrieve the record
users.where(first_name: 'John', last_name: 'Doe')`
But how do we do the same for multiple records?
(1) doesn't work (or at least I don't know the syntax). Please enlighten me if you do!
The trivial way is to just run a map in Ruby and retrieve it:
array_of_first_names_and_last_names.map do |first_name_and_last_name|
first_name, last_name = first_name_and_last_name
users.by_pk(first_name, last_name)
end
We can do the same trivial map in Ruby for (2).
But how can we do it in one database round-trip?
(2) does not work because I will accidentally retrieve John Smith as well if I just do this:
users.where(first_name: ['John', 'Will'], last_name: ['Doe', 'Smith'])
Describe what you’ve tried
I've tried to do this in Sequel (and you can do it in SQL as well if you wanted):
def find_all_by_pk(array_of_first_names_and_last_names:) # [['John', 'Doe'], ['Will', 'Smith']]
dataset = users.dataset
array_of_first_names_and_last_names.each.with_index do |first_name_and_last_name, index|
first_name, last_name = first_name_and_last_name
index += 1
dataset = if index == 1
dataset.where(first_name: first_name, last_name: last_name)
else
dataset.or(first_name: first_name, last_name: last_name)
end
end
dataset.to_a
end
UPDATE: A few months later (17th Nov 2022), I stumbled upon my own question while trying to solve a similar problem. All I managed to do was make the Ruby part of it a bit cleaner:
def find_all_by_pk(array_of_first_names_and_last_names:) # [['John', 'Doe'], ['Will', 'Smith']]
head, *tail = array_of_first_names_and_last_names
initial_query = users.dataset.where(first_name: head[0], last_name: head[1])
tail.reduce(initial_query) do |query, first_name_and_last_name|
first_name, last_name = first_name_and_last_name
query.or(first_name: first_name, last_name: last_name)
end.to_a
end
Is there a better/cleaner/more idiomatic way to do this?
I have 2 nodes:
Students and Subjects.
I want to be able to add multiple student names to multiple subjects at the same time using cypher query.
So far I have done it by iterating through the list of names of students and subjects and executing the query for each. but is there a way to do the same in the query itself?
This is the query I use for adding 1 student to 1 subject:
MATCH
(s:Student)-[:STUDENT_BELONGS_TO]->(c:Classroom),
(u:Subjects)-[:SUBJECTS_TAUGHT_IN]->(c:Classroom)
WHERE
s.id = ${"$"}studentId
AND c.id = ${"$"}classroomId
AND u.name = ${"$"}subjectNames
AND NOT (s)-[:IN_SUBJECT]->(u)
CREATE (s)-[:IN_SUBJECT]->(u)
So I want to be able to receive multiple subjectNames and studentIds at once to create these connections. Any guidance for multi relationships in cypher ?
I think what you are looking for is UNWIND. If you have an array as parameter to your query:
studentList :
[
studentId: "sid1", classroomId: "cid1", subjectNames: ['s1','s2'] },
studentId: "sid2", classroomId: "cid2", subjectNames: ['s1','s3'] },
...
]
You can UNWIND that parameter in the beginning of your query:
UNWIND $studentList as student
MATCH
(s:Student)-[:STUDENT_BELONGS_TO]->(c:Classroom),
(u:Subjects)-[:SUBJECTS_TAUGHT_IN]->(c:Classroom)
WHERE
s.id = student.studentId
AND c.id = student.classroomId
AND u.name = in student.subjectNames
AND NOT (s)-[:IN_SUBJECT]->(u)
CREATE (s)-[:IN_SUBJECT]->(u)
You probably need to use UNWIND.
I haven't tested the code, but something like this might work:
MATCH
(s:Student)-[:STUDENT_BELONGS_TO]->(c:Classroom),
(u:Subjects)-[:SUBJECTS_TAUGHT_IN]->(c:Classroom)
WITH
s AS student, COLLECT(u) AS subjects
UNWIND subjects AS subject
CREATE (student)-[:IN_SUBJECT]->(subject)
I have two models: an owner and a pet. An owner has_many :pets and a pet belongs_to :owner.
What I want to do is grab only those owners that have pets which ALL weigh over 30lbs.
#app/models/owner.rb
class Owner < ActiveRecord::Base
has_many :pets
#return only those owners that have heavy pets
end
#app/models/pet.rb
class Pet < ActiveRecord::Base
belongs_to :owner
scope :heavy, ->{ where(["weight > ?", 30])}
end
Here is what is in my database. I have three owners:
Neil, and ALL of which ARE heavy;
John, and ALL of which ARE NOT heavy;
Bob, and SOME of his pets ARE heavy and SOME that ARE NOT heavy.
The query should return only Neil. Right now my attempts return Neil and Bob.
You can form a group for each owner_id and check, if all rows within group match required condition or at least one row doesn't match it, you can achieve it with group by and having clauses:
scope :heavy, -> { group("owner_id").having(["count(case when weight <= ? then weight end) = 0", 30]) }
There is also another option, more of a Rails-ActiverRecord approach:
scope :heavy, -> { where.not(owner_id: Pet.where(["weight <= ?", 30]).distinct.pluck(:owner_id)).distinct }
Here you get all owner_ids that don't fit condition (searching by contradiction) and exclude them from the result of original query.
Isn't this simply a matter of finding the owners for whom the minimum pet weight is greater than some value:
scope :heavy, -> { group("owner_id").joins(:pets).having("min(pets.weight) >= ?", 30)}
Or conversely,
scope :light, -> { group("owner_id").joins(:pets).having("max(pets.weight) < ?", 30)}
These are scopes on the Owner, by the way, not the Pet
Another approach is to turn this into a scope on Owner:
Owner.where(Pet.where.not("pets.owner_id = owners.id and pets.weight < ?", 30).exists)
Subtly different, as it is checking for the non-existence of a per with a weight less than 30, so if an owner has no pets then this condition will match for that owner.
In database terms, this is going to be the most efficient query for large data sets.
Indexing of pets(owner_id, weight) is recommended for both these approaches.
What if you do it in two steps, first you get all owner_ids that have at least 1 heavy pet, then get all owner_ids that have at least 1 not-heavy pet and then grab the owners where id exists in the first array but not in the second?
Something like:
scope :not_heavy, -> { where('weight <= ?', 30) }
...
owner_ids = Pet.heavy.pluck(:owner_id) - Pet.not_heavy.pluck(:owner_id)
owners_with_all_pets_heavy = Owner.where(id: owner_ids)
You can just add a the uniq to your scope:
scope :heavy_pets, -> { uniq.joins(:pets).merge(Pet.heavy) }
It works on a database level, using the distinct query.
I have:
Two database tables:
Users: id, title
Infos: id, type, user_id_createdby, user_id_addressedto, text
in Infos I have records of setting a dates of meetings between users:
Infos record: id: 1, type: "nextmeeting", user_id_createdby: 47, user_id_addressedto: 51, text: "2011/01/13"
beside "nextmeeting" I have other types of data between users as well
while a User logged in I'm showing him a list of users with whom he has a meetings by collecting: unique user_id_addressedto and current_user => user_id_createdby
from Info to array #repilents and then User.find(:all, :conditions => {:id => #repilents})
Question:
How I can sort list of Users by dates from Infos.text where type: "nextmeeting"?
like:
User 4 - 2011/01/05
User 8 - 2011/01/13
User 2 - 2011/01/21
User 5 - Next meeting not defined
User 3 - Next meeting not defined
If you're sure that there will only be one row in Infos that has the type "nextmeeting", you can use a left outer join to link users to meetings:
#users = User.joins("LEFT OUTER JOIN infos ON infos.user_id_createdby = users.id")
To order by the descending date:
#users = User.joins("LEFT OUTER JOIN infos ON infos.user_id_createdby = users.id").order("infos.text DESC")
Now some random comments:
type is a reserved word and you shouldn't use it as a column name (http://stackoverflow.com/questions/2293618/rails-form-not-saving-type-field-newbie)
storing dates as text is going to make your life difficult
You might want to rethink this generic "infos" column and make a distinct Meeting model.
For the recipient field in my message system, if you enter Megan Fo, it will ask you "Did you mean Megan Fox?". Then I also have a "Who did you mean? Megan Fox, Megan Foxxy, Megan Foxxie" if there's more than one found.
And then ofcourse there is if you made it correct. (if this SQL statement is returning 1 only out and the full is 1).
SELECT users.id, users.firstname, users.lastname,
(users.firstname = 'Meg' AND users.lastname = 'Meg') AS full FROM users
INNER JOIN users_friends ON users.id=users_friends.uID
WHERE users_friends.bID='1' AND users_friends.type = 'friend' AND users_friends.accepted = '1' AND (
(users.firstname = 'Meg' AND users.lastname='Meg') OR
(users.firstname LIKE 'Meg%' AND users.lastname LIKE 'Meg%') OR
users.firstname LIKE 'Meg%' OR
users.lastname LIKE 'Meg%')
This is working fine, although now I am having an issue when Im trying to send to a user
"Meg Meg"
Then it returns 2, "Meg Meg" and "Megan Fox". I want it to return one only if it matchs the full. So now I am keep getting "Who did you mean?" as i build it up so if thers more than 1 rowcount, then "who did you mean.."
if($sql_findUser->rowCount() == 1){
$get = $sql_findUser->fetch();
if($get["full"] == 1){
echo "SUCCESS, ONE FOUND FULL MATCH"
}else{
echo "Did you mean ...?"
}
}elseif($sql_findUser->rowCount()>1){
Who did you mean?
}
How can i fix this?
At the end of your SQL statement, it looks like you're including everyone with a first name starting with "Meg", which is why Meg Meg and Megan Fox both match. Try shortening your WHERE clause to:
WHERE users_friends.bID='1' AND users_friends.type = 'friend' AND users_friends.accepted = '1' AND (
(users.firstname = 'Meg' AND users.lastname='Meg') OR
(users.firstname LIKE 'Meg%' AND users.lastname LIKE 'Meg%'))
It will be a tad slower, but you could split the logic in two queries, one that uses exact matching (= instead of like), and if that one does not yield anything, then run the current query.
However, that will make problems, when the user enters Megan Fox, and really meant Megan Foxxy.
As I understand it, this is your problem when using "Meg Meg"...
- Your code identifies an exact match with "Meg Meg"
- Your code also identifies a partial match with "Megan Fox"
- You don't want the partial matches to be included if you get an exact match
The issue here is that no single record in the return set 'knows' about the other records.
To know that you do Not want to include the partial match, you must have already completed a full check for the exact match.
There appear to be two ways of doing that to me...
1) Sequential but seperate queries...
Have your client run a query for exact matches.
If no records are returned, run a query for partial matches on first AND last name.
If no records are returned, run a query for partial matches on first OR last name.
etc, etc.
2. Run one query where you specify the type of match
Add a field to your query that is something like this...
CASE WHEN (users.firstname = 'Meg' AND users.lastname='Meg') THEN 1
WHEN (users.firstname LIKE 'Meg%' AND users.lastname LIKE 'Meg%') THEN 2
WHEN (users.firstname LIKE 'Meg%' OR users.lastname LIKE 'Meg%') THEN 3
END AS 'match_type'
Then also add it to the ORDER BY clause, to make full matches at the top, etc, etc.
Your client can then see how many of each match type were generated and choose to discard/ignore the matches that are not relevant.