地理空间数据库作业笔记——酒驾交通事故分析
3.7 酒驾交通事故分析
美国交通局:We' re directly soliciting your help to better understand what these data are telling us. Whether you' re a non-profit, a tech company, or just a curious citizen wanting to contribute to the conversation in your local community, we want you to jump in and help us understand what the data are telling us. Some key questions worth exploring:
- How might improving economic conditions around the country change how Americans are getting around? What models can we develop to identify communities that might be at a higher risk for fatal crashes?
- How might climate change increase the risk of fatal crashes in a community?
- How might we use studies of attitudes toward speeding, distracted driving, and seat belt use to better target marketing and behavioral change campaigns?
- How might we monitor public health indicators and behavior risk indicators to target communities that might have a high prevalence of behaviors linked with fatal crashes (drinking, drug use/addiction, etc.)? What countermeasures should we create to address these issues?
美国交通局在2018年开展了Visualize Transportation Safety可视化挑战赛。
酒驾(drunk_dr > 0)是否在周末更容易发生?构造SQL语句查询工作日平均每日酒驾事件数与周末平均每日酒驾事件数(avg_weekday_count, avg_weekend_count),保留到小数点后4位,分析查询结果给出结论,注意中美文化差异中星期起始日的差别(3分)
解法一:多级CTE方法
sql
WITH drunk_accidents AS (SELECT day_week,COUNT(*) as daily_countFROM usaccidents WHERE drunk_dr > 0GROUP BY day_week ), weekday_weekend AS (SELECT CASE WHEN day_week IN (1, 2, 3, 4, 5) THEN 'weekday'WHEN day_week IN (6, 7) THEN 'weekend'END as day_type,daily_countFROM drunk_accidents ), summary AS (SELECT day_type,AVG(daily_count) as avg_daily_countFROM weekday_weekendGROUP BY day_type ) SELECT ROUND(AVG(CASE WHEN day_type = 'weekday' THEN avg_daily_count END)::numeric, 4) as avg_weekday_count,ROUND(AVG(CASE WHEN day_type = 'weekend' THEN avg_daily_count END)::numeric, 4) as avg_weekend_count FROM summary;
详细执行步骤分析:
第一步:drunk_accidents CTE
sql
SELECT day_week, COUNT(*) as daily_count FROM usaccidents WHERE drunk_dr > 0 GROUP BY day_week
作用:统计每天的酒驾事故总数
输出示例:
text
day_week | daily_count ---------|-------------1 | 1502 | 1203 | 1104 | 1155 | 1256 | 1807 | 200
第二步:weekday_weekend CTE
sql
SELECT CASE WHEN day_week IN (1, 2, 3, 4, 5) THEN 'weekday'WHEN day_week IN (6, 7) THEN 'weekend'END as day_type,daily_count FROM drunk_accidents
作用:将每天数据分类为工作日/周末
输出示例:
text
day_type | daily_count ---------|------------- weekday | 150 (周日) weekday | 120 (周一) weekday | 110 (周二) weekday | 115 (周三) weekday | 125 (周四) weekend | 180 (周五) weekend | 200 (周六)
第三步:summary CTE
sql
SELECT day_type, AVG(daily_count) as avg_daily_count FROM weekday_weekend GROUP BY day_type
作用:计算工作日和周末的平均值
输出示例:
text
day_type | avg_daily_count ---------|----------------- weekday | 124.0 weekend | 190.0
第四步:最终SELECT
sql
SELECT ROUND(AVG(CASE WHEN day_type = 'weekday' THEN avg_daily_count END)::numeric, 4) as avg_weekday_count,ROUND(AVG(CASE WHEN day_type = 'weekend' THEN avg_daily_count END)::numeric, 4) as avg_weekend_count FROM summary
问题:这里使用了不必要的
AVG()函数应该改为:
sql
SELECT ROUND(MAX(CASE WHEN day_type = 'weekday' THEN avg_daily_count END)::numeric, 4) as avg_weekday_count,ROUND(MAX(CASE WHEN day_type = 'weekend' THEN avg_daily_count END)::numeric, 4) as avg_weekend_count FROM summary
解法二:简洁版本
sql
SELECT ROUND(AVG(CASE WHEN day_week IN (1,2,3,4,5) THEN daily_count END)::numeric, 4) as avg_weekday_count,ROUND(AVG(CASE WHEN day_week IN (6,7) THEN daily_count END)::numeric, 4) as avg_weekend_count FROM (SELECT day_week,COUNT(*) as daily_countFROM usaccidents WHERE drunk_dr > 0GROUP BY day_week ) as daily_counts;
详细执行步骤分析:
子查询:daily_counts
sql
SELECT day_week, COUNT(*) as daily_count FROM usaccidents WHERE drunk_dr > 0 GROUP BY day_week
与解法一的第一步完全相同
输出同样的每日酒驾事故统计
主查询:条件平均值计算
sql
ROUND(AVG(CASE WHEN day_week IN (1,2,3,4,5) THEN daily_count END)::numeric, 4)
执行逻辑:
对于每一行数据,检查
day_week是否在1-5(工作日)如果是工作日,取
daily_count值;否则返回NULLAVG()函数自动忽略NULL值,只计算工作日的平均值ROUND(...::numeric, 4)进行四舍五入
两种解法的对比分析
解法一的问题:
逻辑错误:在最后一步错误地使用了
AVG()而不是MAX()过度复杂:使用了不必要的多级CTE
效率较低:需要多次中间结果传递
解法二的优势:
逻辑正确:直接使用条件平均值计算
代码简洁:减少了不必要的中间步骤
执行高效:数据库优化器可以更好地优化单层查询
关于星期定义的说明:
两种解法都使用了相同的星期定义:
周日=1, 周一=2, 周二=3, 周三=4, 周四=5, 周五=6, 周六=7
工作日:1,2,3,4,5(周日到周四)
周末:6,7(周五、周六)
修正后的最优解法:
sql
SELECT ROUND(AVG(CASE WHEN day_week IN (1,2,3,4,5) THEN daily_count END)::numeric, 4) as avg_weekday_count,ROUND(AVG(CASE WHEN day_week IN (6,7) THEN daily_count END)::numeric, 4) as avg_weekend_count FROM (SELECT day_week, COUNT(*) as daily_countFROM usaccidents WHERE drunk_dr > 0GROUP BY day_week ) as daily_counts;
结论:解法二不仅更简洁,而且逻辑正确、执行效率更高,是更好的解决方案。
ROUND函数的基本概念
ROUND函数是一种常见的数学函数,用于对数字进行四舍五入操作。它广泛应用于Excel、SQL、Python等多种编程语言和工具中,主要功能是根据指定的小数位数对数值进行精确舍入。
语法格式
在大多数场景中,ROUND函数的语法如下:
ROUND(number, digits)
- number:需要舍入的原始数值。
- digits:指定保留的小数位数。
- 若为正数,表示舍入到小数点右侧的位数。
- 若为负数,表示舍入到小数点左侧的位数(如整数位、十位等)。
- 若为0,表示舍入到最接近的整数。
使用示例
保留两位小数
ROUND(3.14159, 2)返回结果为3.14。舍入到整数
ROUND(3.6, 0)返回结果为4。负数位数舍入
ROUND(1234, -2)返回结果为1200(舍入到百位)。
注意事项
- 当舍入位数的后一位数字为5时,不同语言或工具可能采用不同的规则(如“银行家舍入法”或“四舍五入”)。
- 在Excel中,ROUND函数严格遵循四舍五入规则;而在Python的
round()函数中,对中间值(如0.5)的处理可能依赖具体实现。
跨平台差异
- Excel/Google Sheets:直接支持ROUND函数,行为一致。
- SQL:语法类似,如MySQL的
ROUND(3.1415, 2)。 - Python:通过内置函数
round()实现,但需注意浮点数精度问题。
代码逐行解析
第一行:
SELECT ROUND(AVG(CASE WHEN day_week IN (1,2,3,4,5) THEN daily_count END)::numeric, 4) as avg_weekday_count,
SELECT:开始一个查询语句。ROUND(x, 4):将数值x四舍五入到4位小数。AVG():计算平均值。CASE WHEN day_week IN (1,2,3,4,5) THEN daily_count END:条件判断,若day_week为工作日(1-5),则取daily_count的值,否则返回NULL。::numeric:将结果转换为numeric类型以确保精确计算。as avg_weekday_count:将结果列命名为avg_weekday_count。
第二行:
ROUND(AVG(CASE WHEN day_week IN (6,7) THEN daily_count END)::numeric, 4) as avg_weekend_count
- 逻辑与第一行类似,但条件改为
day_week IN (6,7)(周末),结果列命名为avg_weekend_count。
第三行:
FROM (
- 开始子查询,作为主查询的数据来源。
第四行:
SELECT day_week, COUNT(*) as daily_count
SELECT day_week:选择day_week列。COUNT(*):统计每组的行数。as daily_count:将统计结果命名为daily_count。
第五行:
FROM usaccidents
- 从表
usaccidents中获取数据。
第六行:
WHERE drunk_dr > 0
- 筛选条件:仅选择
drunk_dr大于0的记录(涉及酒驾的事故)。
第七行:
GROUP BY day_week
- 按
day_week分组,每组对应一周中的某一天(1-7)。
第八行:
) as daily_counts;
- 结束子查询,并将其命名为
daily_counts。
功能总结
- 子查询:统计表
usaccidents中每天(day_week)的酒驾事故数量(daily_count)。 - 主查询:分别计算工作日(1-5)和周末(6-7)的平均事故数量,并四舍五入到4位小数。
- 最终输出两列:
avg_weekday_count(工作日平均事故数)和avg_weekend_count(周末平均事故数)。
