【问题标题】:How can I query the count of the intersection of IDs from two different join tables如何从两个不同的连接表中查询 ID 交集的计数
【发布时间】:2019-07-08 22:40:12
【问题描述】:

我有几个模型。用户、曲目、播放列表、用户跟踪和播放列表跟踪。一个用户可以有许多曲目,播放列表也可以。我想查询与用户的 UserTracks 匹配的曲目最多的播放列表。

这是我现在的做法:

user_tracks = # get all track_ids for a user in the users_tracks join table

playlists =
  from(p in Playlist,
    join: pt in PlaylistTrack,
    where: pt.playlist_id == p.id,
    having:
      fragment(
        "cardinality(array(
              select unnest(array_agg(?)::varchar[])
              intersect
              select unnest(?::varchar[])
            )) > 0",
        pt.track_id,
        ^user_tracks
      ),
    group_by: p.id,
    order_by:
      fragment(
        "cardinality(array(
              select unnest(array_agg(?)::varchar[])
              intersect
              select unnest(?::varchar[])
            )) DESC",
        pt.track_id,
        ^user_tracks
      ),
    limit: ^limit,
    select: p
  )

这可行,但速度很慢,有没有更好的方法来编写这个查询?

简而言之:“如果我们有表users,在连接表user_tracks 中有许多关联,则链接到tracks。按照他们与表@ 的关联数量对表playlists 中的项目进行排序987654327@ 与users 在表中的关联 tracks"

【问题讨论】:

  • 示例数据和结果可以更清楚地说明您的问题。
  • 看看这个:sqlfiddle.com/#!17/994fc/3/0。您可以使用以前获取的查询来操作多个连接

标签: sql elixir ecto


【解决方案1】:

我会先创建一个最佳的原始 ​​SQL 查询,然后看看是否/如何将其转换为 Ecto。

鉴于此数据:

CREATE TABLE playlist_tracks (playlist_id integer, track_id integer);
CREATE TABLE playlists (id integer, title character varying);
CREATE TABLE user_tracks (user_id integer, track_id integer);

INSERT INTO user_tracks VALUES (1, 2), (1, 1), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (1, 10);
INSERT INTO playlist_tracks VALUES (1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (2, 4), (2, 5), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (4, 9), (4, 10), (4, 11), (5, 10), (5, 11), (5, 12);
INSERT INTO playlists VALUES (1, 'Classic Rock''s Greatest Hits'),
                             (2, 'Kings & Queens of Blues Rock'),
                             (3, 'Mojito Lounge'),
                             (4, 'Dark Electro Lounging'),
                             (5, 'Atmospheric Chilled Electronica');

我得到的查询是这样的:

  SELECT p.id,
         p.title
    FROM playlists p
    JOIN playlist_tracks pt on p.id = pt.playlist_id
    JOIN user_tracks ut on pt.track_id = ut.track_id
   WHERE ut.user_id = 1
GROUP BY p.id,
         p.title
ORDER BY count(1) DESC;

注意这里不需要加入users表。

在查询上运行 explain analyse ... 会返回以下内容:

                                                                  QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=150.61..151.11 rows=200 width=44) (actual time=0.131..0.131 rows=5 loops=1)
   Sort Key: (count(1)) DESC
   Sort Method: quicksort  Memory: 25kB
   ->  HashAggregate  (cost=140.97..142.97 rows=200 width=44) (actual time=0.119..0.121 rows=5 loops=1)
         Group Key: p.id, p.title
         ->  Hash Join  (cost=82.25..135.05 rows=789 width=36) (actual time=0.104..0.108 rows=15 loops=1)
               Hash Cond: (p.id = pt.playlist_id)
               ->  Seq Scan on playlists p  (cost=0.00..22.70 rows=1270 width=36) (actual time=0.011..0.012 rows=5 loops=1)
               ->  Hash  (cost=80.70..80.70 rows=124 width=4) (actual time=0.073..0.073 rows=15 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 9kB
                     ->  Hash Join  (cost=38.39..80.70 rows=124 width=4) (actual time=0.042..0.066 rows=15 loops=1)
                           Hash Cond: (pt.track_id = ut.track_id)
                           ->  Seq Scan on playlist_tracks pt  (cost=0.00..32.60 rows=2260 width=8) (actual time=0.005..0.008 rows=18 loops=1)
                           ->  Hash  (cost=38.25..38.25 rows=11 width=4) (actual time=0.020..0.020 rows=10 loops=1)
                                 Buckets: 1024  Batches: 1  Memory Usage: 9kB
                                 ->  Seq Scan on user_tracks ut  (cost=0.00..38.25 rows=11 width=4) (actual time=0.007..0.011 rows=10 loops=1)
                                       Filter: (user_id = 1)
 Planning Time: 0.215 ms
 Execution Time: 0.658 ms
(19 rows)

拥有以下索引将显着提高查询性能:

create index ut_user_id on user_tracks (user_id);
create index ut_track_id on user_tracks (track_id);
create index pt_track_id on playlist_tracks (track_id);
create index pt_playlist_id on playlist_tracks (playlist_id);
create index p_id on playlists (id);

改进计划:

                                                                 QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=3.26..3.27 rows=1 width=44) (actual time=0.103..0.103 rows=5 loops=1)
   Sort Key: (count(1)) DESC
   Sort Method: quicksort  Memory: 25kB
   ->  GroupAggregate  (cost=3.23..3.25 rows=1 width=44) (actual time=0.093..0.098 rows=5 loops=1)
         Group Key: p.id, p.title
         ->  Sort  (cost=3.23..3.24 rows=1 width=36) (actual time=0.087..0.089 rows=15 loops=1)
               Sort Key: p.id, p.title
               Sort Method: quicksort  Memory: 26kB
               ->  Nested Loop  (cost=1.27..3.22 rows=1 width=36) (actual time=0.043..0.075 rows=15 loops=1)
                     ->  Hash Join  (cost=1.14..2.39 rows=1 width=4) (actual time=0.037..0.045 rows=15 loops=1)
                           Hash Cond: (pt.track_id = ut.track_id)
                           ->  Seq Scan on playlist_tracks pt  (cost=0.00..1.18 rows=18 width=8) (actual time=0.007..0.010 rows=18 loops=1)
                           ->  Hash  (cost=1.12..1.12 rows=1 width=4) (actual time=0.016..0.016 rows=10 loops=1)
                                 Buckets: 1024  Batches: 1  Memory Usage: 9kB
                                 ->  Seq Scan on user_tracks ut  (cost=0.00..1.12 rows=1 width=4) (actual time=0.006..0.009 rows=10 loops=1)
                                       Filter: (user_id = 1)
                     ->  Index Scan using p_id on playlists p  (cost=0.13..0.82 rows=1 width=36) (actual time=0.001..0.001 rows=1 loops=15)
                           Index Cond: (id = pt.playlist_id)
 Planning Time: 0.505 ms
 Execution Time: 0.163 ms
(20 rows)

它返回以下结果:

 id |              title
----+---------------------------------
  3 | Mojito Lounge
  2 | Kings & Queens of Blues Rock
  1 | Classic Rock's Greatest Hits
  4 | Dark Electro Lounging
  5 | Atmospheric Chilled Electronica
(5 rows)

看起来像您所期望的那样吗?

如果是,以下是如何将其翻译为 Ecto:

import Ecto.Query

user_id = 1

query =
  from p in "playlists",
    select: {p.id, p.title},
    join: pt in "playlist_tracks", on: pt.playlist_id == p.id,
    join: ut in "user_tracks", on: ut.track_id == pt.track_id,
    where: ut.user_id == ^user_id,
    group_by: [p.id, p.title],
    order_by: count(1)

MyRepo.all(query)

您应该能够通过以下方式采用此示例:

  • 切换表名称引用,如 playlistsuser_tracks 用于模式,例如分别为PlaylistUser.Track(假设这是您的架构模块的调用方式),

  • select: {p.id, p.title} 更改为select: struct(p, [p.id, p.title]),如果需要的话,这将为您提供Playlist 结构而不是元组(虽然经过测试)。


一般来说,除非查询必须高度灵活(例如,取决于在运行时确定的许多参数)(通过使用 Ecto 动态构建它来实现),否则将查询保持在原始 SQL 中编写应该是完全可以的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-16
    • 2016-04-12
    • 2021-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多