在Cloud Firestore中将OR查询与AND查询结合使用

时间:2018-09-18 10:49:12

标签: javascript firebase google-cloud-firestore

我需要根据一些过滤器类别来过滤文档:

在经典情况下,应在同一类别内的“或”条件下以及不同类别之间的“与”查询中获取值。 我使用以下代码为Cloud Firestore构建QueryFn(尚未进行任何优化):

const queryResults: Observable<Place[]>[] = [];
queryFilter.typeFilter.forEach(filter => {
queryResults.push(
    this.afs
        .collection<Place>(
            this.PLACE_COLLECTION,
            ref => ref.orderBy('placeName').where('type', '==', filter)
        )
        .valueChanges()
    );
});

queryFilter.starsFilter.forEach(filter => {
    queryResults.push(
        this.afs
            .collection<Place>(
                this.PLACE_COLLECTION,
                ref => ref.orderBy('placeName').where('rate', '==', filter) 
            )
            .valueChanges()
    );
});

// TODO: it should filter AND over the OR results

const resultNum = queryResults.length;

if (resultNum === 1) {
    return queryResults[0].pipe(shareReplay(1));
} else if (resultNum > 1) {
    const firstObs = queryResults[0];
    const clone = [...queryResults]; 
    clone.splice(0, 1);
    return firstObs.pipe(
        combineLatest(...clone, (...arrays) => arrays.reduce((acc: any, array: any) => [...acc, ...array], [])),
        shareReplay(1)
    );
}

return of([]);

在这里,我将如何构建过滤器并调用上面的方法:

 combineLatest(
        this.formGroup.get('typeFilters').valueChanges.pipe(startWith(null)),
        this.formGroup.get('starsFilters').valueChanges.pipe(startWith(null))
    )
        .pipe(
            filter(values => !(values[0] === null && values[1] === null)),
            switchMap(([types, stars]) => {
                const typeFilter: string[] = [];
                const starsFilter: number[] = [];

                if (types) {
                    types.map((v: string, idx: number) => {
                        if (v) {
                            typeFilter.push(this.placeTypelabels[idx]);
                        }
                    });
                }

                if (stars) {
                    stars.map((v: string, idx: number) => {
                        if (v) {
                            starsFilter.push(this.ratinglabels[idx].value);
                        }
                    });
                }

                const filters: FilterModel = { typeFilter: typeFilter, starsFilter: starsFilter };
                return this.placeService.filterPlaces(filters);
            }),
            takeUntil(this.destroy$)
        )
        .subscribe(p => {
            return (this.places = p);
        });

分别可以正确检索结果,但是它们不在两个结果集之间。如果不使用Elastic Search或类似的解决方案,是否可以在Firestore中实现这一目标?

1 个答案:

答案 0 :(得分:0)

经过更多的尝试,即使结果不尽理想,我仍然能够实现自己的目标。

我以以下方式编辑了构建AND / OR查询的代码:

filterPlaces(queryFilter: FilterModel): Observable<Place[]> {
    const queryResults: Observable<Place[]>[] = [];

    const typeFilter = queryFilter && queryFilter.typeFilter ? queryFilter.typeFilter : [];
    const starsFilter = queryFilter && queryFilter.starsFilter ? queryFilter.starsFilter : [];

    // No filter items selected
    if (typeFilter.length < 1 && starsFilter.length < 1) {
        return this.queryPlaces(null);
    }

    if (typeFilter.length > 0 && starsFilter.length > 0) {
        // Multiple filters internally in OR and among them in AND
        starsFilter.forEach(starFilter => {
            // FIXME: The number of Calls grows with the filters used!!
            typeFilter.forEach(filter => {
                queryResults.push(
                    this.afs
                        .collection<Place>(this.PLACE_COLLECTION, ref =>
                            ref
                                .orderBy('placeName')
                                .where('type', '==', filter)
                                .where('rate', '==', starFilter)
                        )
                        .valueChanges()
                );
            });
        });
    } else {
        // Individual OR Cases
        if (typeFilter.length > 0) {
            typeFilter.forEach(filter => {
                queryResults.push(
                    this.afs.collection<Place>(this.PLACE_COLLECTION, ref => ref.orderBy('placeName').where('type', '==', filter)).valueChanges()
                );
            });
        }

        if (starsFilter.length > 0) {
            starsFilter.forEach(filter => {
                queryResults.push(
                    this.afs.collection<Place>(this.PLACE_COLLECTION, ref => ref.orderBy('placeName').where('rate', '==', filter)).valueChanges()
                );
            });
        }
    }

    return combineLatest(...queryResults, (...arrays) => arrays.reduce((acc: any, array: any) => [...acc, ...array], [])).pipe(shareReplay(1));
}

如果用户在两个过滤器中都至少选择了一个值,则会迭代一个过滤器的所选项目,并将它们作为进一步的AND条件添加到第二个过滤器中选择的每个项目。

这样,我可以执行类似的查询:例如“获取(酒店或酒吧)AND(1 *或2 *或5 *)”

但是,此方法仅适用于小型过滤器,因为调用数量随过滤器及其选定元素的数量而增加。 我将这段代码留在这里,因为它可能会对其他人有所启发。不过,我很好奇是否有人可以建议如何回答我的原始问题或改进当前的解决方案。