如何从 Firestore 查询中排除元素?

Posted

技术标签:

【中文标题】如何从 Firestore 查询中排除元素?【英文标题】:How to exclude an element from a Firestore query? 【发布时间】:2019-01-22 09:17:13 【问题描述】:

我有一组用户,我想从数据库中查询所有用户并将它们显示在 RecyclerView 中,除了一个 我的。这是我的数据库架构:

users [collection]
  - uid [document]
     - uid: "fR5bih7SysccRu2Gu9990TeSSyg2"
     - username: "John"
     - age: 22
  - //other users

如何像这样查询数据库:

String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
Query q = db.collection("users").whereNotEqualTo("uid", uid);

所以我需要将此查询对象传递给FirestoreRecyclerOptions 对象,以便显示RecyclerView 中的所有其他用户。

这甚至可能吗?如果没有,我该如何解决这个问题?谢谢!

编辑:

options = new FirestoreRecyclerOptions.Builder<UserModel>()
        .setQuery(query, new SnapshotParser<UserModel>() 
            @NonNull
            @Override
            public UserModel parseSnapshot(@NonNull DocumentSnapshot snapshot) 
                UserModel userModel = documentSnapshot.toObject(UserModel.class);
                if (!userModel.getUid().equals(uid)) 
                    return userModel;
                 else 
                    return new UserModel();
                
            
        ).build();

【问题讨论】:

你执行并查看结果了吗? @UmarHussain 我无法执行此操作,因为没有whereNotEqualTo,这就是为什么我要问我该如何解决这个问题。你有什么想法吗? 好吧,firestore 与我认为的方法相同,但缺少此方法。 如果只是一项的问题,为什么不进行客户端过滤呢? @SuhaylSH 您能否就如何实现这一目标发表答案?谢谢! 【参考方案1】:

经过几天和几天的努力,我终于找到了答案。没有@Raj 的帮助,我无法解决这个问题。非常感谢@Raj 的耐心和指导。

首先,根据@Frank van Puffelen 在此post 的回答中提供的答案,我停止寻找可以帮助我将两个查询传递给单个适配器的解决方案。

在这个问题中,我想要实现的只是查询数据库以获取除我之外的所有用户。所以因为我们不能将两个查询合并到一个实例中,我发现我们可以合并两个查询的结果。所以我创建了两个查询:

FirebaseFirestore db = FirebaseFirestore.getInstance();
Query firstQuery = db.collection("users").whereLessThan("uid", uid);
Query secondQuery = db.collection("users").whereGreaterThan("uid", uid);

我的用户对象有一个UserModel (POJO) 类。我发现不是一种,而是两种解决问题的方法。第一个是查询数据库以获取与第一个条件相对应的所有用户对象并将它们添加到列表中。之后,再次查询数据库,获取与第二个条件对应的其他用户对象,并将它们添加到 same 列表中。现在我有一个列表,其中包含我需要的所有用户,但只有一个,即具有来自查询的特定 id 的用户。这是未来访问者的代码:

firstQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() 
    @Override
    public void onComplete(@NonNull Task<QuerySnapshot> task) 
        List<UserModel> list = new ArrayList<>();
        if (task.isSuccessful()) 
            for (DocumentSnapshot document : task.getResult()) 
                UserModel userModel = document.toObject(UserModel.class);
                list.add(userModel);
            

            secondQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() 
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) 
                    if (task.isSuccessful()) 
                        for (DocumentSnapshot document : task.getResult()) 
                            UserModel userModel = document.toObject(UserModel.class);
                            list.add(userModel);
                        

                        //Use the list of users
                    
                
            );
        
    
);

第二种方法会更短,因为我像这样使用Tasks.whenAllSuccess()

Task firstTask = firstQuery.get();
Task secondTask = secondQuery.get();

Task combinedTask = Tasks.whenAllSuccess(firstTask, secondTask).addOnSuccessListener(new OnSuccessListener<List<Object>>() 
        @Override
        public void onSuccess(List<Object> list) 
            //This is the list that I wanted
        
);

【讨论】:

现在,您能告诉我如何在 Firestore Recycler Adapter 中传递这些数据吗? 在这两种情况下,我都有一个除我之外的所有用户对象的列表。我会将该列表传递给适配器构造函数并相应地填充视图。 当您成功将此数据传递给“Firestore Recycler Adapter”后,请发布解决方案。 你可以改进这个解决方案,因为它使用两个查询来存储,从长远来看它会增加你的成本。如果您有时间改进它,请查看客户端的回收器视图和过滤的自定义实现。 @AdamHurwitz 感谢您的评论和回答,但为什么您会说“查询 Firebase 两次以返回一组信息对于现实世界的应用程序而言似乎不具有成本效益”?会发生什么?谢谢!【参考方案2】:

根据官方firestore文档:-

Cloud Firestore 不支持以下类型的查询:

带有 != 子句的查询。在这种情况下,您应该拆分查询 分为大于查询和小于查询。例如,虽然 不支持查询子句 where("age", "!=", "30"),可以 通过组合两个查询获得相同的结果集,一个带有子句 where("age", "", 30) 的子句。

如果您使用的是 FirestoreRecyclerAdapter,那么 FirestoreRecyclerOptions 将使用 setQuery() 方法直接接受查询,因此不允许您执行客户端过滤。

如果您尝试在 onBindViewHolder() 中应用过滤器,同时设置可能导致回收站视图中出现空项目的数据。为了解决这个问题,请参考方法 2。

因此,解决问题的可能方法是在每个文档下的用户集合中创建一个整数字段。例如:-

users [collection]
  - uid [document]
     - uid: "fR5bih7SysccRu2Gu9990TeSSyg2"
     - username: "John"
     - age: 22
     - check: 100

在此我创建了一个值为 100 的“检查”变量。因此,将所有其他文档中的“检查”值设置为小于 100。 现在,您可以轻松地进行查询以查找带有 check

Query q = db.collection("users").whereLessThan("check", 100);

这将检索除您不想要的文档之外的所有文档。在设置数据时,您可以设置其他参数跳过检查变量。

方法 2(客户端过滤)

我们可以在 onBindViewHolder() 方法中应用检查,如果检索到的 uid 与当前用户 uid 匹配,则将 Recycler 视图的高度设置为 0dp。如:-

ViewUserAdapter.java

public class ViewUserAdapter extends FirestoreRecyclerAdapter<User, ViewUserAdapter.ViewUserHolder>

    String uid;
    FirebaseAuth auth;

    public ViewUserAdapter(@NonNull FirestoreRecyclerOptions<User> options)
    
        super(options);
        auth = FirebaseAuth.getInstance();
        uid = auth.getCurrentUser().getUid();
    

    @Override
    protected void onBindViewHolder(@NonNull ViewUserHolder holder, int position, @NonNull User model)
    
        DocumentSnapshot snapshot =  getSnapshots().getSnapshot(position);
        String id = snapshot.getId();

        if(uid.equals(id))
        
            RecyclerView.LayoutParams param = (RecyclerView.LayoutParams)holder.itemView.getLayoutParams();
            param.height = 0;
            param.width = LinearLayout.LayoutParams.MATCH_PARENT;
            holder.itemView.setVisibility(View.VISIBLE);

        
        else
        
            holder.tvName.setText(model.name);
            holder.tvEmail.setText(model.email);
            holder.tvAge.setText(String.valueOf(model.age));
        
    

【讨论】:

希望这有助于@IoanaP。 非常感谢您的回答,非常感谢,但我无法理解您的解决方案如何解决我的问题。假设我在应用程序中通过了身份验证,一切正常,但如果另一个用户正在使用该应用程序会发生什么?它将显示除我之外的所有用户,这不是我想要的。我要显示除他以外的所有用户,对吧? Raj,你有别的想法吗?谢谢! 因为您在上述问题中没有提到任何有关此的内容。因此,我认为您的要求是仅在一个地方或为一个用户检索数据。因此,这不适用于您的情况。所以,让我想想另一种逻辑来解决你的问题@loanaP。 好的,慢慢来,再次感谢您尝试帮助我。希望你能找到办法,因为我已经挣扎了好几天了。【参考方案3】:

Firestore 不支持not equal to 操作。所以你需要在客户端过滤数据。因为在您的情况下,您只有一个额外的项目,您可以将其过滤掉。

为此,您可能需要构建自己的回收器实现,在将数据添加到回收器适配器数据层时,您会在数据与您的 != 条件匹配时限制数据。

我还没有探索提供的回收器实现 firebase,所以我不能说它是否支持对适配器数据的数据操作。

这是开始实施回收站视图的好资源:https://www.androidhive.info/2016/01/android-working-with-recycler-view/

【讨论】:

感谢您的回答奥马尔。在将名称设置为RecyclerView 时,我已经尝试过使用条件,但我总是得到一个空的项目视图。文本不存在,但视图仍然存在。想象一下,我的数据库中有 4 个用户,当我尝试使用 if 语句 (if(!myUserClas.getId().equals(uid))) 显示它们时,我得到 3 行带有名称的行,1 行没有。您还有其他想法吗?s 你在正确的轨道上,但问题是你在哪里应用条件。那时回收站视图已经有数据,您的条件只删除名称而不是实际的回收站项目。 是的,这正是正在发生的事情。您是绝对正确的,所以我认为我应该在将查询传递给FirestoreRecyclerOptions 之前删除该元素,对吗? 您是否尝试过使用 set 查询的客户解析器并在找到具有当前用户 ID 的用户快照时返回 null?通过传递 null 我认为您可以删除该空视图,但我不确定。 github.com/firebase/FirebaseUI-Android/blob/master/database/… 我无法返回null,因为我收到此错误:FATAL EXCEPTION: main Process: com.package.firebase, PID: 1388 java.lang.NullPointerException: key == null || value == null 我试图返回return new UserModel();,但结果又是一个空视图。你知道为什么会这样吗?我该如何解决这个问题?【参考方案4】:

2021 年更新:支持此功能

开发人员您好。看起来这是 now supported,其中使用了 where 运算符:citiesRef.where("capital", "!=", false);

【讨论】:

【参考方案5】:

最简单的解决方案是使用 PagedListAdapter 并为 Firestore 查询创建自定义 DataSource。在 DataSource 中,Query 可以转换为 Array 或 ArrayList,您可以在其中轻松删除您的项目,然后再将数据添加到方法 callback.onResult(...)

我使用类似的解决方案在 Firestore 查询之后处理数据,以便按时间属性过滤和排序,然后在客户端中按质量分数属性重新排序,然后再将数据传回 callback.onResult(...)

文档

谷歌:Build your own data sources 代码路径:Paging Library Guide

数据源示例

class ContentFeedDataSource() : ItemKeyedDataSource<Date, Content>() 

override fun loadBefore(params: LoadParams<Date>, callback: LoadCallback<Content>) 

override fun getKey(item: Content) = item.timestamp

override fun loadInitial(params: LoadInitialParams<Date>, callback: LoadInitialCallback<Content>) 
    FirestoreCollections.contentCollection
            .collection(FirestoreCollections.ALL_COLLECTION)
            .orderBy(Constants.TIMESTAMP, Query.Direction.DESCENDING)
            .whereGreaterThanOrEqualTo(Constants.TIMESTAMP, DateAndTime.getTimeframe(WEEK))
            .limit(params.requestedLoadSize.toLong())
            .get().addOnCompleteListener 
                val items = arrayListOf<Content?>()
                for (document in it.result.documents) 
                    val content = document.toObject(Content::class.java)
                    items.add(content)
                
                callback.onResult(items.sortedByDescending  it?.qualityScore )
            


override fun loadAfter(params: LoadParams<Date>, callback: LoadCallback<Content>) 
    FirestoreCollections.contentCollection
            .collection(FirestoreCollections.ALL_COLLECTION)
            .orderBy(Constants.TIMESTAMP, Query.Direction.DESCENDING)
            .startAt(params.key)
            .whereGreaterThanOrEqualTo(Constants.TIMESTAMP, DateAndTime.getTimeframe(WEEK))
            .limit(params.requestedLoadSize.toLong())
            .get().addOnCompleteListener 
                val items = arrayListOf<Content?>()
                for (document in it.result.documents) 
                    val content = document.toObject(Content::class.java)
                    items.add(content)
                
                val sortedByQualityScore = ArrayList(items.sortedByDescending  it?.qualityScore )
                callback.onResult(sortedByQualityScore)
                sortedByQualityScore.clear()
            


【讨论】:

您能否解释一下如何使用此代码从查询中删除单个用户(即我)?我更喜欢 Java,因为我不知道 JS 或 Kotlin。谢谢! @IoanaP。在上面的示例代码中,loadInitial() 方法使 Firebase Firestore 查询返回数据it.result.documents。我将每个document 转换为我自己的Content 对象:val content = document.toObject(Content::class.java) 然后将其添加到列表items. 在所有Content 对象都在items 列表中之后,您可以从中删除不需要的用户在将项目列表添加到回调方法callback.onResult(items) 之前使用.remove(itemToRemove) 的列表。您还要确保在 loadAfter() 方法中执行相同的操作。 我上面帖子中的文档详细解释了 PagedListAdapter。 据我从您的代码中了解到,您正在订购结果,您并没有像我最初的问题那样排除其中一个。您能否提供一个具体示例,说明如何使用您的代码获得与我相同的结果?谢谢! @IoanaP。正确,第一步是订购结果。将有序结果组织到一个集合(如 ArrayList)中后,如果您根据对象的属性为对象定义自定义 Hash Code,则可以删除一个或多个对象。如果您定义了ArrayList&lt;YourCustomObject&gt;,则可以调用listName.remove(objectName)。这是定义自定义哈希码的示例:mkyong.com/java/java-how-to-overrides-equals-and-hashcode。【参考方案6】:

更简单且更早的客户端过滤(当您将项目添加到列表时):

    使用 Firestore 的标准方法获取当前用户的 ID。 获取用户集合中所有用户的文档名称。 在将用户添加到之前 您的 RecyclerView 列表,检查它要添加到您的列表中的用户不是当前用户。

以这种方式完成后,您可以在客户端使用“不等于”方法,而不会遇到任何 Firestore 问题。另一个好处是您不必弄乱您的适配器或从您不希望在回收器中的列表项中隐藏视图。

public void getUsers(final ArrayList<Users> usersArrayList, final Adapter adapter) 

    CollectionReference usersCollectionRef = db.collection("users");

    Query query = usersCollectionRef
            .whereEqualTo("is_onboarded", true);

    query.get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() 
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) 
                    if (task.isSuccessful()) 
                        for (QueryDocumentSnapshot document : task.getResult()) 

                            final String otherUserID = document.getId();

                             FirebaseUser user = mAuth.getCurrentUser();
                             String currentUserID = user.getUid();

                            if (!otherUserID.equals(currentUserId)) 

                              usersArrayList.add(new User(otherUserID));
                              adapter.notifyDataSetChanged(); //Ensures users are visible immediately
                                            
                                         else 
                                            Log.d(TAG, "get failed with ", task.getException());
                                        
                                    
                                );
                            

                        
                     else 
                        Log.d(TAG, "Error getting documents: ", task.getException());
                    
                
            );

【讨论】:

【参考方案7】:

你不必做所有这些

只需将 getLayoutParams().height 和 width 分别设置为 0 即可进行普通查询并隐藏布局。请参阅下面的示例。

  if(firebaseUserId.equalIgnoreCase("your_holder_firebase_user_id")
    holder.mainLayout.setVisibility(View.INVISIBLE);
    holder.mainLayout.getLayoutParams().height = 0;
    holder.mainLayout.getLayoutParams().width = 0;

  else 
   //show your list as normal
  
  //This will hide any document snapshot u don't need, it will be there but hidden
 

【讨论】:

【参考方案8】:

这是我的用户名颤振解决方案

Future<bool> checkIfUsernameExistsExcludingCurrentUid(
      // TODO NOT DONE
      String username,
      String uid) async 
    print("searching db for: $username EXCLUDING SELF");
    bool exists = true;

    QuerySnapshot result = await _firestore
        .collection(USERS_COLLECTION)
        .where(
          "username",
          isEqualTo: username,
        )
        .getDocuments();

    List _documents = result.documents;
    _documents.forEach((element) 
      if (element['uid'] == uid) 
        exists = false;
       else 
        return true;
      
    );

    return exists;
  

【讨论】:

以上是关于如何从 Firestore 查询中排除元素?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 FireStore 查询文档名称?

如何通过在 Firestore 中的查询从索引中获取多个文档到其他索引

如何从 Firestore 7.24.0 实例中查询具有多个 where 子句的数据?

如何从 firestore 两个集合中获取数据并将所有数据合并在一起

FLUTTER,FIRESTORE:我有一个流构建器,它从集合中的所有文档中返回某个字段..如何添加查询?

如何以管理员身份从命令行查询 Firestore