Answer the question
In order to leave comments, you need to log in
Is there a more convenient way to build an aggregation pipeline for filtering?
Our CRM system has the ability to sort students by various parameters, such as: age, gender, first name, last name, patronymic, teachers, groups.
And at the moment, all this has the following implementation: the client sends filtering parameters to the server in the format of such a DTO
export class FilterDTO {
@IsOptional()
@IsString({ each: true })
names?: string[];
@IsOptional()
@IsString({ each: true })
surnames?: string[];
@IsOptional()
@IsString({ each: true })
midnames?: string[];
@IsOptional()
@IsArray()
ages?: number[];
@IsOptional()
@IsString({ each: true })
gender?: string[];
@IsOptional()
@IsString({ each: true })
groups?: string[];
@IsOptional()
@IsString({ each: true })
tutors?: string[];
@IsOptional()
@IsObject()
balance?: { $gte?: number; $lte?: number; $lt?: number };
@IsOptional()
@IsBoolean()
emptyAge?: boolean;
@IsOptional()
@IsString({ each: true })
@IsMongoId({ each: true })
statuses?: string[];
}
private createFilterPipeline(filters: FilterDTO): any[] {
if (!filters) return;
const pipeline = [];
if (filters.names) {
const nameFilter = {
$match: {
$or: []
}
};
filters.names.forEach(name => {
nameFilter.$match.$or.push({
name: { $regex: new RegExp(`${name}`, 'i') }
});
});
pipeline.push(nameFilter);
}
if (filters.surnames) {
const surnameFilter = {
$match: {
$or: []
}
};
filters.surnames.forEach(surname => {
surnameFilter.$match.$or.push({
surname: { $regex: new RegExp(`${surname}`, 'i') }
});
});
pipeline.push(surnameFilter);
}
if (filters.midnames) {
const midnameFilter = {
$match: {
$or: []
}
};
filters.midnames.forEach(midname => {
midnameFilter.$match.$or.push({
midname: { $regex: new RegExp(`${midname}`, 'i') }
});
});
pipeline.push(midnameFilter);
}
if (filters.balance) {
if (filters.balance.$lte) {
pipeline.push({
$match: {
$and: [
{
balance: {
$gte: filters.balance.$gte
}
},
{
balance: {
$lte: filters.balance.$lte
}
}
]
}
});
} else {
pipeline.push({
$match: {
$or: [
{
balance: {
$gte: filters.balance.$gte
}
},
{
balance: {
$lt: filters.balance.$lt
}
}
]
}
});
}
}
if (filters.gender) {
pipeline.push({
$match: {
gender: {
$in: filters.gender
}
}
});
}
if (filters.groups) {
pipeline.push({
$match: {
groups: {
$all: filters.groups
}
}
});
}
if (filters.ages) {
const agesFilter = {
$match: {
$or: []
}
};
if (filters.emptyAge === true) {
agesFilter.$match.$or.push({ dateOfBirth: null });
}
filters?.ages?.forEach(age => {
agesFilter.$match.$or.push({
dateOfBirth: {
$gte: new Date(
moment()
.subtract(age + 1, 'years')
.add(1, 'day')
.toISOString()
),
$lt: new Date(
moment().subtract(age, 'years').toISOString()
)
}
});
});
pipeline.push(agesFilter);
} else if (!filters.ages && filters.emptyAge === true) {
pipeline.push({ $match: { dateOfBirth: null } });
}
if (filters.tutors) {
pipeline.push({
$match: {
'tutors.tutor': { $in: filters.tutors }
}
});
}
if (filters.statuses) {
pipeline.push({
$match: {
statuses: {
$all: filters.statuses.map(status =>
Types.ObjectId(status)
)
}
}
});
}
return pipeline;
}
}
Answer the question
In order to leave comments, you need to log in
Well, we can generalize all this a bit - let's remove the darkness of if's
. we have a set of filters with arbitrary parameters (each filter has its own)
And at the output we go to get the final query to the database
. We can describe the filter processing rules as an object, where the key is the name of the filter, and the value is a callback that returns an element of this pipeline.
Something like
const filters = {
groups: (groups) => ({
$match: {
groups: {
$all: groups
}
}
})
...
}
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question