I wish to update some rows of a table, univ_members, distinguished by their universe #, with their percentile or rank. That pctile value is calculated within a subquery. The subquery works fine by itself, but when fed to an update, fails with "Subquery returns more than one row". Line 23 in the code block is the offending expression. (This bit of code was gleaned from here (stackoverflow), here (a poster's blog) and ultimately (I should think) from a book, "High Performance MySQL", Schwartz, Zaitsev, Tkachenko, p. 648 et seq. in the 3rd edition)
I can't get it to work with the update. I'm beginning to suspect, after reading maybe 30 different posts here concerning update and "Subquery returns more than one row" that this isn't possible in MySQL in a single complex query, rather, I need to write a proc to do it via a loop. Can this be done in MySQL in a query or is a loop required? If with a query, how to do it? I want to mention that the fact that this query uses no self-join is very valuable for me, for performance. My production table starts with 112,000 recs and is likely to grow to 4 million.
Line 11 is where the pctile value is calculated. Line 13 is where the counter variables are initialized (once only, evidently). The inner select orders the set by std_dev_rank (line 15), the standard deviation of each of the pid's $vol (dollar-volume), both calculated previously. @curr is initially set to the highest of them (line 10), then on 13, compared with the previous one and is thus given a rank accordingly. Some simple arithmetic ensues. I omit variable definitions and other details necessary for you to try this yourselves, but I do include the univ_members table (line 19).
In case you're interested, this is out of my stock momentum/price estimation and prediction program. $-volume is a measure of liquidity. This is for preparing to select stocks (the pids) that are good candidates for further examination. (Each 'universe' is stocks across time, often overlapping. That's why the table can grow beyond the number of stocks in existence.)
1 create temporary table um like univ_members;
2 insert into um select * from univ_members where id_universe = @univ_num;
3
4 update um
5 set pctile =
6 (
7 select pctile from(
8 select
9 @prev := @curr,
10 @curr := std_dev_rank,
11 (100- ((@rank := IF(@prev= @curr, @rank, @rank+1))* @recs_per_pctile)) AS pctile
12 from univ_members
13 ,(select @curr := null, @prev := null, @rank := 0) x
14 where id_universe = @univ_num
15 order by std_dev_rank desc) q
16 where id_universe = @univ_num);
17
18 -- the univ_members table
19 create table univ_members
20 (
21 id_universe smallint(5) unsigned not null,
22 pid char(8) not null,
23 $vol double unsigned default null,
24 coef_cor double default null, -- 'distance' from median $vol
25 pctile double unsigned default null, -- another 'distance' measure
26 std_dev_rank double default null, -- individual std dev bracket
27
28 primary key pk_univ_members (id_universe, pid),
29 key k_$vol ($vol)
30 ) engine=InnoDB;