如何在连接其他表时获得 Lucid 模型的分页的正确计数

How to get the correct count for a Lucid Model's Paginate when joining with additional tables

我有 2 个 Lucid 模型:AdCampaign,它们使用 Many:Many 关系关联。他们有一个枢轴 table 来管理具有附加信息的关系,所以我的 table 结构如下:

我正在尝试使用 Ad 模型的 query 函数获取 paginate 查询的结果,但除了 Ad 模型的字段之外,我还想从相关的 Campaign 模型的支点中获取 spendsentclicksleadsftds 的总和。

我想出了下面的代码,其中 returns 集合中的正确信息,但是 returns count

的值不正确
const Ad = use('App/Models/Ad');
const query = Ad.query()
                .leftJoin('campaign_ads', 'ads.id', 'campaign_ads.ad_id')
                .select('ads.*')
                .sum('campaign_ads.spend as spend')
                .sum('campaign_ads.sent as sent')
                .sum('campaign_ads.clicks as clicks')
                .sum('campaign_ads.leads as leads')
                .sum('campaign_ads.ftds as ftds')
                .groupBy('ads.id')
                .paginate()

我认为这与 paginate 函数如何重写或执行查询有关,但我不知道如何解决它。

因此,正如我之前在笔记中指出的那样,我遇到的问题是由 Lucid 的查询构建器处理 paginate 函数的方式引起的,因此我被迫“自己动手”。这是我想出的:

paginate (query, page = 1, perPage = 20) {
  // Types of statements which are going to filter from the count query
  const excludeAttrFromCount = ['order', 'columns', 'limit', 'offset', 'group']
  // Clone the original query which we are paginating
  const countByQuery = query.clone();
  // Case Page and Per Page as Numbers
  page = Number(page)
  perPage = Number(perPage)
  // Filter the statments from the array above so we have a query which can run cleanly for counting
  countByQuery.query._statements = _.filter(countByQuery.query._statements, (statement) => {
    return excludeAttrFromCount.indexOf(statement.grouping) < 0
  })
  // Since in my case, i'm working with a left join, i'm going to ensure that i'm only counting the unique models
  countByQuery.countDistinct([this.#model.table, 'id'].join('.'));
  const counts = await countByQuery.first()
  const total = parseInt(counts.count);
  let data;
  // If we get a count of 0, there's no point in delaying processing for an additional DB query
  if (0 === total) {
    data = [];
  }
  // Use the query's native `fetch` method, which already creates instances of the models and eager loads any relevant data
  else {
    const {rows} = await query.forPage(page, perPage).fetch();
    data = rows;
  }
  // Create the results object that you would normally get
  const result = {
    total: total,
    perPage: perPage,
    page: page,
    lastPage: Math.ceil(total / perPage),
    data: data
  }
  // Create the meta data which we will pass to the pagination hook + serializer
  const pages = _.omit(result, ['data'])

  // this.#model references the Model (not the instance). I reference it like this because this function is part of a larger class
  if (this.#model.$hooks) {
    await this.#model.$hooks.after.exec('paginate', data, pages)
  }
  // Create and return the serialized versions
  const Serializer = this.#model.resolveSerializer()
  return new Serializer(data, pages);
}

当我在查询中检测到 group by 时,我只使用这个版本的分页,它非常接近 Lucid 自己的 paginate 功能,并且 returns 相同的反馈。虽然它不是 100% 的直接解决方案,但足以满足我的需求

下面是一些基于答案的用法示例:

const Ad = use('App/Models/Ad');
const query = Ad.query()
                .leftJoin('campaign_ads', 'ads.id', 'campaign_ads.ad_id')
                .select('ads.*')
                .sum('campaign_ads.spend as spend')
                .sum('campaign_ads.sent as sent')
                .sum('campaign_ads.clicks as clicks')
                .sum('campaign_ads.leads as leads')
                .sum('campaign_ads.ftds as ftds')
                .groupBy('ads.id')
const paginate = async (query, page = 1, perPage = 20) {
  // Types of statements which are going to filter from the count query
  const excludeAttrFromCount = ['order', 'columns', 'limit', 'offset', 'group']
  // Clone the original query which we are paginating
  const countByQuery = query.clone();
  // Case Page and Per Page as Numbers
  page = Number(page)
  perPage = Number(perPage)
  // Filter the statments from the array above so we have a query which can run cleanly for counting
  countByQuery.query._statements = _.filter(countByQuery.query._statements, (statement) => {
    return excludeAttrFromCount.indexOf(statement.grouping) < 0
  })
  // Since in my case, i'm working with a left join, i'm going to ensure that i'm only counting the unique models
  countByQuery.countDistinct([Ad.table, 'id'].join('.'));
  const counts = await countByQuery.first()
  const total = parseInt(counts.count);
  let data;
  // If we get a count of 0, there's no point in delaying processing for an additional DB query
  if (0 === total) {
    data = [];
  }
  // Use the query's native `fetch` method, which already creates instances of the models and eager loads any relevant data
  else {
    const {rows} = await query.forPage(page, perPage).fetch();
    data = rows;
  }
  // Create the results object that you would normally get
  const result = {
    total: total,
    perPage: perPage,
    page: page,
    lastPage: Math.ceil(total / perPage),
    data: data
  }
  // Create the meta data which we will pass to the pagination hook + serializer
  const pages = _.omit(result, ['data'])
  
  if (Ad.$hooks) {
    await Ad.$hooks.after.exec('paginate', data, pages)
  }
  // Create and return the serialized versions
  const Serializer = Ad.resolveSerializer()
  return new Serializer(data, pages);
}
paginate(query, 1, 20)
    .then(results => {
        // do whatever you want to do with the results here
    })
    .catch(error => {
        // do something with the error here
    })