Addd peak query and tie detection for rank
Also begins friends query for songs
This commit is contained in:
parent
f9718f3a37
commit
b0727e77e1
7 changed files with 135 additions and 44 deletions
|
|
@ -58,5 +58,8 @@ pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View {
|
||||||
const ratings = try queries.entityQueryResult(request, queries.loadQuery(.song, .get_ratings), .{id_int});
|
const ratings = try queries.entityQueryResult(request, queries.loadQuery(.song, .get_ratings), .{id_int});
|
||||||
try root.put("reviews", ratings);
|
try root.put("reviews", ratings);
|
||||||
|
|
||||||
|
const peak = try queries.entityQueryResult(request, comptime queries.loadQuery(.album, .peak), .{id_int});
|
||||||
|
try root.put("peak", peak);
|
||||||
|
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,12 @@
|
||||||
|
|
||||||
<div style="display:flex;flex-direction:row;justify-content:space-evenly">
|
<div style="display:flex;flex-direction:row;justify-content:space-evenly">
|
||||||
<div style="display:flex;flex-direction:column;align-self:left">
|
<div style="display:flex;flex-direction:column;align-self:left">
|
||||||
|
@if ($.album.is_tie)
|
||||||
|
<div>{{.album.scrobbles}} scrobbles ({{.album.rank}} place, tied)</div>
|
||||||
|
@else
|
||||||
<div>{{.album.scrobbles}} scrobbles ({{.album.rank}} place)</div>
|
<div>{{.album.scrobbles}} scrobbles ({{.album.rank}} place)</div>
|
||||||
|
@end
|
||||||
|
<div>All-time peak: {{.peak.rank}} ({{.peak.date}})</div>
|
||||||
<div>{{.album.song_num}} songs</div>
|
<div>{{.album.song_num}} songs</div>
|
||||||
@partial partials/firstlast_listens(firstlast: .firstlast)
|
@partial partials/firstlast_listens(firstlast: .firstlast)
|
||||||
<h3>Yearly Performance</h3>
|
<h3>Yearly Performance</h3>
|
||||||
|
|
|
||||||
|
|
@ -57,5 +57,8 @@ pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View {
|
||||||
const timescale = try queries.entityQueryResult(request, queries.loadQuery(.artist, .timescale), .{id_int});
|
const timescale = try queries.entityQueryResult(request, queries.loadQuery(.artist, .timescale), .{id_int});
|
||||||
try root.put("yearly", timescale);
|
try root.put("yearly", timescale);
|
||||||
|
|
||||||
|
const peak = try queries.entityQueryResult(request, comptime queries.loadQuery(.artist, .peak), .{id_int});
|
||||||
|
try root.put("peak", peak);
|
||||||
|
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,12 @@
|
||||||
@partial partials/header
|
@partial partials/header
|
||||||
<h1>{{.artist.artist_name}}</h1>
|
<h1>{{.artist.artist_name}}</h1>
|
||||||
<div>
|
<div>
|
||||||
<div>{{.artist.scrobbles}} scrobbles ({{.artist.rank}} place)</div>
|
@if ($.artist.is_tie)
|
||||||
|
<div>{{.artist.scrobbles}} scrobbles ({{.artist.rank}} place, tied)</div>
|
||||||
|
@else
|
||||||
|
<div>{{.artist.scrobbles}} scrobbles ({{.artist.rank}} place)</div>
|
||||||
|
@end
|
||||||
|
<div>All-time peak: {{.peak.rank}} ({{.peak.date}})</div>
|
||||||
<div>{{.artist.song_num}} songs</div>
|
<div>{{.artist.song_num}} songs</div>
|
||||||
<div>{{.artist.album_num}} albums</div>
|
<div>{{.artist.album_num}} albums</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -63,5 +63,8 @@ pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View {
|
||||||
const ratings = try queries.entityQueryResult(request, comptime queries.loadQuery(.song, .get_ratings), .{id_int});
|
const ratings = try queries.entityQueryResult(request, comptime queries.loadQuery(.song, .get_ratings), .{id_int});
|
||||||
try root.put("reviews", ratings);
|
try root.put("reviews", ratings);
|
||||||
|
|
||||||
|
const peak = try queries.entityQueryResult(request, comptime queries.loadQuery(.song, .peak), .{id_int});
|
||||||
|
try root.put("peak", peak);
|
||||||
|
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
@else
|
@else
|
||||||
<div>{{.song.scrobbles}} scrobbles ({{.song.rank}} place)</div>
|
<div>{{.song.scrobbles}} scrobbles ({{.song.rank}} place)</div>
|
||||||
@end
|
@end
|
||||||
|
<div>All-time peak: {{.peak.rank}} ({{.peak.date}})</div>
|
||||||
@partial partials/firstlast_listens(firstlast: .firstlast)
|
@partial partials/firstlast_listens(firstlast: .firstlast)
|
||||||
<h3>Yearly Performance</h3>
|
<h3>Yearly Performance</h3>
|
||||||
@partial partials/timescale(range: .yearly)
|
@partial partials/timescale(range: .yearly)
|
||||||
|
|
|
||||||
157
src/queries.zig
157
src/queries.zig
|
|
@ -3,16 +3,43 @@ const jetzig = @import("jetzig");
|
||||||
const TableRow = @import("types.zig").TableRow;
|
const TableRow = @import("types.zig").TableRow;
|
||||||
const HyperlinkData = @import("types.zig").HyperlinkData;
|
const HyperlinkData = @import("types.zig").HyperlinkData;
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const ordinalFmt = @import("./ordinal_fmt.zig").ordinalFmt;
|
||||||
|
|
||||||
pub fn entityQueryResult(request: *jetzig.Request, query: GeneratedQuery, args: anytype) !*jetzig.Data.Value {
|
pub fn entityQueryResult(request: *jetzig.Request, comptime query: GeneratedQuery, args: anytype) !*jetzig.Data.Value {
|
||||||
//var result = try request.repo.executeSql(query.query, args);
|
//var result = try request.repo.executeSql(query.query, args);
|
||||||
//
|
//
|
||||||
|
var Data = jetzig.Data.init(request.allocator);
|
||||||
|
|
||||||
|
if (query.query_type == .peak) {
|
||||||
|
const id = switch (@TypeOf(args)) {
|
||||||
|
struct { i64 } => args[0],
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
var result = try request.repo.connection.?.postgresql.connection.queryOpts(query.query, .{}, .{ .allocator = request.allocator, .column_names = true });
|
||||||
|
var out: *jetzig.Data.Value = try Data.object();
|
||||||
|
var mapper = result.mapper(PeakResult, .{ .dupe = true, .allocator = request.allocator });
|
||||||
|
var rank = comptime (std.math.pow(u64, 2, 63) - 1);
|
||||||
|
var date: []const u8 = undefined;
|
||||||
|
var scrobbles = std.AutoArrayHashMap(i64, u32).init(request.allocator);
|
||||||
|
|
||||||
|
while (try mapper.next()) |scrobble| {
|
||||||
|
const res = try scrobbles.getOrPut(scrobble.eid);
|
||||||
|
if (res.found_existing) res.value_ptr.* += 1 else res.value_ptr.* = 1;
|
||||||
|
scrobbles.sort(PeakContext{ .keys = scrobbles.keys(), .vals = scrobbles.values(), .preferred = id });
|
||||||
|
const idx = scrobbles.getIndex(id);
|
||||||
|
if (idx != null and idx.? <= rank) {
|
||||||
|
if (idx.? < rank) rank = idx.?;
|
||||||
|
date = scrobble.datetime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try out.put("rank", ordinalFmt(request.allocator, @as(i64, @bitCast(rank + 1))));
|
||||||
|
try out.put("date", date);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
var result = try request.repo.connection.?.postgresql.connection.queryOpts(query.query, args, .{ .allocator = request.allocator, .column_names = true });
|
var result = try request.repo.connection.?.postgresql.connection.queryOpts(query.query, args, .{ .allocator = request.allocator, .column_names = true });
|
||||||
|
|
||||||
defer result.deinit();
|
defer result.deinit();
|
||||||
|
|
||||||
var Data = jetzig.Data.init(request.allocator);
|
|
||||||
|
|
||||||
var artist_list = std.ArrayList(HyperlinkData).init(request.allocator);
|
var artist_list = std.ArrayList(HyperlinkData).init(request.allocator);
|
||||||
|
|
||||||
if (query.query_type == .entity_info) {
|
if (query.query_type == .entity_info) {
|
||||||
|
|
@ -51,7 +78,22 @@ pub fn entityQueryResult(request: *jetzig.Request, query: GeneratedQuery, args:
|
||||||
}
|
}
|
||||||
|
|
||||||
const EntityType = enum { scrobble, song, album, artist };
|
const EntityType = enum { scrobble, song, album, artist };
|
||||||
const QueryTypeEnum = enum { firstlast, timescale, entities, get_songs, get_albums, get_scrobbles, appears, entity_info, datestreak, entities_by_name, get_ratings };
|
const QueryTypeEnum = enum { firstlast, timescale, entities, get_songs, get_albums, get_scrobbles, appears, entity_info, datestreak, entities_by_name, get_ratings, friends, peak };
|
||||||
|
|
||||||
|
const PeakResult = struct {
|
||||||
|
eid: i64,
|
||||||
|
datetime: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PeakContext = struct {
|
||||||
|
keys: []i64,
|
||||||
|
vals: []u32,
|
||||||
|
preferred: i64,
|
||||||
|
pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
|
||||||
|
if (ctx.vals[a_index] == ctx.vals[b_index] and ctx.keys[a_index] == ctx.preferred) return true;
|
||||||
|
return ctx.vals[a_index] > ctx.vals[b_index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const GeneratedQuery = struct {
|
const GeneratedQuery = struct {
|
||||||
entity: EntityType,
|
entity: EntityType,
|
||||||
|
|
@ -84,9 +126,10 @@ const EntityInfoResult = struct {
|
||||||
rank: []const u8,
|
rank: []const u8,
|
||||||
song_num: ?i64 = null,
|
song_num: ?i64 = null,
|
||||||
album_num: ?i64 = null,
|
album_num: ?i64 = null,
|
||||||
|
is_tie: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn loadQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQuery {
|
pub fn loadQuery(comptime entity: EntityType, comptime query_type: QueryTypeEnum) GeneratedQuery {
|
||||||
return GeneratedQuery{
|
return GeneratedQuery{
|
||||||
.entity = entity,
|
.entity = entity,
|
||||||
.query_type = query_type,
|
.query_type = query_type,
|
||||||
|
|
@ -344,7 +387,7 @@ pub fn loadQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQuery {
|
||||||
},
|
},
|
||||||
|
|
||||||
.entity_info => switch (entity) {
|
.entity_info => switch (entity) {
|
||||||
.scrobble => unreachable,
|
.scrobble => @compileError("Cannot specify scrobble for entity_info"),
|
||||||
.song =>
|
.song =>
|
||||||
\\WITH ranked AS (
|
\\WITH ranked AS (
|
||||||
\\SELECT songs.name AS song_name, COUNT(songs.id) AS scrobbles, RANK() OVER ( ORDER BY COUNT(songs.id) DESC) AS rank, songs.id AS song_id
|
\\SELECT songs.name AS song_name, COUNT(songs.id) AS scrobbles, RANK() OVER ( ORDER BY COUNT(songs.id) DESC) AS rank, songs.id AS song_id
|
||||||
|
|
@ -359,30 +402,34 @@ pub fn loadQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQuery {
|
||||||
\\FROM ranked) WHERE song_id = $1;
|
\\FROM ranked) WHERE song_id = $1;
|
||||||
,
|
,
|
||||||
.album =>
|
.album =>
|
||||||
\\SELECT album_name, t.album_id, artists.name AS artist_name, artists.id AS artist_id, song_num, scrobbles, rank FROM
|
\\WITH ranked AS (
|
||||||
\\(SELECT *, TO_CHAR(ROW_NUMBER() OVER (ORDER BY scrobbles DESC),'FM9999th') AS rank FROM
|
\\SELECT albums.name AS album_name, COUNT(albums.id) AS scrobbles, RANK() OVER ( ORDER BY COUNT(albums.id) DESC) AS rank, albums.id AS album_id, COUNT(DISTINCT songs.id) AS song_num
|
||||||
\\(SELECT albums.name AS album_name, albums.id AS album_id, COUNT(DISTINCT songs.id) AS song_num, COUNT(scrobbles) AS scrobbles
|
\\FROM scrobbles
|
||||||
\\FROM albumsongs
|
\\INNER JOIN albumsongs ON albumsongs.id = scrobbles.albumsong
|
||||||
\\INNER JOIN albums ON albumsongs.album_id = albums.id
|
|
||||||
\\INNER JOIN scrobbles ON scrobbles.albumsong = albumsongs.id
|
|
||||||
\\INNER JOIN songs ON songs.id = albumsongs.song_id
|
|
||||||
\\GROUP BY albums.id)) AS t
|
|
||||||
\\INNER JOIN artistalbums ON artistalbums.album_id = t.album_id
|
|
||||||
\\INNER JOIN artists ON artists.id = artistalbums.artist_id
|
|
||||||
\\WHERE t.album_id = $1
|
|
||||||
,
|
|
||||||
.artist =>
|
|
||||||
\\SELECT * FROM
|
|
||||||
\\(SELECT *, TO_CHAR(ROW_NUMBER() OVER (ORDER BY scrobbles DESC),'FM9999th') AS rank FROM
|
|
||||||
\\(SELECT artists.name AS artist_name, artists.id AS artist_id, COUNT(scrobbles) AS scrobbles, COUNT(DISTINCT albums.id) AS album_num, COUNT(DISTINCT songs.id) AS song_num
|
|
||||||
\\FROM albumsongsartists
|
|
||||||
\\INNER JOIN artists ON albumsongsartists.artist_id = artists.id
|
|
||||||
\\INNER JOIN albumsongs ON albumsongsartists.albumsong_id = albumsongs.id
|
|
||||||
\\INNER JOIN scrobbles ON scrobbles.albumsong = albumsongs.id
|
|
||||||
\\INNER JOIN albums ON albums.id = albumsongs.album_id
|
\\INNER JOIN albums ON albums.id = albumsongs.album_id
|
||||||
\\INNER JOIN songs ON songs.id = albumsongs.song_id
|
\\INNER JOIN songs ON songs.id = albumsongs.song_id
|
||||||
\\GROUP BY artists.id))
|
\\GROUP BY albums.id)
|
||||||
\\WHERE artist_id = $1
|
\\SELECT * FROM (SELECT album_name, scrobbles, TO_CHAR(rank,'FM9999th') AS rank, album_id, song_num,
|
||||||
|
\\( rank = lag(rank, 1, -1::bigint) OVER (ORDER BY rank)
|
||||||
|
\\OR rank = lead(rank, 1, -1::bigint) OVER (ORDER BY rank)
|
||||||
|
\\)AS is_tie
|
||||||
|
\\FROM ranked) WHERE album_id = $1;
|
||||||
|
,
|
||||||
|
.artist =>
|
||||||
|
\\WITH ranked AS (
|
||||||
|
\\SELECT artists.name AS artist_name, COUNT(artists.id) AS scrobbles, RANK() OVER ( ORDER BY COUNT(artists.id) DESC) AS rank, artists.id AS artist_id, COUNT(DISTINCT songs.id) AS song_num, COUNT(DISTINCT albums.id) AS album_num
|
||||||
|
\\FROM scrobbles
|
||||||
|
\\INNER JOIN albumsongs ON albumsongs.id = scrobbles.albumsong
|
||||||
|
\\INNER JOIN albumsongsartists ON albumsongsartists.albumsong_id = scrobbles.albumsong
|
||||||
|
\\INNER JOIN albums ON albums.id = albumsongs.album_id
|
||||||
|
\\INNER JOIN songs ON songs.id = albumsongs.song_id
|
||||||
|
\\INNER JOIN artists ON artists.id = albumsongsartists.artist_id
|
||||||
|
\\GROUP BY artists.id)
|
||||||
|
\\SELECT * FROM (SELECT artist_name, scrobbles, TO_CHAR(rank,'FM9999th') AS rank, artist_id, song_num, album_num,
|
||||||
|
\\( rank = lag(rank, 1, -1::bigint) OVER (ORDER BY rank)
|
||||||
|
\\OR rank = lead(rank, 1, -1::bigint) OVER (ORDER BY rank)
|
||||||
|
\\)AS is_tie
|
||||||
|
\\FROM ranked) WHERE artist_id = $1;
|
||||||
,
|
,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -457,21 +504,45 @@ pub fn loadQuery(entity: EntityType, query_type: QueryTypeEnum) GeneratedQuery {
|
||||||
,
|
,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
},
|
},
|
||||||
|
.friends => switch (entity) {
|
||||||
|
.song =>
|
||||||
|
\\SELECT name, COUNT(DISTINCT dt) AS days, COUNT(dt) AS plays
|
||||||
|
\\FROM (
|
||||||
|
\\ SELECT scrobbles.albumsong, songs.name, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD') AS dt
|
||||||
|
\\ FROM scrobbles
|
||||||
|
\\ INNER JOIN albumsongs ON albumsongs.id = scrobbles.albumsong
|
||||||
|
\\ INNER JOIN songs ON songs.id = albumsongs.song_id
|
||||||
|
\\ WHERE TO_CHAR(datetime,'YYYY-MM-DD') IN (
|
||||||
|
\\ SELECT DISTINCT TO_CHAR(datetime,'YYYY-MM-DD')
|
||||||
|
\\ FROM scrobbles
|
||||||
|
\\ WHERE albumsong = $1
|
||||||
|
\\ )
|
||||||
|
\\) GROUP BY albumsong, name ORDER BY days DESC, plays DESC, name ASC;
|
||||||
|
,
|
||||||
|
else => unreachable,
|
||||||
|
},
|
||||||
|
.peak => switch (entity) {
|
||||||
|
.scrobble => @compileError("Cannot specify scrobble for peak"),
|
||||||
|
.song =>
|
||||||
|
\\SELECT songs.id AS eid, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD') AS datetime FROM scrobbles
|
||||||
|
\\INNER JOIN albumsongs ON albumsongs.id = scrobbles.albumsong
|
||||||
|
\\INNER JOIN songs ON songs.id = albumsongs.song_id
|
||||||
|
\\ORDER BY datetime ASC;
|
||||||
|
,
|
||||||
|
.album =>
|
||||||
|
\\SELECT albums.id AS eid, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD') AS datetime FROM scrobbles
|
||||||
|
\\INNER JOIN albumsongs ON albumsongs.id = scrobbles.albumsong
|
||||||
|
\\INNER JOIN albums ON albums.id = albumsongs.album_id
|
||||||
|
\\ORDER BY datetime ASC;
|
||||||
|
,
|
||||||
|
.artist =>
|
||||||
|
\\SELECT artists.id AS eid, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD') AS datetime FROM scrobbles
|
||||||
|
\\INNER JOIN albumsongs ON albumsongs.id = scrobbles.albumsong
|
||||||
|
\\INNER JOIN albumsongsartists ON albumsongsartists.albumsong_id = albumsongs.id
|
||||||
|
\\INNER JOIN artists ON artists.id = albumsongsartists.artist_id
|
||||||
|
\\ORDER BY datetime ASC;
|
||||||
|
,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// I'm pretty sure this query will tell you the number of days a song was played on the same day as a specified song
|
|
||||||
// The output looked right at least. Can easily be changed into the number of times a song was played on the same day
|
|
||||||
// as a specified song by removing DISTINCT from the first subquery (which would increase the count if a song was
|
|
||||||
// played more than once in a day)
|
|
||||||
|
|
||||||
//SELECT COUNT(albumsong), name
|
|
||||||
//FROM (
|
|
||||||
// SELECT DISTINCT scrobbles.albumsong, songs.name, TO_CHAR(scrobbles.datetime, 'YYYY-MM-DD')
|
|
||||||
// FROM scrobbles
|
|
||||||
// INNER JOIN albumsongs ON albumsongs.id = scrobbles.albumsong
|
|
||||||
// INNER JOIN songs ON songs.id = albumsongs.song_id
|
|
||||||
// WHERE TO_CHAR(datetime,'YYYY-MM-DD') IN (
|
|
||||||
// SELECT DISTINCT TO_CHAR(datetime,'YYYY-MM-DD') FROM scrobbles WHERE albumsong = $1
|
|
||||||
//)) GROUP BY albumsong, name ORDER BY COUNT(albumsong) DESC, name ASC;
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue