ActiveRecord:具有嵌套关联和排序的复杂查询

时间:2017-07-07 13:50:11

标签: sql ruby-on-rails activerecord

Alrigth,所以我不确定这是不是太具体......但我真的不知道如何构建这样的查询,既不是在AR中,也不是在SQL中。所以情况如下:

  • 我有一个User型号。 User has_many Project s。反过来,Project具有以下关联:
  • Project has_one BasicEvent
  • Project had_many AdditionalEvent s
  • BasicEventAdditionalEvent类是基于AR模型类的继承而构建的,Event

现在,目标是:在我看来,我需要:

  • 根据Project
  • 访问所有User个点数
  • 访问Event
  • 中的happened_at属性的所有nil计数
  • User的所有Event排序,按happened_at的计数排序,nil
  • 中的public class Tab3 extends Fragment { private static RecyclerView.Adapter adapter; private RecyclerView.LayoutManager layoutManager; private static RecyclerView recyclerView; private static ArrayList<String> reminderMessages; protected static ArrayList<String> receiverNames; String senderUID; private DatabaseReference mDatabase; //Overriden method onCreateView @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { //Change R.layout.tab1 in you classes View v = inflater.inflate(R.layout.tab3, container, false); //get current user final FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); reminderMessages = new ArrayList<>(); receiverNames = new ArrayList<>(); senderUID = user.getUid(); mDatabase = FirebaseDatabase.getInstance().getReference(); Query query = mDatabase.child("reminders").orderByChild("senderUID").equalTo(senderUID); //Setting size of recycler view as constant recyclerView = (RecyclerView) v.findViewById(R.id.my_recycler_view); recyclerView.setHasFixedSize(true); //Setting Linear Layout layoutManager = new LinearLayoutManager(getActivity()); recyclerView.setLayoutManager(layoutManager); recyclerView.setItemAnimator(new DefaultItemAnimator()); query.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { for (DataSnapshot ds : dataSnapshot.getChildren()) { //Getting message from database String message = ds.child("reminderMessage").getValue(String.class); //Adding database to ArrayList reminderMessages.add(message); //Getting ReceiverUID String rUID = ds.child("receiverUID").getValue(String.class); //Getting corresponding username of the ReceiverUID GetName getName = new GetName(); getName.GetName(rUID); reminderMessages.add(message); // Log.d("String names", receiverNames.toString()); } adapter = new DataAdapter(reminderMessages,receiverNames); recyclerView.setAdapter(adapter); } @Override public void onCancelled(DatabaseError databaseError) { //Error in Reaching Database Toast.makeText(getContext(), "Something went Wrong!", Toast.LENGTH_SHORT).show(); } }); //Returning the layout file after inflating return v; } 属性

到目前为止,我做了几次尝试,但并没有真正走得太远...我会感谢任何帮助这个复杂的(至少从我的角度来看)查询。

3 个答案:

答案 0 :(得分:0)

您可以从以下代码开始:

# -*- frozen-string-literal: true -*-
class UserStatService
  SELECT_CLAUSE = <<~SQL
    (SELECT count(1) FROM projects WHERE user_id=users.id) AS projects_count,
    (SELECT count(1) FROM events WHERE (happened_at IS NULL)
      AND (project_id IN (SELECT id FROM projects WHERE user_id=users.id)
    ) AS events_count,
    users.*
  SQL

  def call
    User
      .select( SELECT_CLAUSE )
      .order( 'events_count' )
  end
end

结果集中的每个User实例都有projects_countevents_count个属性。

还建议使用任何类型的缓存(counter_cache或手工制作)。因为预计查询不会很快。

答案 1 :(得分:0)

假设basic_eventsadditional_events基本上具有相同的列,则此问题的一个很好的解决方案是将您的数据库/应用更改为使用single table inheritance

假设您正在使用Rails 5.x,您的模型将如下所示:

class User < ApplicationRecord
  has_many :projects
  has_many :basic_events, through: :projects
  has_many :additional_events, through: :projects
end
class Project < ApplicationRecord
  belongs_to :user
  has_one :basic_event
  has_many :additional_events
end
class Event < ApplicationRecord
  belongs_to :project
end
class BasicEvent < Event
  belongs_to :project
end
class AdditionalEvent < Event
  belongs_to :project
end

这样,只有三个表涉及usersprojectsevents。您的events表格会有一个:type列,您可以在其中指定basicadditional

您的疑问:

  

访问每个用户的所有项目计数

User
  .select("users.*, count(projects.id) AS projects_count")
  .joins(:projects)
  .group('users.id')
  .order('projects_count DESC')

使用select这样可以访问此活动记录关系中返回的每个User对象的:projects_count方法,因此如果您将此查询分配给名为users_with_projects_count的变量,则可以{{1它将返回与该用户关联的项目数。

  

访问所有事件计数,其中的events_at属性为nil

users_with_projects_count.first.projects_count

您可以像上一个示例中User .select("users.*, count(events.id) AS events_count") .joins(:events) .where('events.happened_at IS NULL') .group('users.id') 那样访问:events_count

  

让所有用户按事件计数排序,其中happen_at属性为nil

:projects_count

您使用与上一个示例相同的查询,只需添加User .select("users.*, count(events.id) AS events_count") .joins(:events) .where('events.happened_at IS NULL') .group('users.id') .order('events_count DESC')

答案 2 :(得分:0)

每位用户的项目数:Project.group(:user_id).count

事件计数,happened_at中的nil属性:Event.where(happened_at: nil).count

根据事件计数,用户按happened_at中的nil属性进行排序:这是最棘手的,但也不是特别困难:

User.
  select("users.*, (SELECT COUNT(*) FROM events WHERE user_id = users.id AND happened_at IS NULL) AS unhappened_events_count").
  order("unhappened_events_count DESC")

这不是一个特别有效的查询,但它应该可以很好地完成数千条记录的工作 - 特别是如果你在user_idhappened_at中设置索引events