I'm having trouble converting a SQL query to ActiveRecord and I'm hoping you can help.
select tbl2.id
from Table1 tbl1
JOIN Table2 tbl2 ON tbl1.id = tbl2.some_column_id
where tbl1.id in (1, 2, 3, 4, 5)
and tbl2.id not in (10, 13, 22, 44, 66)
Rails Models exists and the relationship is like this:
Table2:
has_many :table1
Assumed you setup your classes with appropriate table names (table1 and table2 are not good names for rails models, btw).
Then
Table2
.select(:id).joins(:table1)
.where(table1: { id: [1, 2, 3, 4, 5] }
.where.not(id: [10, 13, 22, 44, 66])
Related
I have database records arranged in an ltree structure (Postgres ltree extension).
I want to filter these items down to the outer most ancestors of the current selection.
Test cases:
[11, 111, 1111, 2, 22, 222, 2221, 2222] => [11, 2];
[1, 11, 111, 1111, 1112, 2, 22, 222, 2221, 2222, 3, 4, 5] => [1, 2, 3, 4, 5];
[1111, 1112, 2221, 2222] => [1111, 1112, 2221, 2222];
1
|_1.1
| |_1.1.1
| |_1.1.1.1
| |_1.1.1.2
2
|_2.2
| |_2.2.2
| |_2.2.2.1
| |_2.2.2.2
3
|
4
|
5
I have implemented this in Ruby like so.
def fetch_outer_most_items(identifiers)
ordered_items = Item.where(id: identifiers).order("path DESC")
items_array = ordered_items.to_a
outer_most_item_ids = []
while(items_array.size > 0) do
item = items_array.pop
outer_most_item_ids.push(item.id)
duplicate_ids = ordered_items.where("items.path <# '#{item.path}'").pluck(:id)
if duplicate_ids.any?
items_array = items_array.select { |i| !duplicate_ids.include?(i.id) }
end
end
return ordered_items.where(id: outer_most_item_ids)
end
I have eliminated descendants as duplicates via recursion. I'm pretty sure there is an SQL way of doing this which will be the preferred solution as this one triggers n+1 queries. Ideally I would add this function as a named scope for the Item model.
Any pointers please?
I have a specific order in which I want to show a number of my blog posts, but I'd also like to display regular blog posts after those selected ones.
So let's say I have posts [1, 7, 35, 36, 48] which I want to go first:
#selected_posts = Post.find([1, 7, 35, 36, 48])
But now I need to query for every other post, excluding those above:
#other_posts = Post.where.not(id: [1, 7, 35, 36, 48])
And now I need to combine those to maintain that order:
Post.find([1, 7, 35, 36, 48]) + Post.where.not(id: [1, 7, 35, 36, 48])
I'm using Postgres. Is it possible to do this in one query?
You can do order by case...
priority_ids = [1, 7, 35, 36, 48]
#all_posts = Post.all.order("CASE WHEN id IN (#{priority_ids.join(',')}) THEN 1 ELSE 2 END")
(thanks to #DeepakMahakale for correction)
I want to be able to query a user's games where their user_id is in the games' move's white_user_ids or black_user_ids. I also want to be able to query a game's users where its move's white_user_ids or black_user_ids correspond to users.
EDIT:
Before I start explaining my question in more detail, I came up with a workaround:
class User < ActiveRecord::Base
def games
Game.where( [ "moves -> 'black_user_ids' #> '?' OR moves -> 'white_user_ids' #> '?'", self.id, self.id ] )
end
end
class Game < ActiveRecord::Base
serialize :moves, HashSerializer
store_accessor :moves, :white_user_ids, :black_user_ids
def users
User.find((self.white_user_ids + self.black_user_ids).uniq)
end
end
Is there anything wrong with creating custom methods with the names I want, instead of writing out has_many ...? If that is a hack and bad, then please keep reading and feel free to answer.
Initial question:
Here is the most basic version of the schema for this question:
User
id
Game
id
moves - jsonb
white_user_ids - integer array
black_user_ids - integer array
I have created an expression index for both user_ids:
execute <<-SQL
CREATE INDEX moves_white_user_ids_on_games ON games USING GIN ((moves->'white_user_ids'));
CREATE INDEX moves_black_user_ids_on_games ON games USING GIN ((moves->'black_user_ids'));
SQL
I don't know how to set up the relationship between users and games. There is clearly some kind of has_many relationship, or maybe even a has_and_belongs_to_many relationship. But I am storing all the data that I would need to make the association in the games table, moves jsonb column.
Examples:
<User id: 1>
<User id: 5>
<User id: 6>
<User id: 11>
<User id: 17>
<User id: 20>
<User id: 23>
<User id: 35>
<User id: 76>
<User id: 89>
<User id: 93>
<Game id: 1, moves: {"black_user_ids"=>[88],
"white_user_ids"=>[23]}>
<Game id: 2, moves: {"black_user_ids"=>[6, 1, 11, 76, 17, 23],
"white_user_ids"=>[93, 89, 1, 35, 20, 5, 6]}>
<Game id: 3, moves: {"black_user_ids"=>[76, 68, 20, 96, 19, 3],
"white_user_ids"=>[82, 48, 29, 37, 20, 74]}>
<Game id: 4, moves: {"black_user_ids"=>[82],
"white_user_ids"=>[74, 16]}>
<Game id: 5, moves: {"black_user_ids"=>[22, 41, 25, 78, 50],
"white_user_ids"=>[24, 10, 99, 26, 1, 4]}>
User associations/scopes:
User.find(1).games would return games [2, 5], any unique game where either black_user_ids or white_user_ids has their user_id.
User.find(1).black_games would return game [2], any game where black_user_ids has their user_id.
User.find(1).white_games would return games [2, 5], any game where white_user_ids has their user_id.
Game associations/scopes:
Game.find(2).users would return users [1, 5, 6, 11, 17, 20, 23, 35, 76, 89, 93], any unique user in its black_user_ids or white_user_ids.
Game.find(3).black_users would return users [3, 19, 20, 68, 76, 96], any user in its black_user_ids.
Game.find(4).white_users would return users [16, 74], any user in its white_user_ids.
You can do this with polymorphic associations.
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
The moves table would then have id and type black or white. You can then associate that to game and query black moves or white moves.
I can't find a simple way to get the ids of pointfields from this relationship
pointtype has_and_belongs_to_many pointfields
pointfield has_and_belongs_to_many pointtypes
I do the following :
#pointtypes = current_project.points.map{|p| p.pointtype}.uniq
#pointtypes = #pointtypes - [nil] # tricky... Is this Railsy ?
#pointfield_ids = #pointtypes.map(&:pointfield_ids)
returns
[[16, 17, 18, 23, 24, 25, 26, 27, 28, 29], [16, 17, 32, 33, 34, 35, 36]]
and thus
#pointfield_ids.uniq!
is not working because there is two sub-arrays...
What I need is to get one single array with the unique ids of the pointfields (or the objects itself)
Let’s say I have this array with shipments ids.
s = Shipment.find(:all, :select => "id")
[#<Shipment id: 1>, #<Shipment id: 2>, #<Shipment id: 3>, #<Shipment id: 4>, #<Shipment id: 5>]
Array of invoices with shipment id's
i = Invoice.find(:all, :select => "id, shipment_id")
[#<Invoice id: 98, shipment_id: 2>, #<Invoice id: 99, shipment_id: 3>]
Invoices belongs to Shipment.
Shipment has one Invoice.
So the invoices table has a column of shipment_id.
To create an invoice, I click on New Invoice, then there is a select menu with Shipments, so I can choose "which shipment am i creating the invoice for". So I only want to display a list of shipments that an invoice hasn't been created for.
So I need an array of Shipments that don't have an Invoice yet. In the example above, the answer would be 1, 4, 5.
a = [2, 4, 6, 8]
b = [1, 2, 3, 4]
a - b | b - a # => [6, 8, 1, 3]
First you would get a list of shipping_id's that appear in invoices:
ids = i.map{|x| x.shipment_id}
Then 'reject' them from your original array:
s.reject{|x| ids.include? x.id}
Note: remember that reject returns a new array, use reject! if you want to change the original array
Use substitute sign
irb(main):001:0> [1, 2, 3, 2, 6, 7] - [2, 1]
=> [3, 6, 7]
Ruby 2.6 is introducing Array.difference:
[1, 1, 2, 2, 3, 3, 4, 5 ].difference([1, 2, 4]) #=> [ 3, 3, 5 ]
So in the case given here:
Shipment.pluck(:id).difference(Invoice.pluck(:shipment_id))
Seems a nice elegant solution to the problem. I've been a keen follower of a - b | b - a, though it can be tricky to recall at times.
This certainly takes care of that.
Pure ruby solution is
(a + b) - (a & b)
([1,2,3,4] + [1,3]) - ([1,2,3,4] & [1,3])
=> [2,4]
Where a + b will produce a union between two arrays
And a & b return intersection
And union - intersection will return difference
The previous answer here from pgquardiario only included a one directional difference. If you want the difference from both arrays (as in they both have a unique item) then try something like the following.
def diff(x,y)
o = x
x = x.reject{|a| if y.include?(a); a end }
y = y.reject{|a| if o.include?(a); a end }
x | y
end
This should do it in one ActiveRecord query
Shipment.where(["id NOT IN (?)", Invoice.select(:shipment_id)]).select(:id)
And it outputs the SQL
SELECT "shipments"."id" FROM "shipments" WHERE (id NOT IN (SELECT "invoices"."shipment_id" FROM "invoices"))
In Rails 4+ you can do the following
Shipment.where.not(id: Invoice.select(:shipment_id).distinct).select(:id)
And it outputs the SQL
SELECT "shipments"."id" FROM "shipments" WHERE ("shipments"."id" NOT IN (SELECT DISTINCT "invoices"."shipment_id" FROM "invoices"))
And instead of select(:id) I recommend the ids method.
Shipment.where.not(id: Invoice.select(:shipment_id).distinct).ids
When dealing with arrays of Strings, it can be useful to keep the differences grouped together.
In which case, we can use Array#zip to group the elements together and then use a block to decide what to do with the grouped elements (Array).
a = ["One", "Two", "Three", "Four"]
b = ["One", "Not Two", "Three", "For" ]
mismatches = []
a.zip(b) do |array|
mismatches << array if array.first != array.last
end
mismatches
# => [
# ["Two", "Not Two"],
# ["Four", "For"]
# ]
s.select{|x| !ids.include? x.id}