Knex.js INNER JOIN 结果的 DISTINCT

Posted

技术标签:

【中文标题】Knex.js INNER JOIN 结果的 DISTINCT【英文标题】:DISTINCT on results of a knex.js INNER JOIN 【发布时间】:2020-01-22 18:57:05 【问题描述】:

我有两张桌子,metadataview_eventsmetadataview_events 都有 config_idconfig_type 列。我正在尝试为给定的用户电子邮件选择所有view_events,由config_idconfig_type 区分,由timestamp, desc 排序,并且仅限于最近的10 个。以下 knex.js 代码不起作用,但希望能表达我想要实现的目标:

return dbClient<AuthenticatedUserIndexRow>(METADATA_TABLE_NAME)
    .select([
      `$METADATA_TABLE_NAME.$METADATA_COLUMNS.CONFIG_ID`,
      `$METADATA_TABLE_NAME.$METADATA_COLUMNS.CONFIG_TYPE`,
      `$METADATA_TABLE_NAME.$METADATA_COLUMNS.DESCRIPTION`,
      `$VIEW_EVENTS_TABLE_NAME.$VIEW_EVENTS_COLUMNS.TIMESTAMP`,
    ])
    .innerJoin<AuthenticatedUserIndexRow>(VIEW_EVENTS_TABLE_NAME, function innerJoinOnViewEvents() 
      this.on(
        `$METADATA_TABLE_NAME.$METADATA_COLUMNS.STORAGE_ID`,
        '=',
        `$VIEW_EVENTS_TABLE_NAME.$VIEW_EVENTS_COLUMNS.CONFIG_STORAGE_ID`,
      )
        .andOn(
          `$VIEW_EVENTS_TABLE_NAME.$VIEW_EVENTS_COLUMNS.USER_EMAIL`,
          '=',
          rawSql('?', [authUserEmail]),
        )
        .andOn(`$METADATA_TABLE_NAME.$METADATA_COLUMNS.DELETED`, '=', rawSql('?', [false]));
    )
    .distinct([
      `$METADATA_TABLE_NAME.$METADATA_COLUMNS.CONFIG_TYPE`,
      `$METADATA_TABLE_NAME.$METADATA_COLUMNS.CONFIG_ID`,
    ])
    .limit(EVENT_LIMIT)
    .orderBy(VIEW_EVENTS_COLUMNS.TIMESTAMP, 'desc');

例如,给定以下表格:

view_events
+-------------+-----------+--------------------------+----------------------+
| config_type | config_id |        timestamp         |        email         |
+-------------+-----------+--------------------------+----------------------+
| a           | foo       | 2020-01-23T03:08:14.618Z | john.smith@gmail.com |
| a           | foo       | 2020-01-23T03:08:14.500Z | jane.doe@gmail.com   |
| a           | foo       | 2020-01-23T03:08:13.618Z | john.smith@gmail.com |
| a           | bar       | 2020-01-23T03:08:12.618Z | john.smith@gmail.com |
| a           | foo       | 2020-01-23T03:08:11.618Z | john.smith@gmail.com |
| b           | foo       | 2020-01-23T03:08:10.618Z | john.smith@gmail.com |
| a           | baz       | 2020-01-23T03:08:09.618Z | john.smith@gmail.com |
| a           | foo       | 2020-01-23T03:08:08.618Z | john.smith@gmail.com |
+-------------+-----------+--------------------------+----------------------+

metadata
+-------------+-----------+---------------------------+
| config_type | config_id |        description        |
+-------------+-----------+---------------------------+
| a           | foo       | Type a config with id foo |
| a           | bar       | Type a config with id bar |
| b           | foo       | Type b config with id foo |
| a           | baz       | Type a config with id baz |
+-------------+-----------+---------------------------+

我正在尝试获取以下输出(给定 authUserEmailjohn.smith@gmail.com):

+-------------+-----------+---------------------------+
| config_type | config_id |        description        |
+-------------+-----------+---------------------------+
| a           | foo       | Type a config with id foo |
| a           | bar       | Type a config with id foo |
| b           | foo       | Type b config with id foo |
| a           | baz       | Type a config with id baz |
+-------------+-----------+---------------------------+

我不是 SQL 专家,但我通常知道在这里一起使用 SELECTDISTINCT 是行不通的。正确的做法是什么?

【问题讨论】:

您的意思是您只希望每个配置 ID/类型返回一行吗?也许提供最小的输入/输出示例只是为了阐明要求? 正确。我会将其添加到原始问题中。 听起来你想要groupBy,在这种情况下,你需要聚合选择列表中的剩余列。可能只获取最新的(最大)时间戳就可以了…… 不完全确定我是否遵循。提供的输入/输出是否有助于澄清我的问题? 我想是的,会玩得很开心的...... 【参考方案1】:

以下内容大致适合您吗?我确实使用了 as,因此我们可以获取 10 个最新配置 (max(timestamp)..group by config),然后在最终投影中删除时间戳列。请注意,最终记录可能不会以确切的时间戳顺序出现,因为您不希望在最终输出中出现时间戳,但它们将是最近的 10 个。我没有添加 DELETED 列,但假设您将根据问题中的代码重新添加它。

knex.with('ordered_items', (qb) =>
          qb.table('metadata')
          .innerJoin('view_events', function() 
              this.on('metadata.config_id', '=', 'view_events.config_id')
                  .andOn('metadata.config_type', '=', 'view_events.config_type')
          )
          .where('view_events.email': 'john.smith@gmail.com')
          .select(['metadata.config_type', 'metadata.config_id',
                   'metadata.description'])
          .max('view_events.timestamp', as: 'max_ts')
          .groupBy(['metadata.config_id', 'metadata.config_type', 'metadata.description'])
          .orderBy('max_ts', 'desc')
          .limit(10))
    .table('ordered_items')
    .select(['config_type', 'config_id', 'description'])

我的输入输出:

sqlite> select * from metadata;
a|foo|Type a config with id foo
a|bar|Type a config with id bar
b|foo|Type b config with id foo
a|baz|Type a config with id baz
sqlite> select * from view_events;
a|foo|2020-01-23T03:08:14.618Z|john.smith@gmail.com
a|foo|2020-01-23T03:08:14.500Z|jane.doe@gmail.com
a|foo|2020-01-23T03:08:13.618Z|john.smith@gmail.com
a|bar|2020-01-23T03:08:12.618Z|john.smith@gmail.com
a|foo|2020-01-23T03:08:11.618Z|john.smith@gmail.com
b|foo|2020-01-23T03:08:10.618Z|john.smith@gmail.com
a|baz|2020-01-23T03:08:09.618Z|john.smith@gmail.com
a|foo|2020-01-23T03:08:08.618Z|john.smith@gmail.com

[  config_type: 'a',
    config_id: 'foo',
    description: 'Type a config with id foo' ,
   config_type: 'a',
    config_id: 'bar',
    description: 'Type a config with id bar' ,
   config_type: 'b',
    config_id: 'foo',
    description: 'Type b config with id foo' ,
   config_type: 'a',
    config_id: 'baz',
    description: 'Type a config with id baz'  ]

【讨论】:

"with \"ordered_events\" as (select max(\"view_events\".\"timestamp\") from \"metadata\" inner join \"view_events\" on \"metadata\ ".\"storage_id\" = \"view_events\".\"config_storage_id\" where \"view_events\".\"user\" = $1 and \"metadata\".\"deleted\" = $2 group by \ "metadata\".\"config_id\", \"metadata\".\"config_type\", \"metadata\".\"description\" order by \"view_events\".\"timestamp\" desc 限制 $3 ) select \"config_id\", \"config_type\", \"description\" from \"ordered_events\" - 列 \"view_events.timestamp\" 必须出现在 GROUP BY 子句中或用于聚合函数" 我不知道这是否重要,但我使用的是 Postgres,而您的解决方案看起来是针对 sqlite 运行的。 Mm sqlite3 相当宽容。我已经尝试根据错误进行修复,手指交叉它可以工作,但如果不是我可以翻转到 postgres 并在那里修复它。告诉我。 做到了!标记为已解决。不过很好奇,如果添加“as”子句来修复初始错误呢? 太棒了——谢谢。与其说是修复的as,不如说是通过max(timestamp) 而不是timestamp 排序,后者是无效的sql,但sqlite3 允许通过。为了按max(timestamp) 排序,添加别名似乎更容易,因此添加了as。希望这很清楚。

以上是关于Knex.js INNER JOIN 结果的 DISTINCT的主要内容,如果未能解决你的问题,请参考以下文章

SQL中inner join,outer join和cross join的区别

Knex.js:加入 'select' 和 'where' 子句

INNER JOIN 改变 SUM 结果

Inner Join, Left Outer Join和Association的区别

inner join(inner可省) 与 left join 之间的区别

mysql的unionleft join right join inner join和视图学习