WeatherNews.jl/src/db.jl

178 lines
4.6 KiB
Julia

using SQLite
using DataFrames
using Dates
"""
sql_fn(db)
Return a function that takes an SQL and optional bind parameters
and runs the query and returns the results as a DataFrame.
```julia
db = SQLite.DB("sql/wn.db")
sql = WeatherNews.DB.sql_fn(db)
res = sql("SELECT * FROM cancellation")
```
"""
function sql_fn(db)
function sql(query, params=[])
DBInterface.execute(db, query, params) |> DataFrame
end
end
"""
Find or create a caster by short name `n`.
"""
function find_or_create_caster(db, n)
sql_select = "SELECT * FROM caster WHERE n = ? LIMIT 1"
df = DBInterface.execute(db, sql_select, [n]) |> DataFrame
if df.id != Union{Missing,Int64}[]
return df
else
DBInterface.execute(db, "INSERT INTO caster (n) VALUES (?)", [n])
df = DBInterface.execute(db, sql_select, [n]) |> DataFrame
return df
end
end
"""
Find all casters
"""
function find_all_casters(db)
sql_select = "SELECT * FROM CASTER"
return DBInterface.execute(db, sql_select) |> DataFrame
end
"""
Find a segment by short name `n`.
"""
function find_segment(db, n)
return DBInterface.execute(db, "SELECT * FROM segment WHERE n = ? LIMIT 1", [n]) |> DataFrame
end
"""
Insert a new `row` into schedule if possible.
If a row with the same `jst` value already exists, it will silently fail on purpose.
"""
function insert_schedule(db, row)
# find caster
caster_id = if row[:caster] == ""
missing
else
caster = find_or_create_caster(db, row[:caster])
caster.id[1]
end
# find segment
segment = find_segment(db, row[:title])
# insert row
try
sql_insert = "INSERT INTO schedule (caster_id, segment_id, jst) VALUES (?, ?, ?)"
timestamp = Dates.format(row[:t], "Y-mm-ddTHH:MM:SSz")
#@info "before insert" caster_id, segment.id[1] timestamp
ri = DBInterface.execute(db, sql_insert, [caster_id, segment.id[1], timestamp])
return ri
catch e
@debug e
return false
end
end
"""
Return a row from the schedule based on its `jst` time.
"""
function find_schedule_by_jst(db, jst)
df = DBInterface.execute(db, "SELECT * FROM schedule WHERE jst = ? LIMIT 1", [jst]) |> DataFrame
if size(df, 1) == 0
return missing
else
return eachrow(df)[1]
end
end
"""
Update a schedule row with its `video_id` and `view_count`.
This function is for WN videos only, and it should skip over au PAY videos.
"""
function update_schedule_with_video(db, jst, video_id, view_count)
try
sql_update = """
UPDATE schedule SET video_id = ?, view_count = ?
WHERE jst = ? AND segment_id NOT IN (8);
"""
return DBInterface.execute(db, sql_update, [video_id, view_count, jst])
catch e
@debug e
return e
end
end
"""
Cancel a schedule, and replace the entry with a new caster (or none at all).
If there's no new caster to fill in, pass in `missing` as the `new_caster_id`.
"""
function cancel_schedule(db, schedule_id, new_caster_id)
sql_insert = "INSERT INTO cancellation SELECT id, caster_id, segment_id, jst FROM schedule WHERE id = ?"
sql_update = "UPDATE schedule SET caster_id = ? WHERE id = ?"
try
return DBInterface.transaction(db) do
DBInterface.execute(db, sql_insert, [schedule_id])
DBInterface.execute(db, sql_update, [new_caster_id, schedule_id])
end
catch e
@debug e
return e
end
end
"""
Load the entire schedule into a DataFrame
"""
function load_schedule_joined(db)
sql_select = """
SELECT s.id AS id,
seg.n AS title,
c.n AS caster,
s.video_id AS video_id,
s.view_count AS view_count,
m.val AS mscale,
s.jst AS jst
FROM schedule s
JOIN segment seg ON seg.id = s.segment_id
LEFT JOIN caster c ON c.id = s.caster_id
LEFT JOIN mscale m ON m.jst = s.jst
ORDER BY jst DESC
"""
return DBInterface.execute(db, sql_select) |> DataFrame
end
#=
using WeatherNews
using WeatherNews: API, DB
using DataFrames
using DataFramesMeta
using SQLite
using Statistics
db = SQLite.DB("sql/wn.db")
s = DB.load_schedule_joined(db)
# Try finding the median instead of the average.
rs = @chain s begin
@subset(:title .!= "au PAY")
dropmissing(:view_count)
dropmissing(:caster)
@select(:caster, :view_count)
@groupby(:caster)
@combine begin
:median_views = median(:view_count)
:average_views = mean(:view_count)
end
sort(:median_views, rev=true)
end
=#