Postgree double group by repeating attribute - ruby-on-rails

I have table with some columns: id, user_id, message_id, message_type; for example:
id: 1, user_id: 1, message_id: 4, message_type: 'Warning'
id: 2, user_id: 1, message_id: 5, message_type: 'Warning'
id: 3, user_id: 1, message_id: 6, message_type: 'Warning'
id: 4, user_id: 2, message_id: 4, message_type: 'Error'
id: 5, user_id: 2, message_id: 1, message_type: 'Exception'
id: 6, user_id: 1, message_id: 2, message_type: 'Exception'
id: 7, user_id: 1, message_id: 3, message_type: 'Exception'
id: 8, user_id: 2, message_id: 4, message_type: 'Exception'
I want to get grouping result like news in social networks. On columns user_id and message_type, while message_type repeating. And need LIMIT 20 ORDER BY id DESC.
Example:
id: 8, user_id: 2, message_id: 4, message_type: 'Exception'
id: {6,7} user_id: 1, message_id: {2,3}, message_type: 'Exception'
id: 5, user_id: 2, message_id: 1, message_type: 'Exception'
id: 4, user_id: 2, message_id: 4, message_type: 'Error'
id: {1, 2, 3}, user_id: 1, message_id: {4, 5, 6}, message_type: 'Warning'
How to do it with best performance?

I found only 1 way:
With window function lead() find a moment when was changed dict (user, message type)
With window function sum() set sequnce number for each new dict
Group by sequence and select what you need:
Checking:
create table test (
id serial primary key,
user_id integer,
message_id integer,
message_type varchar
);
insert into test (user_id, message_id, message_type)
values
(1, 4, 'Warning'),
(1, 5, 'Warning'),
(1, 6, 'Warning'),
(2, 4, 'Error'),
(2, 1, 'Exception'),
(1, 2, 'Exception'),
(1, 3, 'Exception'),
(2, 4, 'Exception')
;
select
array_agg(grouped.id) as record_ids,
grouped.user_id,
array_agg(grouped.message_id) as message_ids,
grouped.message_type
from (
select changed.*,
sum(changed.changed) over (order by changed.id desc) as group_n
from (
select tt.*,
case when lag((user_id, message_type)) over (order by tt.id desc) is distinct from (user_id, message_type) then 1 else 0 end as changed
from test tt
) changed
order by id desc
) grouped
group by grouped.group_n, grouped.user_id, grouped.message_type
order by grouped.group_n
;
Result:
record_ids | user_id | message_ids | message_type
------------+---------+-------------+--------------
{8} | 2 | {4} | Exception
{7,6} | 1 | {3,2} | Exception
{5} | 2 | {1} | Exception
{4} | 2 | {4} | Error
{3,2,1} | 1 | {6,5,4} | Warning
(5 rows)

The array_agg function should do the trick:
SELECT user_id,
message_type,
ARRAY_AGG (DISTINCT id),
ARRAY_AGG (DISTINCT message_id)
FROM mytable
GROUP BY user_id, message_type

Related

Getting error on joins when trying to get its key.

Here's my stat model.
Stat(id: integer, points: float, user_id: integer, match_id: integer, team_id: integer)
For team model
Team(id: integer, name: string)
I'm getting error on teams.name part here's the error.
#<ActiveRecord::StatementInvalid: Mysql2::Error: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'db-name.teams.name' which is not functionally dependent on columns in GROUP BY clause;
Sample stat:
{id: 1, points: 2, user_id: 1, match_id: 1, team_id: 1}
{id: 2, points: 3, user_id: 3, match_id: 1, team_id: 2}
{id: 3, points: 4, user_id: 1, match_id: 2, team_id: 1}
My current code:
sample = Stat
.joins(:user)
.joins(:team)
.select('teams.name as team_name, users.id as user_id, match_id, SUM(points) as points')
.where(user_id: params[:user_id])
.group(:match_id)
.where.not(match_id: nil)
.order("match_id DESC")
.limit(10)

How to get the sum of this ActiveRecord::Associations::CollectionProxy?

[#<ActiveRecord::Associations::CollectionProxy [#<InvoiceServiceType id: 1, value_charged: 50.0, invoice_id: 4, service_type_id: 1>, #<InvoiceServiceType id: 2, value_charged: 50.4, invoice_id: 4, service_type_id: 2>]>, #<ActiveRecord::Associations::CollectionProxy [#<InvoiceServiceType id: 8, value_charged: 70.0, invoice_id: 1, service_type_id: 2>, #<InvoiceServiceType id: 9, value_charged: 50.0, invoice_id: 1, service_type_id: 6>]>]
I want to sum all value_charged .
im try map(&:value_charged).sum
collect..
and nothing :(
Thank you
Lets say that you have a #object with a has_many relationship details, and details have a field call :value_charged, you could try sum all the :value_charged of #object.details with:
#object.details.sum(:value_charged)

Rails save serialized object fails?

See the following output:
1.9.3p194 :001 > player = Player.randomize_for_market
=> #<Player id: nil, name: "Gale Bridges", age: 19, energy: 100, attack: 6, defense: 4, stamina: 5, goal_keeping: 3, power: 4, accuracy: 5, speed: 5, short_pass: 5, ball_controll: 4, long_pass: 6, regain_ball: 5, contract_id: nil, created_at: nil, updated_at: nil>
1.9.3p194 :002 > player.save!
(0.2ms) BEGIN
SQL (20.5ms) INSERT INTO "players" ("accuracy", "age", "attack", "ball_controll", "contract_id", "created_at", "defense", "energy", "goal_keeping", "long_pass", "name", "power", "regain_ball", "short_pass", "speed", "stamina", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING "id" [["accuracy", 5], ["age", 19], ["attack", 6], ["ball_controll", 4], ["contract_id", nil], ["created_at", Fri, 29 Jun 2012 04:02:34 UTC +00:00], ["defense", 4], ["energy", 100], ["goal_keeping", 3], ["long_pass", 6], ["name", "Gale Bridges"], ["power", 4], ["regain_ball", 5], ["short_pass", 5], ["speed", 5], ["stamina", 5], ["updated_at", Fri, 29 Jun 2012 04:02:34 UTC +00:00]]
(16.6ms) COMMIT
=> true
1.9.3p194 :003 > YAML::load(YAML::dump(Player.randomize_for_market)).save!
(0.2ms) BEGIN
(0.2ms) COMMIT
=> true
Why this happens and how can I avoid it?
There is no ((before|after)+(save|create|commit)) on the model. I'm using rails 3.2.
Table "public.players"
Column | Type | Modifiers
--------------+-----------------------------+------------------------------------------------------
id | integer | not null default nextval('players_id_seq'::regclass)
name | character varying(255) | not null
age | integer | not null
energy | integer | not null
attack | integer | not null
defense | integer | not null
stamina | integer | not null
goal_keeping | integer | not null
power | integer | not null
accuracy | integer | not null
speed | integer | not null
short_pass | integer | not null
ball_controll | integer | not null
long_pass | integer | not null
regain_ball | integer | not null
contract_id | integer |
created_at | timestamp without time zone | not null
updated_at | timestamp without time zone | not null
Indexes:
"players_pkey" PRIMARY KEY, btree (id)
Edit: Answering "Why do you expect YAML::load(YAML::dump(Player.randomize_for_market)).save! to do anything?"
Because it serializes a object and recovers it?
example:
1.9.3p194 :006 > p = Player.randomize_for_market
=> #<Player id: nil, name: "Vincenzo Allen", age: 23, energy: 100, attack: 2, defense: 8, stamina: 6, goal_keeping: 3, power: 5, accuracy: 6, speed: 5, short_pass: 6, ball_controll: 5, long_pass: 6, regain_ball: 5, contract_id: nil, created_at: nil, updated_at: nil>
1.9.3p194 :007 > p
=> #<Player id: nil, name: "Vincenzo Allen", age: 23, energy: 100, attack: 2, defense: 8, stamina: 6, goal_keeping: 3, power: 5, accuracy: 6, speed: 5, short_pass: 6, ball_controll: 5, long_pass: 6, regain_ball: 5, contract_id: nil, created_at: nil, updated_at: nil>
1.9.3p194 :008 > YAML::load(YAML::dump(p))
=> #<Player id: nil, name: "Vincenzo Allen", age: 23, energy: 100, attack: 2, defense: 8, stamina: 6, goal_keeping: 3, power: 5, accuracy: 6, speed: 5, short_pass: 6, ball_controll: 5, long_pass: 6, regain_ball: 5, contract_id: nil, created_at: nil, updated_at: nil>
Note that the return of p is the same of the return from YAML::load
This may help to answer your question:
:001 > article = Article.new
#<Article:0x102d16b10> { ... }
:002 > article.persisted?
false
:003 > dumped = YAML::dump(article)
"--- !ruby/object:Article ... "
:004 > loaded = YAML::load(dumped)
#<Article:0x102cf5500> { ... }
:005 > loaded.persisted?
true
Looking into the Rails source code for ActiveRecord::Base#persisted?:
def persisted?
!(new_record? || destroyed?)
end
And for ActiveRecord::Base#new_record?:
def new_record?
#new_record
end
The #new_record instance variable is not saved when you dump the object to Yaml, and therefore it's nil when you load the object from Yaml. So ActiveRecord thinks it's already been persisted to the database and doesn't attempt to save it.
Brandan's answer is very relevant, the object de-serialized from YAML thinks it's already been persisted. Assuming #loaded_obj is the object you loaded from YAML (the object you want to save), try #loaded_obj.dup.save

Duplicate Records created from Association

I am using Mongoid, Rails and Fabrications and at a total loss with how this is happening. Any thoughts very appreciated, but I know this pretty complicated. I just want to fabricate a user and have only four joined groups, but I keep getting eight loaded.
Here is the relevant section of my code
#user1 = Fabricate.build(:registered)
#user1.joined_groups << [common_group,
cali_group,
ca46,
Fabricate(:polco_group, {:name => "Gang of 13", :type => :custom})]
When I run #user1.joined_groups.size I get 4, but when I do #user1.joined_groups.map(&:name), I get 8 records:
#<PolcoGroup _id: 1 ... member_ids: [], follower_ids: []>
#<PolcoGroup _id: 1 ... member_ids: [], follower_ids: []>
#<PolcoGroup _id: 1 ... member_ids: [], follower_ids: []>
#<PolcoGroup _id: 1 ... member_ids: [], follower_ids: []>
#<PolcoGroup _id: 1 ... member_ids: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], follower_ids: [1, 1]>
#<PolcoGroup _id: 1 ... member_ids: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], follower_ids: [1, 1]>
#<PolcoGroup _id: 1 ... member_ids: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], follower_ids: [1, 1]>
#<PolcoGroup _id: 1 ... member_ids: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], follower_ids: [1, 1]>
(where i have replaced all BSON::ObjectId('4eab3ca5f11aac2701000009') statements with ones and removed a lot of the middle code.
The full set of code is available here: https://gist.github.com/1323984
Most bizzarre simply calling map might be causing the problem.
puts "just created user with these groups:"
puts #user1.joined_groups.map(&:name)
puts "then secondly"
puts #user1.joined_groups.map(&:name)
Generates this (!):
just created user with these groups:
Dan Cole
CA
CA46
Gang of 13
then secondly
Dan Cole
CA
CA46
Gang of 13
Dan Cole
CA
CA46
Gang of 13
Thanks for any insight! After repeated attempts, I can't figure out a way in terminal to duplicate this, so I am suspecting the Fabrication gem. (Update: nope, I get this error with standard mongoid objects, so I am totally blaming mongoid.)
Tim
I think the problem might simply be that you are not pushing the groups onto the user correctly. Try using concat or separately shoveling them.
#user1.joined_groups.concat([common_group,
cali_group,
ca46,
Fabricate(:polco_group, {:name => "Gang of 13", :type => :custom})])

Find Next and previous records ordered by position column rails

>> c = Course.find(3).course_steps.order(:position)
=> [#<CourseStep id: 9, step_id: 4, course_id: 3, position: 1, created_at: "2011-03-08 20:57:44", updated_at: "2011-03-08 20:57:44">, #<CourseStep id: 10, step_id: 5, course_id: 3, position: 2, created_at: "2011-03-08 20:57:45", updated_at: "2011-03-08 20:57:45">, #<CourseStep id: 8, step_id: 2, course_id: 3, position: 3, created_at: "2011-03-08 20:57:42", updated_at: "2011-03-08 20:57:42">]
I need to find a course_step that is after id 9 (which happens to be course_step with id 10) (if it is exists)
I also need to find the previous (if it is exists)
I know I could manually do it be looping through the results, but I would rather do it with SQL.
The NEXT sql query would be:
SELECT * FROM course_steps WHERE position >=POSITION_OF_STEP ORDER BY position LIMIT 1 OFFSET 1
The PREVIOUS sql query would be:
SELECT * FROM course_steps WHERE position <= POSITION_OF_STEP ORDER BY position DESC LIMIT 1 OFFSET 1
I think I got it!
class CourseStep < ActiveRecord::Base
belongs_to :step
belongs_to :course
validates_uniqueness_of :step_id, :scope => :course_id
def next_step()
Course.find(self.course.id).course_steps.order(:position).where("position >= ?", self.position).limit(1).offset(1).first
end
def previous_step()
Course.find(self.course.id).course_steps.order("position DESC").where("position <= ?", self.position).limit(1).offset(1).first
end
end

Resources