【问题标题】:Android Firestore querying particular value in Array of ObjectsAndroid Firestore 查询对象数组中的特定值
【发布时间】:2020-07-17 03:31:33
【问题描述】:

这是我的 firestore 数据库结构:

预期结果:获取所有作业,其中体验数组中的 lang 值为“Swift”。

所以按照这个我应该得到前 2 个文件。第三个文档没有体验“Swift”。

Query jobs = db.collection("Jobs").whereArrayContains("experience.lang","Swift");
jobs.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
            @Override
            public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
               //Always the queryDocumentSnapshots size is 0
            }
        });

尝试了大多数答案,但没有一个成功。有什么方法可以查询这种结构中的数据吗? The docs only available for normal array。不适用于自定义对象数组。

【问题讨论】:

    标签: java android firebase kotlin google-cloud-firestore


    【解决方案1】:

    实际上,当拥有像您这样的数据库结构时,可以执行这样的查询。我已经复制了您的架构,这里是 document1、document2 和 document3。

    请注意,您不能使用部分(不完整)数据进行查询。您仅使用 lang 属性进行查询,这是不正确的。您应该使用包含两个属性的对象,lang 和 years。

    看到你的截图,乍一看,experience 数组是HashMap 对象的列表。但是最好的部分来了,该列表可以简单地映射到自定义对象列表中。让我们尝试将数组中的每个对象映射到Experience 类型的对象。该模型仅包含两个属性:

    public class Experience {
        public String lang, years;
    
        public Experience() {}
    
        public Experience(String lang, String years) {
            this.lang = lang;
            this.years = years;
        }
    }
    

    我不知道您如何命名表示文档的类,但我将其命名为 Job。为了简单起见,我只使用了两个属性:

    public class Job {
        public String name;
        public List<Experience> experience;
        //Other prooerties
    
        public Job() {}
    }
    

    现在,要搜索数组中包含lang 设置为Swift 的对象的所有文档,请按照以下步骤操作。首先,创建Experience 类的新对象:

    Experience firstExperience = new Experience("Swift", "1");
    

    现在你可以这样查询了:

    CollectionReference jobsRef = rootRef.collection("Jobs");
    jobsRef.whereArrayContains("experience", firstExperience).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
        @Override
        public void onComplete(@NonNull Task<QuerySnapshot> task) {
            if (task.isSuccessful()) {
                for (QueryDocumentSnapshot document : task.getResult()) {
                    Job job = document.toObject(Job.class);
                    Log.d(TAG, job.name);
                }
            } else {
                Log.d(TAG, task.getException().getMessage());
            }
        }
    });
    

    logcat 中的结果将是document1 和document2 的名称:

    firstJob
    secondJob
    

    这是因为只有这两个文档在数组中包含一个对象,其中lang 设置为Swift。

    使用地图也可以达到同样的效果:

    Map<String, Object> firstExperience = new HashMap<>();
    firstExperience.put("lang", "Swift");
    firstExperience.put("years", "1");
    

    所以在这个用例中不需要复制数据。我也写了一篇关于同一主题的文章

    编辑:

    在您的方法中,仅当 expreience 为“1”且 lang 为“Swift”时才提供结果,对吗?

    没错,它只搜索一个元素。但是,如果您需要查询更多信息:

    Experience firstExperience = new Experience("Swift", "1");
    Experience secondExperience = new Experience("Swift", "4");
    //Up to ten
    

    我们使用另一种方法,实际上非常简单。我说的是Query的whereArrayContainsAny()方法:

    创建并返回一个带有附加过滤器的新查询,该过滤器文档必须包含指定的字段,值必须是一个数组,并且该数组必须包含提供的列表中的至少一个值。

    在代码中应该是这样的:

    jobsRef.whereArrayContainsAny("experience", Arrays.asList(firstExperience, secondExperience)).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
        @Override
        public void onComplete(@NonNull Task<QuerySnapshot> task) {
            if (task.isSuccessful()) {
                for (QueryDocumentSnapshot document : task.getResult()) {
                    Job job = document.toObject(Job.class);
                    Log.d(TAG, job.name);
                }
            } else {
                Log.d(TAG, task.getException().getMessage());
            }
        }
    });
    

    logcat 中的结果将是:

    firstJob
    secondJob
    thirdJob
    

    这是因为所有三个文档都包含一个或另一个对象。

    我为什么要在文档中谈论复制数据,因为文档有限制。因此,在您可以将多少数据放入文档时存在一些限制。根据usage and limits的官方文档:

    文档的最大大小:1 MiB(1,048,576 字节)

    如您所见,单个文档中的数据总量限制为 1 MiB。所以存储重复数据只会增加变化达到极限。

    如果我将“exprience”和“swift”的空数据发送为“lang”会被查询吗?

    不,不会工作。

    编辑2:

    whereArrayContainsAny() 方法最多可处理 10 个对象。如果你有 30 个,那么你应该将 10 个对象中的每个 query.get() 保存到一个 Task 对象中,然后将 pass them one by one to the 保存到任务的 whenAllSuccess(Task... tasks) 中。

    你也可以pass them directly as a list 到Tasks 的whenAllSuccess(Collection> tasks) 方法。

    【讨论】:

    • 顺便说一句,您的数据库中有错字,数组中的第一个对象有一个属性lan 和not lang。
    • 抱歉,这是一个拼写错误。实际上这是一个类似结构的假模型来提出问题(我的数据隐私)
    • 哦,我明白了。不用担心,此解决方案适用于任何类型的数据。你试过了吗?
    • 在提问之前我已经尝试过这种方法。但我不想查询这些年。在您的方法中,仅当 years 为“1”且 lang 为“Swift”时才提供结果,对吗?但是如果数组包含“swift”,我想要所有的工作。您的方法避免了数据重复。但我对此感到困惑。如果我将“years”和“swift”的空数据发送为“lang”,它会被查询吗?如果我尝试过 Dough 的答案及其工作。我认为在 javascript 中,有一个查询此类数据的选项。感谢您的建议。
    • 我明白您的担忧,但实际上有一个非常简单的解决方案。因此,请使用有助于避免重复数据的方法查看我的更新答案。另请参阅 Firestore 文档限制。
    【解决方案2】:

    使用您当前的文档结构,无法执行您想要的查询。 Firestore 不允许查询列表字段中对象的单个字段。

    您需要做的是在文档中创建一个可查询的附加字段。例如,您可以创建一个列表字段,其中仅包含作为文档一部分的字符串语言列表。有了这个,您可以使用包含数组的查询来查找至少提到一次语言的文档。

    对于屏幕截图中显示的文档,您将有一个名为“languages”的列表字段,其值为 ["Swift", "Kotlin"]。

    【讨论】:

    • 感谢您的快速回答。但是我需要类似的结构,其中经验在文档中。如果我使用嵌套集合,是否可以轻松查询特定数据?
    • 查询子集合从来都不是“难”的。它们就像顶级集合一样工作。现在,需要明确的是,在我的回答中,我并不是建议你改变你现在拥有的东西。我建议您只需向其中添加新数据以进行查询。这非常常见。
    • 我将按照您的建议将新数据添加为普通数组。看起来很简单。谢谢
    猜你喜欢
    • 2021-07-30
    • 1970-01-01
    • 2021-03-26
    • 1970-01-01
    • 2018-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多