我会先创建一个最佳的原始 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)
您应该能够通过以下方式采用此示例:
切换表名称引用,如 playlists 和 user_tracks 用于模式,例如分别为Playlist 和User.Track(假设这是您的架构模块的调用方式),
将select: {p.id, p.title} 更改为select: struct(p, [p.id, p.title]),如果需要的话,这将为您提供Playlist 结构而不是元组(虽然未经过测试)。
一般来说,除非查询必须高度灵活(例如,取决于在运行时确定的许多参数)(通过使用 Ecto 动态构建它来实现),否则将查询保持在原始 SQL 中编写应该是完全可以的。