使用值列表过滤多对多关系

Filter a many-to-many relation using a list of values

我正在为我的 RESTful API 使用 objection.js 和 knex.js。

在我的数据库中,我有三个 table:productscharacteristicsproduct_characteristics(加入 table 和名为 [=54 的额外列=]).

所以我要做的是获取所有具有相应特征值的产品。

在 GET 请求中,我接受了一个 characteristics 查询参数并解析它以获取我需要用来过滤我的产品的特征数组。

解析特征数组:

[{
   characteristicId: number,
   value: string
}, ...]

'products' table 字段:

[id, title, price, discount]

'特征的table字段:

[id, name]

'product_characteristics' 加入 table 个字段:

[productId, characteristicId, value]

目前我使用 objection 的 queryBuilderwithGraphFetch 方法以及 pagelimit 方法获取所有产品:

const query: any = ProductModel.query()
  .page(page - 1, limit)
  .orderBy(sortBy, order)
  .withGraphFetched({
    category: true,
    images: true,
    characteristics: true,
  });

const result = await query;
return {
  products: result.results,
  total: result.total,
};

Objection.js 提供了 withGraphJoined 方法,可以访问 queryBuilder 内部的相关实体,以便它可以用于根据 parentModel 过滤关系,但它不支持 pagelimit 方法。

因此,一种可能的解决方案是使用 knex.raw() 方法执行原始 SQL 查询。但是我花了一天时间尝试编写原始 SQL 查询来获取所有需要的数据。

理想的结果是一个特征过滤的产品数组,其中所有产品相关特征作为 JSON 响应参数。

const products = [
  {
    id: 1,
    title: 'Pipe 1',
    price: 3000,
    discount: 500,
    characteristics: [
      {
        id: 1,
        name: 'diameter',
        value: '120 mm',
      },
      {
        id: 2,
        name: 'color',
        value: 'black',
      },
    ],
  },
  {
    id: 2,
    title: 'Pipe 2',
    price: 5000,
    discount: 0,
    characteristics: [
      {
        id: 1,
        name: 'diameter',
        value: '120 mm',
      },
      {
        id: 2,
        name: 'color',
        value: 'blue',
      },
    ],
  },
];

我认为您要编写的 SQL 查询是

SELECT
  p.id,
  p.title,
  p.price,
  p.discount,
  c.name characteristicName,
  pc.value characteristicValue
FROM
  products p
  INNER JOIN product_characteristics pc ON pc.productId = p.id
  INNER JOIN characteristics c ON c.id = pc.characteristicId
WHERE
  (c.id = 1 AND pc.value = 'value1')
  OR (c.id = 2 AND pc.value = 'value2')
  OR (c.id = 3 AND pc.value = 'value3')

在knex中可以这样实现

knex({p: 'products'})
  .join({pc: 'product_characteristics'}, 'pc.productId', '=', 'p.id')
  .join({c: 'characteristics'}, 'c.id', '=', 'pc.characteristicId')
  .where(builder =>
    characteristics.forEach(item =>
      builder.orWhere(builder =>
        builder
          .andWhere('c.id', '=', item.characteristicId)
          .andWhere('pc.value', '=', item.value)
      )
    )
  )
  .select('p.id', 'p.title', 'p.price' /* etc */)

您可以轻松地向其中添加 .offset() and .limit() 以支持分页。它将翻译成这个 SQL:

select
  "p"."id",
  "p"."title",
  "p"."price"
from
  "products" as "p"
  inner join "product_characteristics" as "pc" on "pc"."productId" = "p"."id"
  inner join "characteristics" as "c" on "c"."id" = "pc"."characteristicId"
where
  (
    (
      "c"."id" = 1
      and "pc"."value" = 'value1'
    )
    or (
      "c"."id" = 2
      and "pc"."value" = 'value2'
    )
    or (
      "c"."id" = 3
      and "pc"."value" = 'value3'
    )
  )