CREATE SCHEMA IF NOT EXISTS best_practice_copyright_royalty;
ODS 层:原始数据接入
内容资产表
CREATE TABLE IF NOT EXISTS best_practice_copyright_royalty.doc_content_assets (
content_id STRING,
title STRING,
content_type STRING, -- movie / tv_series / music / documentary / podcast / short_video
rights_holder STRING,
release_year INT,
region STRING -- CN / GLOBAL
);
写入 15 条内容资产数据:
从本地 CSV 导入(推荐):
-- 第一步:通过 SQL PUT 将本地 CSV 文件上传到 User Volume
PUT '/path/to/doc_content_assets.csv' TO USER VOLUME FILE 'doc_content_assets.csv';
-- 第二步:从 User Volume COPY INTO 表
COPY INTO best_practice_copyright_royalty.doc_content_assets
FROM USER VOLUME
USING csv
OPTIONS('header'='true', 'sep'=',', 'nullValue'='')
FILES ('doc_content_assets.csv');
-- 第一步:通过 SQL PUT 将本地 CSV 文件上传到 User Volume
PUT '/path/to/doc_license_contracts.csv' TO USER VOLUME FILE 'doc_license_contracts.csv';
-- 第二步:从 User Volume COPY INTO 表
COPY INTO best_practice_copyright_royalty.doc_license_contracts
FROM USER VOLUME
USING csv
OPTIONS('header'='true', 'sep'=',', 'nullValue'='')
FILES ('doc_license_contracts.csv');
也可直接内联插入小批量测试数据(不需要 CSV 文件):
INSERT INTO best_practice_copyright_royalty.doc_license_contracts VALUES
('CTR001','C001','PLT_A',CAST('2025-01-01' AS DATE),CAST('2025-12-31' AS DATE),'revenue_share',0.30,50000,'active'),
('CTR002','C001','PLT_B',CAST('2025-03-01' AS DATE),CAST('2025-08-31' AS DATE),'revenue_share',0.25,30000,'active'),
('CTR003','C002','PLT_A',CAST('2025-01-01' AS DATE),CAST('2025-06-30' AS DATE),'per_play',0.005,10000,'expired'),
('CTR004','C002','PLT_C',CAST('2025-04-01' AS DATE),CAST('2025-12-31' AS DATE),'revenue_share',0.20,20000,'active'),
('CTR005','C003','PLT_A',CAST('2025-01-01' AS DATE),CAST('2025-12-31' AS DATE),'per_play',0.003,5000,'active'),
('CTR006','C003','PLT_B',CAST('2025-02-01' AS DATE),CAST('2026-01-31' AS DATE),'flat_fee',8000,0,'active'),
('CTR007','C004','PLT_C',CAST('2024-12-01' AS DATE),CAST('2025-11-30' AS DATE),'revenue_share',0.15,15000,'active'),
('CTR008','C005','PLT_A',CAST('2025-01-01' AS DATE),CAST('2025-12-31' AS DATE),'per_play',0.002,3000,'active'),
('CTR009','C006','PLT_B',CAST('2025-01-01' AS DATE),CAST('2025-06-30' AS DATE),'revenue_share',0.18,12000,'expired'),
('CTR010','C007','PLT_A',CAST('2025-03-01' AS DATE),CAST('2025-12-31' AS DATE),'per_play',0.004,6000,'active'),
('CTR011','C008','PLT_C',CAST('2025-01-01' AS DATE),CAST('2025-12-31' AS DATE),'revenue_share',0.22,25000,'active'),
('CTR012','C009','PLT_A',CAST('2025-05-01' AS DATE),CAST('2026-04-30' AS DATE),'revenue_share',0.28,40000,'active'),
('CTR013','C010','PLT_B',CAST('2025-01-01' AS DATE),CAST('2025-12-31' AS DATE),'per_play',0.001,2000,'active'),
('CTR014','C011','PLT_C',CAST('2025-02-01' AS DATE),CAST('2025-07-31' AS DATE),'flat_fee',5000,0,'expired'),
('CTR015','C012','PLT_A',CAST('2025-01-01' AS DATE),CAST('2025-12-31' AS DATE),'revenue_share',0.20,18000,'active'),
('CTR016','C013','PLT_B',CAST('2025-04-01' AS DATE),CAST('2026-03-31' AS DATE),'per_play',0.003,4000,'active'),
('CTR017','C014','PLT_A',CAST('2024-11-01' AS DATE),CAST('2025-10-31' AS DATE),'revenue_share',0.25,35000,'active'),
('CTR018','C014','PLT_C',CAST('2025-01-01' AS DATE),CAST('2025-12-31' AS DATE),'per_play',0.006,8000,'active'),
('CTR019','C015','PLT_B',CAST('2025-03-01' AS DATE),CAST('2025-08-31' AS DATE),'revenue_share',0.18,14000,'active'),
('CTR020','C015','PLT_C',CAST('2025-06-01' AS DATE),CAST('2026-05-31' AS DATE),'revenue_share',0.15,10000,'active');
合同分成方式分布:
SELECT rate_type, COUNT(*) AS cnt, ROUND(AVG(rate_value), 4) AS avg_rate
FROM best_practice_copyright_royalty.doc_license_contracts
GROUP BY rate_type;
-- 第一步:通过 SQL PUT 将本地 CSV 文件上传到 User Volume
PUT '/path/to/doc_platform_plays.csv' TO USER VOLUME FILE 'doc_platform_plays.csv';
-- 第二步:从 User Volume COPY INTO 表
COPY INTO best_practice_copyright_royalty.doc_platform_plays
FROM USER VOLUME
USING csv
OPTIONS('header'='true', 'sep'=',', 'nullValue'='')
FILES ('doc_platform_plays.csv');
INSERT INTO best_practice_copyright_royalty.doc_platform_plays VALUES
('P001','C001','PLT_A',CAST('2025-01-15' AS DATE),12500,18750.00),
('P002','C001','PLT_A',CAST('2025-02-15' AS DATE),14200,21300.00),
('P003','C001','PLT_A',CAST('2025-03-15' AS DATE),16800,25200.00),
('P004','C001','PLT_A',CAST('2025-04-15' AS DATE),15000,22500.00),
('P005','C009','PLT_A',CAST('2025-05-15' AS DATE),25000,37500.00),
('P006','C009','PLT_A',CAST('2025-06-15' AS DATE),28000,42000.00),
('P007','C009','PLT_A',CAST('2025-07-15' AS DATE),31000,46500.00),
('P008','C014','PLT_A',CAST('2025-02-15' AS DATE),21000,31500.00),
('P009','C014','PLT_A',CAST('2025-03-15' AS DATE),19500,29250.00),
('P010','C014','PLT_A',CAST('2025-05-15' AS DATE),20000,30000.00),
('P011','C003','PLT_A',CAST('2025-01-15' AS DATE),300000,900.00),
('P012','C003','PLT_A',CAST('2025-04-15' AS DATE),400000,1200.00),
('P013','C005','PLT_A',CAST('2025-06-15' AS DATE),350000,700.00),
('P014','C007','PLT_A',CAST('2025-03-15' AS DATE),250000,1000.00),
('P015','C012','PLT_A',CAST('2025-07-15' AS DATE),300000,6000.00),
('P016','C015','PLT_A',CAST('2025-06-15' AS DATE),100000,18000.00),
('P017','C005','PLT_A',CAST('2025-05-15' AS DATE),61000,24803.00),
('P018','C001','PLT_B',CAST('2025-03-15' AS DATE),10000,8000.00),
('P019','C001','PLT_B',CAST('2025-04-15' AS DATE),12000,9000.00),
('P020','C001','PLT_B',CAST('2025-05-15' AS DATE),15000,9500.00),
('P021','C003','PLT_B',CAST('2025-02-15' AS DATE),80000,6000.00),
('P022','C003','PLT_B',CAST('2025-05-15' AS DATE),100000,4000.00),
('P023','C006','PLT_B',CAST('2025-01-15' AS DATE),120000,7000.00),
('P024','C006','PLT_B',CAST('2025-06-15' AS DATE),160000,6500.00),
('P025','C010','PLT_B',CAST('2025-01-15' AS DATE),200000,2200.00),
('P026','C010','PLT_B',CAST('2025-05-15' AS DATE),120000,1200.00),
('P027','C013','PLT_B',CAST('2025-03-15' AS DATE),210000,2100.00),
('P028','C013','PLT_B',CAST('2025-04-15' AS DATE),190000,1900.00),
('P029','C001','PLT_B',CAST('2025-06-15' AS DATE),8000,6000.00),
('P030','C003','PLT_B',CAST('2025-07-15' AS DATE),50000,5000.00),
('P031','C006','PLT_B',CAST('2025-02-15' AS DATE),40000,4800.00),
('P032','C010','PLT_B',CAST('2025-07-15' AS DATE),30000,3000.00),
('P033','C013','PLT_B',CAST('2025-06-15' AS DATE),20000,2000.00),
('P034','C001','PLT_B',CAST('2025-07-15' AS DATE),7000,5000.00),
('P035','C003','PLT_B',CAST('2025-01-15' AS DATE),25000,1500.00),
('P036','C006','PLT_B',CAST('2025-03-15' AS DATE),15000,2000.00),
('P037','C010','PLT_B',CAST('2025-02-15' AS DATE),12000,1000.00),
('P038','C013','PLT_B',CAST('2025-07-15' AS DATE),9800,424.00),
('P039','C002','PLT_C',CAST('2025-04-15' AS DATE),30000,6000.00),
('P040','C002','PLT_C',CAST('2025-05-15' AS DATE),40000,7000.00),
('P041','C004','PLT_C',CAST('2025-01-15' AS DATE),45000,9000.00),
('P042','C004','PLT_C',CAST('2025-05-15' AS DATE),50000,8000.00),
('P043','C008','PLT_C',CAST('2025-02-15' AS DATE),60000,12000.00),
('P044','C008','PLT_C',CAST('2025-05-15' AS DATE),70000,3000.00),
('P045','C011','PLT_C',CAST('2025-07-15' AS DATE),80000,10000.00),
('P046','C014','PLT_C',CAST('2025-03-15' AS DATE),90000,15000.00),
('P047','C014','PLT_C',CAST('2025-05-15' AS DATE),100000,1000.00),
('P048','C015','PLT_C',CAST('2025-06-15' AS DATE),70000,9000.00),
('P049','C002','PLT_C',CAST('2025-06-15' AS DATE),5000,7000.00),
('P050','C004','PLT_C',CAST('2025-02-15' AS DATE),4000,6000.00),
('P051','C008','PLT_C',CAST('2025-03-15' AS DATE),3000,5000.00),
('P052','C011','PLT_C',CAST('2025-04-15' AS DATE),4000,5000.00),
('P053','C014','PLT_C',CAST('2025-06-15' AS DATE),3000,4000.00),
('P054','C015','PLT_C',CAST('2025-07-15' AS DATE),3000,3000.00),
('P055','C011','PLT_C',CAST('2025-05-15' AS DATE),46300,471.00),
('P056','C002','PLT_C',CAST('2025-07-15' AS DATE),1000,1000.00),
('P057','C004','PLT_C',CAST('2025-03-15' AS DATE),1000,1000.00),
('P058','C008','PLT_C',CAST('2025-04-15' AS DATE),1000,1000.00),
('P059','C011','PLT_C',CAST('2025-06-15' AS DATE),700,792.00),
('P060','C001','PLT_A',CAST('2025-05-15' AS DATE),17200,25800.00);
验证各平台总体数据量:
SELECT
platform_id,
SUM(play_count) AS total_plays,
ROUND(SUM(revenue), 2) AS total_revenue,
COUNT(DISTINCT content_id) AS unique_contents
FROM best_practice_copyright_royalty.doc_platform_plays
GROUP BY platform_id
ORDER BY total_revenue DESC;
MERGE INTO best_practice_copyright_royalty.doc_contract_history AS t
USING (
SELECT
CONCAT(contract_id, '_', CAST(CURRENT_TIMESTAMP() AS STRING)) AS history_id,
contract_id,
content_id,
platform_id,
CASE WHEN __change_type = 'UPDATE_AFTER' THEN NULL
WHEN __change_type = 'UPDATE_BEFORE' THEN status
WHEN __change_type = 'DELETE' THEN status
ELSE NULL END AS old_status,
CASE WHEN __change_type = 'UPDATE_AFTER' THEN status
WHEN __change_type = 'INSERT' THEN status
ELSE NULL END AS new_status,
__change_type AS change_reason
FROM best_practice_copyright_royalty.stream_contract_changes
WHERE __change_type IN ('UPDATE_AFTER', 'INSERT', 'DELETE')
) AS s
ON t.contract_id = s.contract_id AND t.new_status = s.new_status
WHEN NOT MATCHED THEN
INSERT (history_id, contract_id, content_id, platform_id, old_status, new_status, change_reason)
VALUES (s.history_id, s.contract_id, s.content_id, s.platform_id, s.old_status, s.new_status, s.change_reason);
CREATE DYNAMIC TABLE IF NOT EXISTS best_practice_copyright_royalty.dwd_play_facts
AS
SELECT
p.play_id,
p.content_id,
p.platform_id,
p.play_date,
p.play_count,
p.revenue,
c.title,
c.content_type,
c.rights_holder,
c.release_year,
c.region,
DATE_FORMAT(p.play_date, 'yyyy-MM') AS stat_month
FROM best_practice_copyright_royalty.doc_platform_plays p
LEFT JOIN best_practice_copyright_royalty.doc_content_assets c
ON p.content_id = c.content_id;
手动触发首次刷新:
REFRESH DYNAMIC TABLE best_practice_copyright_royalty.dwd_play_facts;
SELECT COUNT(*) AS row_count FROM best_practice_copyright_royalty.dwd_play_facts;
row_count
---------
60
DWS 层:版税归因计算
DWS 层是版税数仓的核心层。按
content_id + platform_id + stat_month
content_id + platform_id + stat_month
三维聚合播放数据,关联合同条款,根据
rate_type
rate_type
应用不同计算公式,并取保底值的月度摊销值进行比较。
三种分成公式:
rate_type
版税计算逻辑
revenue_share
revenue_share
MAX(本月平台收益 × 比例, 年保底 / 12)
MAX(本月平台收益 × 比例, 年保底 / 12)
per_play
per_play
MAX(本月播放量 × 单价, 年保底 / 12)
MAX(本月播放量 × 单价, 年保底 / 12)
flat_fee
flat_fee
年授权费 / 12
年授权费 / 12
(固定,不随播放量浮动)
CREATE DYNAMIC TABLE IF NOT EXISTS best_practice_copyright_royalty.dws_royalty_calc
AS
SELECT
f.content_id,
f.platform_id,
f.stat_month,
f.title,
f.content_type,
f.rights_holder,
SUM(f.play_count) AS total_plays,
SUM(f.revenue) AS total_revenue,
c.contract_id,
c.rate_type,
c.rate_value,
c.min_guarantee,
c.end_date,
CASE
WHEN c.rate_type = 'revenue_share'
THEN GREATEST(SUM(f.revenue) * c.rate_value, c.min_guarantee / 12)
WHEN c.rate_type = 'per_play'
THEN GREATEST(SUM(f.play_count) * c.rate_value, c.min_guarantee / 12)
WHEN c.rate_type = 'flat_fee'
THEN c.rate_value / 12
ELSE 0
END AS estimated_royalty,
DATEDIFF(c.end_date, CURRENT_DATE()) AS days_to_expiry
FROM best_practice_copyright_royalty.dwd_play_facts f
LEFT JOIN best_practice_copyright_royalty.doc_license_contracts c
ON f.content_id = c.content_id AND f.platform_id = c.platform_id
GROUP BY
f.content_id, f.platform_id, f.stat_month, f.title, f.content_type, f.rights_holder,
c.contract_id, c.rate_type, c.rate_value, c.min_guarantee, c.end_date;
SELECT
content_id, platform_id, stat_month, title,
total_plays, ROUND(total_revenue, 2) AS total_revenue
FROM best_practice_copyright_royalty.dws_royalty_calc
ORDER BY total_revenue DESC
LIMIT 8;
content_id | platform_id | stat_month | title | total_plays | total_revenue
-----------+-------------+------------+--------------------+-------------+--------------
C009 | PLT_A | 2025-07 | The Lost City | 31000 | 46500
C009 | PLT_A | 2025-06 | The Lost City | 28000 | 42000
C009 | PLT_A | 2025-05 | The Lost City | 25000 | 37500
C014 | PLT_A | 2025-02 | Thriller Night | 21000 | 31500
C014 | PLT_A | 2025-05 | Thriller Night | 20000 | 30000
C014 | PLT_A | 2025-03 | Thriller Night | 19500 | 29250
C001 | PLT_A | 2025-05 | The Dragon Legacy | 17200 | 25800
C001 | PLT_A | 2025-03 | The Dragon Legacy | 16800 | 25200
《The Lost City》(C009)在 PLT_A 平台按 28%
revenue_share
revenue_share
分成,7 月单月收益 46,500 元,版税 13,020 元,是版税最高的单月记录。
ADS 层:结算报表与合同临期告警
ADS 层在 DWS 基础上加入合同状态标签,输出可直接用于对账的结算报表和临期告警。
CREATE DYNAMIC TABLE IF NOT EXISTS best_practice_copyright_royalty.ads_settlement_report
AS
SELECT
content_id,
platform_id,
title,
content_type,
rights_holder,
stat_month,
contract_id,
rate_type,
total_plays,
ROUND(total_revenue, 2) AS total_revenue,
ROUND(estimated_royalty, 2) AS estimated_royalty,
days_to_expiry,
CASE
WHEN days_to_expiry < 0 THEN 'expired'
WHEN days_to_expiry <= 30 THEN 'expiring_soon'
WHEN days_to_expiry <= 90 THEN 'expiring_in_quarter'
ELSE 'active'
END AS contract_status
FROM best_practice_copyright_royalty.dws_royalty_calc
WHERE estimated_royalty > 0;
SELECT
rights_holder,
stat_month,
ROUND(SUM(estimated_royalty), 2) AS total_royalty,
COUNT(DISTINCT content_id) AS content_cnt
FROM best_practice_copyright_royalty.ads_settlement_report
GROUP BY rights_holder, stat_month
ORDER BY total_royalty DESC, rights_holder, stat_month DESC
LIMIT 10;
SELECT
c.content_id, a.title, a.rights_holder,
c.contract_id, c.platform_id, c.end_date,
DATEDIFF(c.end_date, CURRENT_DATE()) AS days_remaining
FROM best_practice_copyright_royalty.doc_license_contracts c
LEFT JOIN best_practice_copyright_royalty.doc_content_assets a
ON c.content_id = a.content_id
ORDER BY days_remaining ASC
LIMIT 8;
SELECT
SUM(play_count) AS total_plays,
ROUND(SUM(revenue), 2) AS total_revenue
FROM best_practice_copyright_royalty.doc_platform_plays
TIMESTAMP AS OF '2026-06-06 23:53:00'
WHERE play_date >= CAST('2025-05-01' AS DATE)
AND play_date <= CAST('2025-05-31' AS DATE);