Объедините ИЛИ-запрос с И-запросом в Cloud Firestore - PullRequest
0 голосов
/ 18 сентября 2018

Мне нужно отфильтровать документы по категориям некоторых фильтров:

Это классический случай, когда значения должны быть взяты в условии ИЛИ в одной и той же категории и в запросе И между различными категориями.Я использую следующий код для создания QueryFn for Cloud Firestore (без какой-либо оптимизации):

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

По отдельности результаты корректно извлекаются, но они не находятся в AND между двумя наборами результатов.Можно ли добиться этого в Firestore без использования Elastic Search или подобных решений?

1 Ответ

0 голосов
/ 19 сентября 2018

После еще нескольких испытаний я смог достичь своей цели, даже если результат более чем неоптимальный.

Я отредактировал код, строящий запросы 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));
}

Если пользователь выбирает хотя бы значение в обоих фильтрах, я перебираю выбранные элементы одногофильтровать, и я добавляю их как условие «И» к каждому элементу, выбранному во втором фильтре.

Таким образом, я могу выполнять запросы типа: «Получить (Гостиница И Бар) И (1 * ИЛИ 2 *»ИЛИ 5 *) " например.

Однако этот подход подходит только для небольших фильтров, так как количество вызовов увеличивается с увеличением количества фильтров и их выбранных элементов.Я оставляю этот код здесь, поскольку он может быть источником вдохновения для кого-то другого.Мне любопытно, если кто-нибудь может предложить, как ответить на мой первоначальный вопрос или улучшить текущее решение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...