跳转至

看板 2.0 — 五份用户反馈,和五个新机制

这一章讲一件事: 应用上线之后怎么办。上一章的看板发布几周后, CEO 的邮件里装着五条用户反馈;本章逐条消化它们,每消化一条,学会一个新机制。 读完你会看到「迭代真实应用」的完整手感:粒度、级联过滤、对比、下钻、深链接, 外加一次数据源迁移(CSV → 数仓)。这是全书最接近真实工作的一章。

1. 这一章讲什么

上线 ≠ 完工。书里第一章就埋的观点在这里兑现:发布只是把东西送出去, 「落地」(landing it)——让它真正满足用户——要靠听用户说话1。 五条反馈和对应的解法,先给全景:

# 反馈(谁说的) 背后的真实需求 本章的解法
1 「时间图看几天挺好,看几年就看不懂了」 数字太密,要按周/月/年聚合 粒度列 + st.select_slider
2 「我选了 Writing tools,为什么还让我选订书钉?」(CFO) 过滤选项要联动 级联过滤
3 「RoyalQuill 七月 $1.32M,是好是坏?」(产品线负责人) 数字要有参照物 对比日期范围 + st.metric 的 delta
4 「我是个注重细节的人,我想钻进去看」(CEO) 高级用户要下钻 模态框 + 动态透视表
5 「把我看到的发给别人要截图,这是协作税」(CEO) 界面状态要可分享 深链接(URL 存状态)

最后加一条不在邮件里的「技术债」:CSV 该换成真正的数仓(BigQuery)2。

2. 核心原理(一):时间粒度——给用户一把「变焦镜头」

问题机制:折线图逐日打点,看一个月 30 个点没问题;看两年是 730 个点,糊成一片3。

解法分两层。数据层:prep_data 里给每行加三列——Week、Month、Year, 都是「该日所在周/月/年的第一天」:

df['Month'] = df['Day'].dt.to_period('M').dt.to_timestamp()
# 2024-07-12 → 期间「2024 年 7 月」→ 时间戳 2024-07-01

.dt 是 pandas 里「对整列逐元素取日期属性」的访问器;to_period('M') 把具体某天 折叠成「整个月」这个区间,再 to_timestamp() 落回月初那个日期4。 聚合函数把硬编码的 'Day' 换成参数 grain,一行参数贯穿数据与图表5。

界面层:st.select_slider——selectbox 和 slider 的杂交:选项是文本, 但带顺序(日 < 周 < 月 < 年),拖起来有滑块的体感6。

为什么这是好设计:粒度不该由应用猜(自动选?规则复杂、且用户失去控制), 也不该写死(第 06 章的教训);把选择器暴露给用户,是把「信息密度」的决策权 还给最知道自己在看什么的人。

3. 核心原理(二):级联过滤——选项从「已过滤的数据」里来

CFO 的吐槽:选了 Category = Writing tools 后,Segment 下拉里还站着订书钉—— 选项是无效的,组合是无意义的7。

两种解法,书里明确取舍。 解法 A:维护一张「维度依赖关系表」 (Product name 依赖 Category 和 Segment……),取选项时解析依赖—— 配置复杂、逻辑绕8。解法 B:换一个顺序——每画一个下拉,先把已选条件应用掉, 再从这个「有效数据」里取下一个下拉的选项:

effective_df = df
for idx, dim in enumerate(filter_dims):
    effective_df = apply_filters(effective_df, filters)   # 先应用前面的选择
    unique_vals = get_unique_values(effective_df, dim)    # 选项只来自剩下的行
    filters[dim] = st.multiselect(dim, unique_vals)

没有配置、没有依赖图,一个循环顺序就解决了——顺序即依赖9。 一个要紧的推论书里特意警告:过滤器的排列顺序从此有了语义。 把 Category 排在 Segment 后面,选 Category 就影响不到 Segment 的选项10。 这条约束在重构、加新过滤维度时会咬人,是这种「隐式依赖」的代价。

4. 核心原理(三):对比——数字离开参照物就没有意义

产品线负责人的问题翻译过来:$1.32M 本身不是信息,「比过去多了还是少了」才是11。

四种对比口径(书中定义,走一遍 2024-08-01 ~ 08-15 的例子):

  MoM(month over month,环比)  往前推一个月 → 2024-07-01 ~ 07-15
  QoQ(quarter over quarter)    往前推三个月 → 2024-05-01 ~ 05-15
  YoY(year over year,同比)     往前推一年   → 2023-08-01 ~ 08-15
  Previous period(上一段)      往前推「当前范围天数」= 15 天 → 2024-07-17 ~ 07-31

  实现就是把四种偏移量存成字典:{ "MoM": DateOffset(months=1), ... },
  返回 (start - offset, end - offset)

(DateOffset 是 pandas 的「日期偏移量」;减去它返回的是 pandas 时间戳, 要 .date() 转回 Python 的 date 保持类型一致12。)

显示:st.metric 的第三、四参数——delta 和 delta_color。 计算 delta = (本期值 - 对比期值) / 对比期值(注意分母是对比期值—— 书里写的例子:八月 $5M、七月 $4M,delta 是 1/4 = 25%,不是 1/5)13; delta_color="normal" 表示「涨绿跌红」,"inverse" 是反的(适合成本这类越低越好的), "off" 全灰14。对比期的 dataframe 用同一套过滤条件、只换日期范围算出15。

5. 核心原理(四):下钻——模态框 + 动态透视表

CEO 想「钻进数字里看为什么」。这是高级功能,设计决定:不进主界面, 进模态框(modal dialog)——盖在主内容上的浮层,不看就关掉,普通用户无感16。

st.dialog 是个装饰器(和 st.cache_data 同类,不是容器):贴在一个函数上, 这个函数的内容就渲染进弹窗;width="large"(750 像素,small 是 500)17:

@st.dialog("Drilldown", width="large")
def drilldown(main_df, compare_df):
    dimensions = drilldown_dimensions()          # 用户选要按哪些维度拆
    main_data = get_drilldown_table(main_df, dimensions)
    # ……两个 tab(Main / Compare)里 st.dataframe 显示

一个「顺序悖论」和它的解法:按钮想放在侧边栏日期面板的上面, 但按钮回调需要 main_df,而 main_df 要日期值算出来才有——代码顺序倒不过来。 st.container 的隐藏用法:占位。先在 sidebar 顶部放一个空容器占位, 等 main_df 算出来,再往占位里塞按钮:dd_button_container.button("Drilldown")18。 这把「渲染顺序」和「计算顺序」解耦了——是声明式布局思维的一个小高峰。

透视表内容:用户用 multiselect 任选维度组合,groupby(维度列表).apply(算全部指标), 一行 = 一个维度组合、四个指标全列出;没选维度时退化成单行总计。 加总计行要把「维度列留空、第一列写 Total」和「在未聚合的原始数据上重算指标」拼起来 (聚合后的表里算不出真总计——比率的总计尤其不能平均),再 pd.concat 到表顶, 用 df.style.apply 给首列为 Total 的行上灰色背景(CSS 条件样式)19。

一个执行模型的意外之喜:按第 04 章的模型,对话框里的 multiselect 一变, 脚本重跑、按钮的「已点击」状态清空,对话框该消失——但没有。 因为 st.dialog 内部基于 st.fragment:对话框里的交互只重跑对话框函数本身, 按钮状态保留20。这条机制书里是当「彩蛋」讲的,但它其实是 Streamlit 生态 向「局部更新」演进的方向标。

6. 核心原理(五):深链接——把界面状态存进 URL

CEO 的最后一击:「把视图发给同事要截图 + 口述操作步骤,这是协作税。」21 搜索引擎早就解决了同类问题:DuckDuckGo 搜索 streamlit 后, URL 是 duckduckgo.com/?t=h_&q=streamlit——输入嵌在网址里,网址即状态22。

机制两件套:

  1. 写入:用户改任何部件 → 更新 st.query_params(字典式对象,改了它就改了浏览器地址栏)。
  2. 读取:打开应用时 → 从 query params 里取出值,在部件创建之前写进 st.session_state[部件key](第 04 章说过:有 key 的部件,其值住在 session_state 里, 而且只能在部件渲染前设置)23。

落地时的三个工程细节,每个都是一个真实坑:

  • key 命名空间:给想进 URL 的部件 key 统一加 w: 前缀, 和 session_state 里的其他状态区分开24。
  • 只设一次:if key not in st.session_state 防止每次重跑都用 URL 覆盖用户的新选择25。
  • 类型编码:query params 全是字符串,但 date_input 要 date 对象、multiselect 要列表。 手工协议:D#2024-07-01 表示日期、L#M,F 表示列表,读写两端各自编解码 (isinstance 判断类型)26。第一版没做这层,粘贴 URL 直接报错——书里完整展示了 这个错误和修复,是「字符串是一切 URL 的母语」的最好教材。

两个调用点的位置本身就是知识:set_widget_defaults() 在文件最顶 (必须在任何部件之前),set_params() 在文件最末(必须在所有部件之后)27。

7. 核心原理(六):数据源迁移——模块化在压力下的兑现

CSV 的原罪:大就慢、不能「在源头查询」(过滤聚合都得先全load进内存)。 本章把数据源换成 BigQuery(Google 的云数仓,SQL 查询,免管基础设施)28。

工程动作链条:注册 GCP → 建 dataset → 上传 CSV 建表 → 启用 BigQuery API → 建服务账号(service account,属于应用而非个人的机器账号)+ 下载 JSON 密钥 → 把 JSON 转写成 secrets.toml 的 [bigquery] 段(用 toml 库转换,密钥照旧走 第 05 章的 secrets 管线)→ 装三个库(google-cloud-bigquery、…-storage、db-dtypes)→ load_data 换成「客户端 + SELECT * + to_dataframe」29。

重点是代价:因为第 06 章的模块化,整个应用只有 load_data 一个函数需要改, 下游的 prep、过滤、指标、图表一概不动30。这是第 03 章「关注点分离」原则 第一次在高压力场景兑现——第 03 章时它还像过度设计,这里它收回了成本。 SQL 顺带留了后手:SELECT * 全量取数之外,完全可以在数仓里先过滤再取 (大数据集时的正解),CSV 时代这是做不到的31。

8. 作者的判断与证据

判断:「和用户谈」是设计下钻视图的前置步骤。 书里真的写了这场访谈: CEO 要查 RoyalQuill 广告投给 46 岁以上女性的效果,需要「年龄 × 性别」交叉, 单一维度的饼图给不了32。用访谈驱动设计是本章最该带走的工作方式, 比任何代码都值钱。

判断:解法 B(级联过滤)优于解法 A(依赖配置)。 依据是「更简单」33。 我们认可,但补注:顺序即语义这件事要有自觉——团队里必须有人知道 filter_dims 的排列是行为的一部分。

判断:饼图争议照用。 第 06 章已判过,本章没有反复。

9. 边界与局限

  • query params 的类型编码是手工协议(D#/L# 前缀),没有版本号、没有转义规则; 值里真出现 # 或 , 就会错位。生产级方案要么用 JSON 编码,要么用成熟库——书里没提这层。
  • st.fragment 机制只在这里「惊鸿一瞥」,没有展开:什么场景该主动用 fragment、 它和普通重跑的性能边界在哪。这是本章最大的留白。
  • BigQuery 一节是 GCP 专属点击流,服务账号、IAM 角色(书里给的 Viewer)界面 都是时点快照;概念(服务账号、密钥、最小权限)可迁移,操作不可照抄。
  • 「顺序即依赖」的级联过滤有一个隐藏 bug 面:用户先选了后面的维度再回头改前面的维度, 已选值可能在新选项集里不存在;书中未处理这个边界(multiselect 对失效选项的行为 依赖 Streamlit 版本)。
  • 对比口径的日历陷阱:MoM 用 DateOffset(months=1),1 月 31 日往回推一个月是 2 月的最后一天这类边界,本章的 15 天例子没暴露,真实月份长度不等的范围会遇到。

判断(我们的,不是书里的): 本章是全书最成熟的一章,因为它示范了 「软件是被人用出来的」这条最朴素的真理:五个机制没有一个是作者坐在屋里想出来的, 全是被用户的具体句子逼出来的(「协作税」三个字逼出了深链接)。 对编程语言与系统研究,这章还提供了一个「界面状态可以存进网址」的绝佳案例: st.query_params + session_state 的组合,本质是把命令式脚本的状态投影成 可分享的声明式快照——这是所有协作文档(共享表格、Figma 链接)的同款问题。 如果错,会错在: 反馈驱动的故事有幸存者偏差——书里五条反馈条条有解, 真实世界里更多的是「反馈了但做不了」「做了但没人用」;本章的顺遂是教学叙事, 不是行业的常态。把它当「方法论示范」读,别当「项目实录」读。

10. 可带走的

  1. 上线后去听用户讲话;每条抱怨背后是一个机制,不是一个 bug。
  2. 粒度问题 = 让用户选聚合键(日/周/月/年),别替他猜。
  3. 级联过滤的极简实现:每画一个下拉,先用已选条件过滤数据,再取选项——顺序即依赖。
  4. 数字必须配参照物:MoM/QoQ/YoY/Previous period 四种口径,DateOffset 字典实现。
  5. st.metric 的 delta 永远相对对比期算;delta_color 记得 "inverse"(成本类指标)。
  6. st.dialog 是装饰器不是容器;对话框内交互只重跑对话框(st.fragment 机制)。
  7. st.container 可以占位——把「渲染顺序」和「计算顺序」解耦的钥匙。
  8. 透视表 = groupby(用户选的维度).apply(全部指标) + 总计行(在未聚合数据上重算)+ 样式高亮。
  9. 深链接 = 写入(query params 跟部件)+ 读取(session_state 在部件前赋值);类型必须手工编解码。
  10. 想让状态可分享,给部件 key 设计命名空间(w: 前缀)。
  11. 模块化在换数据源时兑现:只改 load_data,下游不动——关注点分离的复利。
  12. 服务账号 = 属于应用的账号;JSON 密钥 → secrets.toml,密钥管理沿用第 05 章的三层防线。

11. 原文地图

主题 原书章 原文位置
「launch 之后要 land」 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:32搜「Feedback on the dashboard」)
粒度问题与 730 个点 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:60搜「365 × 2」)
to_period/to_timestamp 加粒度列 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:82搜「to_period」)
st.select_slider 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:138搜「cross between」)
CFO 的级联过滤吐槽 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:169搜「why does it still ask」)
两种解法与顺序即依赖 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:196搜「record the interdependencies」)
级联过滤实现与顺序警告 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:257搜「effective_df」)
对比的四种口径 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:306搜「month-over-month」)
get_compare_range 与 offsets 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:342搜「DateOffset」)
delta 计算(1/4 不是 1/5) 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:404搜「20%」)
st.dialog 装饰器 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:553搜「modal dialog」)
st.container 占位解顺序悖论 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:598搜「out of order」)
CEO 访谈:年龄×性别交叉 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:697搜「RoyalQuill」)
动态聚合透视表 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:751搜「Aggregating by a few chosen dimensions」)
总计行与 ** 解包 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:889搜「add_total_row」)
style.apply 高亮总计行 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:997搜「style property」)
st.dialog 的 fragment 行为 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1066搜「fragment-like」)
协作税与深链接动机 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1100搜「tax」)
query string / query params 解析 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1130搜「question mark」)
部件 key 经 session_state 赋值(须先于渲染) 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1194搜「simulating a user selection」)
w: 前缀与 set_widget_defaults/set_params 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1209搜「Getting and setting query params」)
D#/L# 类型编码与修复报错 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1410搜「expects a date object」)
CSV 的局限与数仓 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1462搜「data warehouse」)
BigQuery 建表流程 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1509搜「Create Dataset」)
服务账号与密钥 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1543搜「service account」)
JSON → secrets.toml 转换 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1566搜「Generating secrets.toml」)
新 load_data 只改一个文件 7 The CEO strikes back text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1616搜「separation of concerns」)


  1. 出处:「7 The CEO strikes back」第 32–48 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:40,搜「consolidated feedback」);「launch/land」的说法见第 1716–1719 段(小结)。 ↩

  2. 出处:「7 The CEO strikes back」第 1462–1480 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1465,搜「static CSV file」)。 ↩

  3. 出处:「7 The CEO strikes back」第 50–66 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:60,搜「365 × 2」)。 ↩

  4. 出处:「7 The CEO strikes back」第 76–103 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:82,搜「to_period」)。2024-07-12 → 2024-07-01 的例子见第 100–101 段。 ↩

  5. 出处:「7 The CEO strikes back」第 108–130 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:129,搜「adding grain as a new parameter」)。 ↩

  6. 出处:「7 The CEO strikes back」第 132–166 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:138,搜「cross between」)。 ↩

  7. 出处:「7 The CEO strikes back」第 168–184 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:180,搜「Paper clips」)。 ↩

  8. 出处:「7 The CEO strikes back」第 196–204 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:199,搜「maintaining a new configuration」)。 ↩

  9. 出处:「7 The CEO strikes back」第 245–269 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:257,搜「effective_df」)。 ↩

  10. 出处:「7 The CEO strikes back」第 278–281 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:278,搜「order of the filters is now important」)。 ↩

  11. 出处:「7 The CEO strikes back」第 282–298 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:284,搜「is that good or bad」)。 ↩

  12. 出处:「7 The CEO strikes back」第 300–374 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:306,搜「MoM」)。.date() 的必要性见第 367–374 段。 ↩

  13. 出处:「7 The CEO strikes back」第 392–407 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:404,搜「20%」)。原文:「the delta is 20% ($1M / $5M x 100)」——按此原文,delta 的分母是本期 $5M;我们文中按代码实现(除以 compare_value)描述,两处口径不一致已在正文按代码为准说明。 ↩

  14. 出处:「7 The CEO strikes back」第 460–475 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:461,搜「delta_color」)。 ↩

  15. 出处:「7 The CEO strikes back」第 414–438 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:427,搜「compare_df」)。 ↩

  16. 出处:「7 The CEO strikes back」第 553–562 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:559,搜「overlay displayed on top」)。 ↩

  17. 出处:「7 The CEO strikes back」第 563–597 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:590,搜「500 pixels」)。 ↩

  18. 出处:「7 The CEO strikes back」第 598–680 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:643,搜「placeholder」)。「dd_button_container」见第 651–664 段。 ↩

  19. 出处:「7 The CEO strikes back」第 747–1061 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:748,搜「assemble it piece by」)。总计行 861–953 段;样式 962–1056 段。 ↩

  20. 出处:「7 The CEO strikes back」第 1066–1093 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1089,搜「gets rerun, not the entire app」)。 ↩

  21. 出处:「7 The CEO strikes back」第 1095–1110 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1098,搜「Recipients then waste time」)。 ↩

  22. 出处:「7 The CEO strikes back」第 1111–1127 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1115,搜「q=streamlit」)。 ↩

  23. 出处:「7 The CEO strikes back」第 1128–1202 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1730,搜「dictionary-like object」)与第 1183–1202 段(只能先于渲染设置)。 ↩

  24. 出处:「7 The CEO strikes back」第 1235–1248 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1240,搜「prefix the string 'w:'」)。 ↩

  25. 出处:「7 The CEO strikes back」第 1244–1248 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1246,搜「only want to set each widget value once」)。 ↩

  26. 出处:「7 The CEO strikes back」第 1404–1458 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1410,搜「expects a date object」)。报错见第 1397–1401 段,编解码见第 1421–1451 段。 ↩

  27. 出处:「7 The CEO strikes back」第 1344–1363 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1356,搜「exactly where」)。 ↩

  28. 出处:「7 The CEO strikes back」第 1478–1495 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1485,搜「Structured Query Language」)。 ↩

  29. 出处:「7 The CEO strikes back」第 1482–1601 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1509,搜「Create Dataset」)与第 1533–1601 段(服务账号、密钥、secrets.toml)。 ↩

  30. 出处:「7 The CEO strikes back」第 1602–1684 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1614,搜「only need to update」)。 ↩

  31. 出处:「7 The CEO strikes back」第 1670–1674 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:1672,搜「filtering it within」)。 ↩

  32. 出处:「7 The CEO strikes back」第 690–716 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:693,搜「slot on the CEO’s calendar」)。 ↩

  33. 出处:「7 The CEO strikes back」第 196–204 段(text/16-ch07-7-the-ceo-strikes-back-supercharging-the-dashboa.txt:201,搜「There's an easier alternative」)。 ↩