论坛首页 综合技术论坛

SQL 小技巧

浏览 35183 次
锁定老帖子 主题:SQL 小技巧
该帖已经被评为精华帖
作者 正文
   发表时间:2008-08-20   最后修改:2008-11-19
很久不搞sql了。整天就是java, java,也挺无聊的。

其实想想,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)
   发表时间:2008-08-20  
ajoo竟然也用sql的...巨震
不过你这题目出的也太不专业了,起码得说明一下是什么数据库吧?
mysql? oracle?
0 请登录后投票
   发表时间:2008-08-20  
问题一,经过google得到一用sum,log,pow来得到累乘的答案:
SELECT pow(2, log(2, sum((100 + change) / 100))), ticker FROM performance group by ticker
0 请登录后投票
   发表时间:2008-08-20  
Readonly 写道
问题一,经过google得到一用sum,log,pow来得到累乘的答案:
SELECT pow(2, log(2, sum((100 + change) / 100))), ticker FROM performance group by ticker

那你猜猜我当时经手的原始代码是怎么写的?
0 请登录后投票
   发表时间:2008-08-20  
Readonly 写道
问题一,经过google得到一用sum,log,pow来得到累乘的答案:
SELECT pow(2, log(2, sum((100 + change) / 100))), ticker FROM performance group by ticker


嗯?你确定这个对么?
0 请登录后投票
   发表时间:2008-08-20  
Readonly 写道
ajoo竟然也用sql的...巨震
不过你这题目出的也太不专业了,起码得说明一下是什么数据库吧?
mysql? oracle?

居然都不用“虎躯”的,太不专业了!
0 请登录后投票
   发表时间:2008-08-20  
Readonly 写道
问题一,经过google得到一用sum,log,pow来得到累乘的答案:
SELECT pow(2, log(2, sum((100 + change) / 100))), ticker FROM performance group by ticker

0 请登录后投票
   发表时间: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


不过,不知道这个效率如何,而且还有精确度的隐患,搞不好还不如存储过程或者全部抓出来用代码内存中计算。

等公布其他题目的答案...
2 请登录后投票
   发表时间:2008-08-20  
效率没问题。实际上一般的query效率都在查询上,至于对查询结果的计算,代价基本都可以忽略了。全抓出来到程序里的话,大量数据就要从数据库跑到应用程序里,这个开销也不小的。你的程序还要自己做group by,麻烦。

精度也没问题。我是用它来代替一段傻不拉唧的cursor循环的,计算结果(精确到小数点后两位)分毫不差。毕竟,double计算本身就是有误差的。

不过,我用的是自然对数,没试过2或者10.
1 请登录后投票
   发表时间: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的信息。

哦,要下火车了,等有时间再写了只能。
2 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics