Multiple group queries using sum & count - ruby-on-rails

So for the given model
# == Schema Information
#
# Table name: faction_armories
#
# id :bigint not null, primary key
# action :string
# qty :integer
# tid :string
# timestamp :datetime
# user_name :string
# created_at :datetime not null
# updated_at :datetime not null
# faction_id :integer
# item_id :integer
# user_id :integer
#
I'm attempting to group user_name, action, and item_id, and add up all the qty's from each entry. For example
action="used","user_name"="me","qty"=5,"item_id"=4
action="used","user_name"="me","qty"=10,"item_id"=4
Should return a hash that identifies that me - used item 4 - 15 times.
Currently, I'm running two queries but still, the data is not coming through correctly, any input would be greatly appreciated
#news = FactionArmory.where(faction_id: current_user.faction_id).order(user_name: :asc).where("timestamp >= ?", params["report_start"]).where("timestamp <= ?", params["report_end"]).includes([:item]).group("user_name", "action", "item_id").count
#qty = FactionArmory.where(faction_id: current_user.faction_id).order(user_name: :asc).where("timestamp >= ?", params["report_start"]).where("timestamp <= ?", params["report_end"]).includes([:item]).group("user_name", "action", "item_id").sum(:qty)

You can select or pluck to get multiple calculations in one query
FactionArmory
.group("user_name", "action", "item_id")
.pluck("user_name", "action", "item_id", "count(distinct faction_armories.id) as count", "sum(qty) as qty_sum")

Related

Index jsonb column keys using GIN and pg_trgm, for ILIKE queries in Rails

I have a table "Leads" with the following structure :
# == Schema Information
#
# Table name: leads
#
# id :integer not null, primary key
# data :jsonb not null
# state :string
# priority :string
# lead_no :string
# user_id :integer
# location_place_id :string
# uuid :string
# agent_id :integer
# location_coordinates :geography point, 4326
# location :jsonb not null
# user_details :jsonb not null
# inventory_id :integer
# source_details :jsonb not null
# connect_details :jsonb not null
# referral_details :jsonb not null
# process_details :jsonb not null
# tags :jsonb not null
# created_at :datetime
# updated_at :datetime
# name :string
The user_details jsonb column stores data in the form- {name : "John Doe", country : "IN", phone_no : "123456789"}. I want to query my database columns using ILIKE for the name key as :
Lead.where("user_details->>name ILIKE ?","john%")
To achieve this, I created a migration as shown:
class AddIndexUserNameOnLeads < ActiveRecord::Migration[5.2]
def up
execute("CREATE INDEX leads_user_details_name_idx ON leads USING gin((user_details->>'name') gin_trgm_ops)")
end
def down
execute("DROP INDEX leads_user_details_name_idx")
end
end
This creates the necessary index. I have already enabled the pg_trgm extension in a previous migration. My structure.sql looks like :
Also, the corresponding schema.rb adds the following line for leads table -
t.index "((user_details ->> 'name'::text)) gin_trgm_ops", name: "leads_user_details_name_idx", using: :gin
However, when I try to query my database, it does a sequential scan.
On the other hand,if I create a gin index for the entire user_details column and then query using "#> {name: "john"}.to_json" it uses index for scan
My Rails version is 5.2.0 and PostgreSQL version is 12.5. How can I use ILIKE queries for this use case? Where am I going wrong? I'll be happy to provide more details if necessary.
An alternative is to tell your index to already sort the values using either upper or lower case, so that you can simply use LIKE in your queries.
CREATE INDEX leads_user_details_name_idx ON leads
USING gin(lower(user_details->>'name') gin_trgm_ops);
When querying this jsonb key you have to use the same function. Doing so the query planer will find your partial index:
SELECT * FROM leads
WHERE lower(user_details->>'name') ~~ '%doe%';
Demo: db<>fiddle
You table is probably just too small for an index scan to seem worthwhile. It looks like it only has 269 rows in it. You can set enable_seqscan=off to see if it uses the index then. Or you can just add a realistic number of rows to the table (and then VACUUM ANALYZE it)

Getting most liked news from the query

There are two models:
# == Schema Information
#
# Table name: news
#
# id :integer not null, primary key
# title :string not null
# content :text not null
# scope :string not null
# created_at :datetime not null
# updated_at :datetime not null
# person_id :integer not null
# == Schema Information
#
# Table name: likes
#
# id :integer not null, primary key
# like :boolean
# person_id :integer not null
# news_id :integer not null
Relation
news has many likes
like belongs to news
I want to get most liked news from query. Query should substract count of likes equal true from likes equal false. The highest number is most liked news.
What I tried:
#count_true_likes = Like.where('likes.like = ?', true).group(:news_id).count
#count_false_likes = Like.where('likes.like = ?', false).group(:news_id).count
Result is Hash with id and counted likes. I don't have idea how to subtract in query positive likes from negative likes, and do it for every news.
This kind of querying becomes prohibitively slow very quickly, as your dataset grows. A common workaround is to cache number of upvotes and downvotes. For example
# Table name: news
#
# id :integer not null, primary key
# title :string not null
# content :text not null
# scope :string not null
# created_at :datetime not null
# updated_at :datetime not null
# person_id :integer not null
#
# upvotes_count :integer not null
# downvotes_count :integer not null
# vote_result :integer not null
Where vote_result is a cached upvotes_count - downvotes_count.
Then simply do
News.order(vote_result: :desc).limit(10) # top 10 articles
The downside is, of course, that you need to maintain those cached counters (increase/decrease corresponding ones when you register a vote).
I resolved my problem:
#most_liked_news_id = Like.group(:news_id)
.select('news_id, SUM(case when likes.like then 1 else -1 end) as max_positive')
.order('max_positive desc').map(&:news_id).first
#most_liked_news = News.find(#most_liked_news_id)

Validation date in specific range

Hi I have model bought_details and entry_types.
# Table name: bought_details
#
# id :integer not null, primary key
# bought_data :date not null
# end_on :date not null
# entry_type_id :integer
# person_id :integer
# start_on :date
# cost :decimal(5, 2) not null
# Table name: entry_types
#
# id :integer not null, primary key
# kind :string not null
# kind_details :string not null
# description :text
# price :decimal(5, 2) not null
What I want to have:
If I register new purchase e.g. person with id 1 buy pass(entry_type id: 1) for 1 month(start_on: 20.04.2016 end_on: 20.05.2016).
I want create validation to protect buy new pass(entry_type kind == "Pass", in a database I have few entry type object with kind equals "Pass"), when start_on or end_on is between 20.04.2016 and 20.05.2016r. with simply alert "You already have valid pass".
How can I resolve my problem?
Thanks in advance.
Have you try validates_timeliness gem?
https://github.com/adzap/validates_timeliness
It can simply validate date ranges.
validates_date :start_on, after: lambda { (DateTime.now - 1.month }, before: lambda { (DateTime.now + 1.month }

Pluck and ids give array of non unique elements

In console:
Course.ids.count
=> 1766
Course.pluck(:id).count
=> 1766
Course.ids.uniq.count
=> 1529
Course.count
=> 1529
It's normal?
small comment - model Course uses ancestry (gem).
UPD1:
Generated sql:
Learn::Course.ids.count
(5.4ms) SELECT "learn_courses"."id" FROM "learn_courses" LEFT OUTER JOIN "learn_course_translations" ON "learn_course_translations"."learn_course_id" = "learn_courses"."id"
=> 1766
Learn::Course.count
(1.5ms) SELECT COUNT(*) FROM "learn_courses"
=> 1529
hmm...
UPD2:
Schema Information
#
# Table name: learn_courses
#
# id :integer not null, primary key
# name :string(255)
# position :integer
# created_at :datetime
# updated_at :datetime
# ancestry :string(255)
# course_type :string(255)
# article :string(255)
# item_style :integer
# hidden :boolean
# score :integer default(0)
# next_id :integer
# first :boolean
You should be able to work around this with
Learn::Course.pluck('distinct learn_courses.id')
The problem is that LEFT OUTER JOIN with learn_course_translations, which must have multiple rows per Learn::Course, resulting in the same learn_courses.id appearing several times. pluck doesn't care about distinctness, so it just passes them all back.
Maybe ancestry adds default_scope to your model. Try to check it with
Learn::Course.unscoped.ids.count

Rails filter on date returning no results

I'm trying to filter records that were assigned on particular day (today) using this query:
assignments = p.assignments.where("assigned_date = ?", Date.today)
Even though I know these records exist, I always get a blank result back.
I've also tried ...where(:assigned_date => Date.today) with no luck.
The database schema is:
# == Schema Information
#
# Table name: assignments
#
# id :integer not null, primary key
# name :string(255)
# rep :integer
# set :integer
# instructions :text
# created_at :datetime not null
# updated_at :datetime not null
# player_id :integer
# actual_percentage :float
# predicted_percentage :float
# assigned_date :date
#
And in the console, when I type
p.assignments.first.assigned_date == Date.today
it returns true.
Any idea what I'm doing wrong?
DateTime holds a date and a time, so you're looking for records that have a precise value, not just the same day.
assignments = p.assignments.where('assigned_date BETWEEN ? AND ?', DateTime.now.beginning_of_day, DateTime.now.end_of_day).all
should return what's expected
P.S.
all credits to Tadman

Resources