ActiveRecord select by last character of string - ruby-on-rails

I tried
members = Member.select(:member_id[6] == "1")
and
members = Member.select(member_id[6]: == "1")
and
members = Member.select("member_id[6]" == "1")
I am trying to get only those members where their member_id final character is a 1.

To get what you want, efficiently, you'll need to leverage Postgres' string functions in a literal SQL condition. Your existing code is also confusing select, the Active Record method that alters what the query uses in its SELECT clause, with select, the Ruby Enumerable method, which allows you to filter a collection. Since your filtering is best performed in the database, you'll want to instead use where.
So, something like:
Member.where("RIGHT(member_id::varchar, 1) = '1'")

Related

Ruby - Return record with longest string in column

I just need the record with the longest string in the product_description column.
A record could have nil in the product_description column
Which is why this won't work:
Product.where(parent_product_id: 22033).pluck(:product_description).max_by(&:length)
Then I try SQL and get:
ActiveRecord::UnknownAttributeReference (Query method called with non-attribute argument(s): "max(length(product_description))")
From this query:
Product.where(parent_product_id: 22033).pluck("max(length(product_description))")
This returns the same:
Product.where(parent_product_id: 22033).order("MAX(CHAR_LENGTH(product_description)) desc").limit(1)
But product_description is definitely a column on the Products table.. that's not the issue
You can use the length function of your RDBMS to calculate the length, then order by it.
Ties
There might be many products with the same description length. In order to have consistent results, you will need a tie breaker as otherwise the order within the products with same description length is not defined. You could add an order by id clause.
NULL
Be aware that
select length(null)
will return null and not 0.
null might be sorted before actual values or after (depending on your RDBMS and its config).
If you always need a numeric value you can do
select length(coalesce(null, ''))
which will return 0.
coalesce returns the first non-null argument and therefore ensures that we always pass at least an empty string to length.
You can also use the null last option for the order clause.
You can also exclude records with a null value for the description:
products = Product.where.not(product_description: nil)
to avoid dealing with null values altogether.
If the column is not nullable, then there is no problem either.
Now if you just use this:
products = Product.all # or whatever conditions you need
products
.order("length(coalesce(product_description, '')) desc")
.order(id: :asc)
.first
then Rails might complain (depends on the Version you are using) for security reasons with something like ActiveRecord::UnknownAttributeReference: Dangerous query method
which means you need to wrap the whole thing in Arel.sql
products = Product.all # or whatever conditions you need
products
.order(Arel.sql("length(coalesce(product_description, '')) desc"))
.order(id: :asc)
.first
Index
If you have many records, you might want to add an index on the column length. See https://sqlfordevs.com/function-based-index for how to create a function based index.
You can order by length and take first like this
Product
.where(parent_product_id: 22033)
.where.not(product_description: nil)
.order("LENGTH(product_description) DESC")
.first
LENGTH is RDBMS function and depends on specific system and may differ therefore

Can I force the execution of an active record query chain?

I have an edge case where I want to use .first only after my SQL query has been executed.
My case is the next one:
User.select("sum((type = 'foo')::int) as foo_count",
"sum((type = 'bar')::int) as bar_count")
.first
.yield_self { |r| r.bar_count / r.foo_count.to_f }
However, this would throw an SQL error saying that I should include my user_id in the GROUP BY clause. I've already found a hacky solution using to_a, but I really wonder if there is a proper way to force execution before my call to .first.
The error is because first uses an order by statement to order by id.
"Find the first record (or first N records if a parameter is supplied). If no order is defined it will order by primary key."
Instead try take
"Gives a record (or N records if a parameter is supplied) without any implied order. The order will depend on the database implementation. If an order is supplied it will be respected."
So
User.select("sum((type = 'foo')::int) as foo_count",
"sum((type = 'bar')::int) as bar_count")
.take
.yield_self { |r| r.bar_count / r.foo_count.to_f }
should work appropriately however as stated the order is indeterminate.
You may want to use pluck which retrieves only the data instead of select which just alters which fields get loaded into models:
User.pluck(
"sum((type = 'foo')::int) as foo_count",
"sum((type = 'bar')::int) as bar_count"
).map do |foo_count, bar_count|
bar_count / foo_count.to_f
end
You can probably do the division in the query as well if necessary.

Can i write this Query in ActiveRecord

for a data analysis i need both results into one set.
a.follower_trackings.pluck(:date, :new_followers, :deleted_followers)
a.data_trackings.pluck(:date, :followed_by_count)
instead of ugly-merging an array (they can have different starting dates and i obv. need only those values where the date exists in both arrays) i thought about mysql
SELECT
followers.new_followers,
followers.deleted_followers,
trackings.date,
trackings.followed_by_count
FROM
instagram_user_follower_trackings AS followers,
instagram_data_trackings AS trackings
WHERE
followers.date = trackings.date
AND
followers.user_id=5
AND
trackings.user_id=5
ORDER
BY trackings.date DESC
This is Working fine, but i wonder if i can write the same with ActiveRecord?
You can do the following which should render the same query as your raw SQL, but it's also quite ugly...:
a.follower_trackings.
merge(a.data_trackings).
from("instagram_user_follower_trackings, instagram_data_trackings").
where("instagram_user_follower_trackings.date = instagram_data_trackings.date").
order(:date => :desc).
pluck("instagram_data_trackings.date",
:new_followers, :deleted_followers, :followed_by_count)
There are a few tricks turned out useful while playing with the scopes: the merge trick adds the data_trackings.user_id = a.id condition but it does not join in the data_trackings, that's why the from clause has to be added, which essentially performs the INNER JOIN. The rest is pretty straightforward and leverages the fact that order and pluck clauses do not need the table name to be specified if the columns are either unique among the tables, or are specified in the SELECT (pluck).
Well, when looking again, I would probably rather define a scope for retrieving the data for a given user (a record) that would essentially use the raw SQL you have in your question. I might also define a helper instance method that would call the scope with self, something like:
def Model
scope :tracking_info, ->(user) { ... }
def tracking_info
Model.tracking_info(self)
end
end
Then one can use simply:
a = Model.find(1)
a.tracking_info
# => [[...], [...]]

Activerecord specifications with 2 different models

I need to find a way to display all Vacancies from my Vacancy model except the ones that a user already applied for.
I keep the IDs of the vacancies a certain user applied for in a seperate model AppliedVacancies.
I was thinking something line the lines of:
#applied = AppliedVacancies.where(employee_id: current_employee)
#appliedvacancies_id = []
#applied.each do |appliedvacancy|
#appliedvacancies_id << appliedvacancy.id
end
#notyetappliedvacancies = Vacancy.where("id != ?", #appliedvacancy_id)
But it does not seem to like getting an array of IDs. How would I go about fixing this?
I get following error:
PG::DatatypeMismatch: ERROR: argument of WHERE must be type boolean, not type record
LINE 1: SELECT "vacancies".* FROM "vacancies" WHERE (id != 13,14)
^
: SELECT "vacancies".* FROM "vacancies" WHERE (id != 13,14)
This is purely an SQL problem.
You cannot use != to compare a value to a set of values. You need to use the IN operator.
#notyetappliedvacancies = Vacancy.where("id NOT IN (?)", #appliedvacancy_id)
As an aside, you can drastically improve the code you've written so far. You are needlessly instantiating complete ActiveRecord models for every record found in your applied_vacancies table, when all you need are the IDs.
A first pass at improvement would be to use pluck to skip the entire process and go straight to the list of IDs:
ids = AppliedVacancies.where(employee_id: current_employee).pluck(:id)
#notyetappliedvacancies = Vacancy.where("id NOT IN (?)", ids)
Next, you can go a step further and eliminate the first query all together (or rather, combine it with the last query as a sub-query) by leaving it as an AREL projection which can be subbed into the second query directly:
ids = AppliedVacancies.select(:id).where(employee_id: current_employee)
#notyetappliedvacancies = Vacancy.where("id NOT IN (?)",App)
This will generate a single query:
select * from vacancies where id not in (select id from applied_vacancies where employee_id = <value>)
Answer like #meagar, but Rails 4 way:
#notyetappliedvacancies = Vacancy.where.not(id: #appliedvacancy_id)

tricky union query using ruby on rails/active record

I have
a = Profile.last
a.mailbox.inbox
a.mailbox.sentbox
active_conversations = [IDS OF ACTIVE CONVERSATIONS]
a.mailbox.inbox & active_conversations
returns part of what I need
I want
(a.mailbox.inbox & active_conversations) AND a.mailbox.sentbox
but I need it as SQL, so that I can order it efficiently. I want to order it by ('updated_at')
I have tried joins and other things but they don't work. The classes of (a.mailbox.inboxa and the sentbox are
ActiveRecord::Relation::ActiveRecord_Relation_Conversation
but
(a.mailbox.inbox & active_conversations)
is an array
edit
Something as simple as a.mailbox.inbox JOINS SOMEHOW a.mailbox.sentbox I should be able to work with, but I also can't seem to figure out.
Instead of doing
(a.mailbox.inbox & active_conversations)
you should be able to do
a.mailbox.inbux.where('conversations.id IN (?)', active_conversations)
I believe the Conversation class (and its underlying conversations table) should be right according to the mailboxer code.
However this gives you an ActiveRelation object instead of an array. You can transform this to pure SQL using to_sql. So I think something like this should work:
# get the SQL of both statements
inbox_sql = a.mailbox.inbux.where('conversations.id IN (?)', active_conversations).to_sql
sentbox_sql = a.mailbox.sentbox.to_sql
# use both statements in a UNION SQL statement issued on the Conversation class
Conversation.from("#{inbox_sql} UNION #{sentbox_sql} ORDER BY id AS conversations")

Resources