【问题标题】:Ignore $in if empty array is provided如果提供了空数组,则忽略 $in
【发布时间】:2021-07-26 03:46:26
【问题描述】:

我有以下型号:

export type IMangaModel = Document & {
  _id: string;
  title: string;
  description: string;
  demographic: Array<string>;
  genre: Array<string>;
  tags: Array<string>;
  related: Array<IMangaModel>;
  cover_image: {
    extraLarge: string | null;
    large: string | null;
    medium: string | null;
  };
  banner: string | null;
  al_id: number;
  al_url: string;
  likes: Array<IUserModel['_id']>;
};

注意:接口与模型完全匹配,但我将其粘贴为它更短。

我正在使用以下数据过滤此集合:

includeFilters: {
    genres: Array<string>;
    tags: Array<string>;
    demographics: Array<string>;
  };
  excludeFilters: {
    genres: Array<string>;
    tags: Array<string>;
    demographics: Array<string>;
  };

我想要实现的是找到每个文档并检查相应的数组是否具有我通过 includeFilters 发送的数组中的至少一个值。虽然它没有通过 excludeFilters 发送的值。

为此,我使用了以下查询:

const results = await this._manga
      .find(
        {
          tags: {
            $elemMatch: {
              $in: data.includeFilters.tags,
              $nin: data.excludeFilters.tags,
            },
          },
          genre: {
            $elemMatch: {
              $in: data.includeFilters.genres,
              $nin: data.excludeFilters.genres,
            },
          },
          demographic: {
            $elemMatch: {
              $in: data.includeFilters.demographics,
              $nin: data.excludeFilters.demographics,
            },
          },
        },
        { al_id: 1 }
      );

这似乎可以正常工作只要,因为 includeFilters 数组中的所有数组都至少有一个值。但是,如果提供了一个空数组,那么在 $in 中找不到匹配项,据我所知,$in 需要至少存在一个值,但由于没有提供任何内容,因此什么也找不到。另一方面,$nin 可以正常工作(至少我愿意这么认为),因为它不会尝试排除任何值。

我想要实现的是,如果向 $in 提供了一个空数组,那么它会直接跳过该过滤器并且不查看它。在 mysql 中执行此操作时,如果提供了一个空数组,则将其忽略并返回每条记录作为结果。这就是我想要的 mongodb。

到目前为止,我尝试了几件事。

首先,我尝试将所有可能的值添加到空数组中,这不起作用有一个原因,并非所有文档都有标签、流派和人口统计数据。所以最终将近 90% 的文档没有包含在结果中。

我尝试的第二件事是在连接选项中启用 ignoreUndefined 选项。然后在创建查询时检查长度是否为 0,如果是,则将 undefined 传递给 $in。这不起作用。

我尝试的第三件事是遍历所有可能的情况,其中某个数组为空,由于显而易见的原因,这很糟糕,所以我抓了它。

我尝试的第四件事是创建一个函数来构建各种动态查询,由于可能性的数量,这也变得非常混乱。

我尝试的第五件事也是最后一件事是使用 $or 并认为会以某种方式跳过空结果。

【问题讨论】:

    标签: javascript arrays typescript mongodb mongoose


    【解决方案1】:

    为了更好地使用 TypeScript,您可以将 Nenad 的方法与 ES6 方法一起使用

    const filters = Object.fromEntries(
      Object.entries(body).flatMap(([filterType, filters]) =>
        Object.entries(filters)
          .filter(([, values]) => values.length)
          .map(([key, value]) =>
            filterType === "includeFilters"
              ? [key, { $elemMatch: { $in: [...value] } }]
              : [key, { $elemMatch: { $nin: [...value] } }]
          )
      )
    );
    

    【讨论】:

      【解决方案2】:

      最简单的解决方案是动态创建整个过滤器。它还将简化您的查询。你可以这样做:

      let elem_match = {
        tags: {},
        genres: {},
        demographics: {}
      }
      
      for (let key in data.includeFilters){
        if(data.includeFilters[key].length) elem_match[key]["$in"] = data.includeFilters[key];
      }
      
      for (let key in data.excludeFilters){
        if(data.excludeFilters[key].length) elem_match[key]["$nin"] = data.excludeFilters[key];
      }
      
      let filter = {};
      for (let key in elem_match){
        if(Object.keys(elem_match[key]).length !== 0) filter[key] = { $elemMatch: elem_match[key]}
      }
      
      const results = await this._manga.find(filter, { al_id: 1 });
      

      这是一个用于测试的sn-p:

      let includeFilters = {
        genres: ['1', '2'],
        tags: ['1', '2'],
        demographics: ['1', '2']
      };
      
      let excludeFilters = {
        genres: ['1', '2'],
        tags: ['1', '2'],
        demographics: ['1', '2']
      };
      
      let elem_match = {
          tags: {},
        genres: {},
        demographics: {}
      }
      
      for (let key in includeFilters) {
          if(includeFilters[key].length) elem_match[key]["$in"] = includeFilters[key];
      }
      
      for (let key in excludeFilters) {
          if(excludeFilters[key].length) elem_match[key]["$nin"] = excludeFilters[key];
      }
      
      let filter = {};
      for (let key in elem_match) {
          if(Object.keys(elem_match[key]).length !== 0) filter[key] = { $elemMatch: elem_match[key]}
      }
      
      console.log('Filter: ', filter)

      【讨论】:

      • 这将无法正常工作,它需要更多的边缘案例。正如我所说,不提供一个阵列的可能性不止一种。如果我要扩展它,我将得到与对整个查询进行硬编码的相同结果,并且仅针对这个查询就可以减少到几十行
      • 我更新了答案。你能检查一下吗?这将动态创建所需的查询对象,因此您可以真正轻松地涵盖更多用例。如果您同时提供两个数组、只提供一个或不提供,这应该可以工作。
      • 它不起作用,它仅在我在每个数组中提供至少一个元素时才起作用。我认为这是因为无论是否通过数组,都提供了 $elemMatch 。也许这会造成问题?
      • 是的,你可能是对的。我重构了代码,因此现在将动态创建整个过滤器,而不仅仅是 match_elem 对象。现在可以试试了吗?
      • 我认为现在这似乎可行。将不得不手动检查一些数据并检查其是否准确。但这只是归结为我最终想要避免的。这是硬编码。更不用说打字稿只是因为所有隐含的任何类型而对我尖叫。真的没有其他办法了吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-14
      • 2012-03-30
      • 2021-02-05
      • 1970-01-01
      • 2021-02-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多