Android Firestore查询对象数组中的特定值

huangapple go评论102阅读模式
英文:

Android Firestore querying particular value in Array of Objects

问题

这是我的 Firestore 数据库结构:

预期结果:获取所有工作,其中在经验数组中,lang 值为 "Swift"。

根据这个,我应该首先获取前两个文档。第三个文档没有 "Swift" 经验。

Query jobs = db.collection("Jobs").whereArrayContains("experience.lang", "Swift");
jobs.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
    @Override
    public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
       //queryDocumentSnapshots 的大小始终为 0
    }
});

尝试了大多数答案,但都没有成功。在这种结构下是否有查询数据的方法?文档仅适用于普通数组,不适用于自定义对象数组。

英文:

This is my structure of the firestore database:

Android Firestore查询对象数组中的特定值

Expected result: to get all the jobs, where in the experience array, the lang value is "Swift".

So as per this I should get first 2 documents. 3rd document does not have experience "Swift".

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

Tried most of the answers but none worked out. Is there any way to query data in this structure? The docs only available for normal array. Not available for array of custom object.

答案1

得分: 4

以下是翻译好的内容:

实际上,如果数据库结构与您的类似,执行此类查询是可能的。我已经复制了您的模式,这里有document1document2document3

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

从您的屏幕截图中可以看出,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;
    //其他属性

    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());
        }
    }
});

日志中的结果将是document1document2的名称:

firstJob
secondJob

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

当使用Map时,您也可以实现相同的结果:

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

因此,在这种用例中不需要复制数据。我还写了一篇关于同一主题的文章:

编辑:

> 在您的方法中,只有当experience为“1”且lang为“Swift”时才会提供结果,对吗?

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

Experience firstExperience = new Experience("Swift", "1");
Experience secondExperience = new Experience("Swift", "4");
//最多十个

我们采用另一种方法,这实际上非常简单。我指的是QuerywhereArrayContainsAny()方法:

> 创建并返回一个新的Query,其中包含额外的过滤器,文档必须包含指定的字段,该值必须是一个数组,该数组必须至少包含来自提供的列表的一个值。

在代码中应该如下所示:

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());
        }
    }
});

日志中的结果将是:

firstJob
secondJob
thirdJob

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

为什么会谈到在文档中复制数据呢?因为文档有限制。因此,在一个文档中放入多少数据存在一些限制。根据关于用法和限制的官方文档:

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

正如您所见,您在单个文档中的数据总量限制为1 MiB。因此,在文档中存储重复数据只会增加达到限制的机会。

> 如果我将“experience”和“lang”的空数据发送为“null”,是否会查询成功?

不会,这将不起作用。

编辑2:

whereArrayContainsAny()方法最多与10个对象一起使用。如果您有30个对象,那么您应该将每个包含10个对象的query.get()保存到一个Task对象中,然后将它们一个接一个地传递给whenAllSuccess(Task...> tasks)方法。

您还可以直接将它们作为列表传递给Tasks的[whenAllSuccess(Collection> tasks)](https://developers.google.com/android/reference/com/google/android/gms/tasks/Tasks#whenAllSuccess(java.util.Collection%3C?%20extends%20com.google.android.gms

英文:

Actually it is possible to perform such a query when having a database structure like yours. I have replicated your schema and here are document1, document2, and document3.

Note that you cannot query using partial (incomplete) data. You are using only the lang property to query, which is not correct. You should use an object that contains both properties, lang and years.

Seeing your screenshot, at first glance, the experience array is a list of HashMap objects. But here comes the nicest part, that list can be simply mapped into a list of custom objects. Let's try to map each object from the array to an object of type Experience. The model contains only two properties:

public class Experience {
    public String lang, years;

    public Experience() {}

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

I don't know how you named the class that represents a document, but I named it simply Job. To keep it simple, I have only used two properties:

public class Job {
    public String name;
    public List&lt;Experience&gt; experience;
    //Other prooerties

    public Job() {}
}

Now, to perform a search for all documents that contain in the array an object with the lang set to Swift, please follow the next steps. First, create a new object of the Experience class:

Experience firstExperience = new Experience(&quot;Swift&quot;, &quot;1&quot;);

Now you can query like so:

CollectionReference jobsRef = rootRef.collection(&quot;Jobs&quot;);
jobsRef.whereArrayContains(&quot;experience&quot;, firstExperience).get().addOnCompleteListener(new OnCompleteListener&lt;QuerySnapshot&gt;() {
    @Override
    public void onComplete(@NonNull Task&lt;QuerySnapshot&gt; 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());
        }
    }
});

The result in the logcat will be the name of document1 and document2:

firstJob
secondJob

And this is because only those two documents contain in the array an object where the lang is set to Swift.

You can also achieve the same result when using a Map:

Map&lt;String, Object&gt; firstExperience = new HashMap&lt;&gt;();
firstExperience.put(&quot;lang&quot;, &quot;Swift&quot;);
firstExperience.put(&quot;years&quot;, &quot;1&quot;);

So there is no need to duplicate data in this use-case. I have also written an article on the same topic

Edit:

> In your approach it provides the result only if expreience is "1" and lang is "Swift" right?

That's correct, it only searches for one element. However, if you need to query for more than that:

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

We use another approach, which is actually very simple. I'm talking about Query's whereArrayContainsAny() method:

> Creates and returns a new Query with the additional filter that documents must contain the specified field, the value must be an array, and that the array must contain at least one value from the provided list.

And in code should look like this:

jobsRef.whereArrayContainsAny(&quot;experience&quot;, Arrays.asList(firstExperience, secondExperience)).get().addOnCompleteListener(new OnCompleteListener&lt;QuerySnapshot&gt;() {
    @Override
    public void onComplete(@NonNull Task&lt;QuerySnapshot&gt; 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());
        }
    }
});

The result in the logcat will be:

firstJob
secondJob
thirdJob

And this is because all three documents contain one or the other object.

Why am I talking about duplicating data in a document it's because the documents have limits. So there are some limits when it comes to how much data you can put into a document. According to the official documentation regarding usage and limits:

> Maximum size for a document: 1 MiB (1,048,576 bytes)

As you can see, you are limited to 1 MiB total of data in a single document. So storing duplicated data will only increase the change to reach the limit.

> If i send null data of "exprience" and "swift" as "lang" will it be queried?

No, will not work.

Edit2:

whereArrayContainsAny() method works with max 10 objects. If you have 30, then you should save each query.get() of 10 objects into a Task object and then pass them one by one to the to the Tasks's whenAllSuccess(Task...<?> tasks).

You can also pass them directly as a list to Tasks's whenAllSuccess(Collection<? extends Task<?>> tasks) method.

答案2

得分: 2

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

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

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

英文:

With your current document structure, it's not possible to perform the query you want. Firestore does not allow queries for individual fields of objects in list fields.

What you would have to do is create an additional field in your document that is queryable. For example, you could create a list field with only the list of string languages that are part of the document. With this, you could use an array-contains query to find the documents where a language is mentioned at least once.

For the document shown in your screenshot, you would have a list field called "languages" with values ["Swift", "Kotlin"].

huangapple
  • 本文由 发表于 2020年4月4日 01:39:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/61017457.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定