锁定老帖子 主题:SQL 小技巧
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2008-08-20
最后修改:2008-11-19
其实想想,sql还是挺好玩的,尤其是把别人吭哧吭哧几百行的stored procedure改写成一个简洁的query,或者一个本来要跑十分钟的stored proc改成几秒钟,感觉还是满爽的。 闲来无事,把一些小技巧回忆下来,省得以后考人的时候想不起来了。 1。一个基金performance表,记录着每个基金的每个月相比于前一个月的增长百分率,比如: 一月: 2.1(%) 二月: -0.5 (%) 三月: ... 十二月:15 (%) 基金用基金ticker来标识。 请列出每个基金从去年五月到今年五月的总共增长。 2。每个基金每个月都有打分,从一分到五分。请列出最近一年内每个基金被打五分的次数,四分的次数,三分的次数。如果一个基金被打至少三分的次数少于两次,就不要列出来了。 3。请针对上一年列出每个基金增长最好的那个月,包括基金ticker,当月的增长百分比,当月打分。如果两个月增长幅度相同,选择打分高的那个月;如果打分也相同,选择最近发生的那个月。 4。假设基金表不小心出现了重复数据,也就是ticker, 月份都相同,但是id是主键,唯一的。请删除重复记录。对重复记录,保留打分高的那条,如果打分也一样,保留其中任意一条。(其实,这个和三是一样的,不过算一个比较常见的dba要面临的问题) (答案回头有时间写出来。提示:珍爱生命,远离存储过程!) (I have discovered a truly marvelous proof of this, which this margin is too narrow to contain) 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-08-20
ajoo竟然也用sql的...巨震
不过你这题目出的也太不专业了,起码得说明一下是什么数据库吧? mysql? oracle? |
|
返回顶楼 | |
发表时间:2008-08-20
问题一,经过google得到一用sum,log,pow来得到累乘的答案:
SELECT pow(2, log(2, sum((100 + change) / 100))), ticker FROM performance group by ticker |
|
返回顶楼 | |
发表时间:2008-08-20
Readonly 写道 问题一,经过google得到一用sum,log,pow来得到累乘的答案:
SELECT pow(2, log(2, sum((100 + change) / 100))), ticker FROM performance group by ticker 那你猜猜我当时经手的原始代码是怎么写的? |
|
返回顶楼 | |
发表时间:2008-08-20
Readonly 写道 问题一,经过google得到一用sum,log,pow来得到累乘的答案:
SELECT pow(2, log(2, sum((100 + change) / 100))), ticker FROM performance group by ticker 嗯?你确定这个对么? |
|
返回顶楼 | |
发表时间:2008-08-20
Readonly 写道 ajoo竟然也用sql的...巨震
不过你这题目出的也太不专业了,起码得说明一下是什么数据库吧? mysql? oracle? 居然都不用“虎躯”的,太不专业了! |
|
返回顶楼 | |
发表时间:2008-08-20
Readonly 写道 问题一,经过google得到一用sum,log,pow来得到累乘的答案:
SELECT pow(2, log(2, sum((100 + change) / 100))), ticker FROM performance group by ticker |
|
返回顶楼 | |
发表时间:2008-08-20
ajoo 写道 Readonly 写道 问题一,经过google得到一用sum,log,pow来得到累乘的答案:
SELECT pow(2, log(2, sum((100 + change) / 100))), ticker FROM performance group by ticker 嗯?你确定这个对么? 哦...发现sum和log放错位置了,应该是: SELECT pow(2, sum(log(2,(100 + change) / 100))), ticker FROM performance group by ticker 不过,不知道这个效率如何,而且还有精确度的隐患,搞不好还不如存储过程或者全部抓出来用代码内存中计算。 等公布其他题目的答案... |
|
返回顶楼 | |
发表时间:2008-08-20
效率没问题。实际上一般的query效率都在查询上,至于对查询结果的计算,代价基本都可以忽略了。全抓出来到程序里的话,大量数据就要从数据库跑到应用程序里,这个开销也不小的。你的程序还要自己做group by,麻烦。
精度也没问题。我是用它来代替一段傻不拉唧的cursor循环的,计算结果(精确到小数点后两位)分毫不差。毕竟,double计算本身就是有误差的。 不过,我用的是自然对数,没试过2或者10. |
|
返回顶楼 | |
发表时间:2008-08-20
第一个问题是我在维护一个金融分析软件的时候碰到的。原来的那位老兄正儿八经地用一个存储过程来搞这个东西:
create proc accumulate @startdate, @enddate as declare cursor c for select distinct ticker from perf create temp table a loop over c declare cursor c2 for select percent from perf where ticker = @ticker order by date loop over c2 and add up all percent for @ticker insert into a values(@ticker, @sum) select * from a 这是伪代码,真正的代码要还要复杂一些。 存储过程的问题在哪? 1. 效率。用循环代替query,或者说用你自己的人肉nested-loop方案来代替优化器可能做到的range scan, hash join, merge join等等,写出来的东西爬得象蜗牛就没什么稀奇的了。 2. 灵活性。用query的话,你可以在where里用任意的表达式来过滤你想要的record,不见得永远都是一个startdate和enddate,比如你可以只关注s&p 500的ticker啦之类的。另外你还可以跟其它的表join,比如得到这个ticker的全名,基金经理老妈贵姓之类的。用存储过程?除非你用dynamic sql,否则对应每个需求你都要copy-paste然后修改一通。 3. 复杂,繁琐,难于维护。 我的解决方法嘛,只读提过了。为了完整,我再贴一遍我的用自然对数的方法: select ticker, (exp(sum(log(1 + percent/100))) - 1) / 100 as accumulated_percent from perf where date between startdate and enddate group by ticker ---------------------------------------------------------------------------------------- 第二个问题,是一个简化版的问题。原始问题涉及了一些无关的条件,结果无非是要多join几个表,where clause和要select的column更多罢了。原始的代码大概是这样: select ticker, count(*) as times into #five_star from perf where star = 5 and date between startdate and enddate group by perf select ticker, count(*) as times into #four_star from pef where star = 4 and date between startdate and enddate group by perf select ticker, count(*) as times into #three_star from perf where star = 3 and date between startdate and enddate group by perf select t.ticker, isnull(s5.times, 0) as star5, isnull(s4.times, 0) as star4, isnull(s3.times, 0) as star3 from ticker t left join #five_star s5 on t.ticker = s5.ticker left join #four_star s4 on t.ticker = s4.ticker left join #three_star s3 on t.ticker = s3.ticker where isnull(s5.times, 0) + isnull(s4.times, 0) + isnull(s3.times, 0) >= 2 呼,小样儿的,写这么长没累死我! 这个玩意儿挑眼太容易了: 1. 无故扫描整表三次!慢 2. 代码重复。我要把同样的where clause抄三遍。实际上,原始问题中,那个where和join之类的东西占了满屏,还要原封不动地照抄。维护的时候要改三个地方! 3. 比较容易出错。比如后面那个join,因为很有可能一个ticker没有被评过五星,如果不用left join和isnull()来绕,就可能漏掉记录。 解决方法嘛,当然是一遍扫描。其实说穿了一点不稀奇,就是想办法弄一个比count(*)聪明点的组合函数,让它能只对需要的记录计数。 select ticker, sum(case star when 5 then 1 else 0 end) star5, sum(case star when 4 then 1 else 0 end) star4, sum(case star when 3 then 1 else 0 end) star3 from perf where date between startdate and enddate group by ticker having sum(case when star >=3 then 1 else 0 end) >=2 ---------------------------------------------------------------------------------------- 第三个问题,要复杂一些了。我也懒得写原始的解决方法了,太麻烦。其实如果你仔细想了的话,这个东西如果仅仅是要表现最好的那个百分比,是非常非常简单的。麻烦就在于我们需要那个百分比之外的其它column的信息。 哦,要下火车了,等有时间再写了只能。 |
|
返回顶楼 | |