Advanced SQL Programming Fall 2002 Take-home exercises, Nov. 25th /*====================================================================*/ 1. (Question about ranking students based on GPA) I have to build a query which will rank all students in a single table: ContactID FileName GPA 7 Morales, Sara 2.9333 8 Wallace, Ben 2.7667 6 Bruce, Tricia 3.2833 9 Davis, Heather 2.9333 This query should produce a recordset which should look like this: ContactID FileName GPA RANKING 6 Bruce, Tricia 3.2833 1 9 Davis, Heather 2.9333 2 7 Morales, Sara 2.9333 2 8 Wallace, Ben 2.7667 3 but [with the query I am using] the ranking shows gaps (ranking #3 is missing): ContactID FileName GPA RANKING 6 Bruce, Tricia 3.2833 1 9 Davis, Heather 2.9333 2 7 Morales, Sara 2.9333 2 8 Wallace, Ben 2.7667 4 Is there any way to implement this using a single sql string? Query that was being used (in MS Access): SELECT t1.ContactID, t1.FileName, t1.GPA, (SELECT DISTINCT COUNT(GPA) FROM GPARanking as t2 WHERE t2.GPA > t1.GPA) + 1 AS [RANKING] FROM GPARanking as t1 ORDER BY t1.FileName, t1.GPA DESC Proposed solutions: ================== - Using MS SQL Server: SELECT t1.ContactID, t1.FileName, t1.GPA, (SELECT COUNT(DISTINCT(GPA)) FROM GPARanking as t2 WHERE t2.GPA > t1.GPA) + 1 AS [RANKING] FROM GPARanking as t1 ORDER BY t1.FileName, t1.GPA DESC - Using 2 queries (Access) or a view (Sybase, Oracle): Save a DISTINCT query first: qryUniqueGPA: SELECT DISTINCT GPA FROM GPARanking Now, solve your problem according to spec: SELECT t1.ContactID, t1.FileName, t1.GPA, (SELECT COUNT(*) FROM qryUniqueGPA as t2 WHERE t2.GPA > t1.GPA) + 1 AS [RANKING] FROM GPARanking as t1 ORDER BY t1.FileName, t1.GPA DESC /*===================================================================*/ 2. (Question about "moving" or "rolling" average) I am trying to make a Moving average in a query, but I can't figure how to do it. Tables name: Item_Stocks Field name: stocks.Item_Stocks Query name: Stocks_MA I want this moving average to be set on a 5 days period. The formula for a moving average is: 1st record: (S1+S2+...+S8) / 8 2nd record: (S2+S3+...+S9) / 8 ... Nth record: (Sn+Sn+1...+Sn+7) / 8 So far the best I could get is: Query1 (NOTE: For Sybase, think "view" and devise a way to find "top 8".) Select 8 records SELECT TOP 8 [Item_Stocks].[Item_Stocks] AS [8_Records] FROM Item_Stocks; Query2 (NOTE: distinctrow = distinct) SELECT DISTINCTROW Avg([Query1].[8_Records]) AS [MyAvg] FROM Query1; a. Rewrite the two queries so that they will work in Sybase. b. Do the entire calculation in one query. Sample data provided later by user: ================================== >Here is an example: >DATE STOCK >Monday Jan 1,2000 5 >Tuesday Jan 2,2000 7 >Wednesday Jan 3,2000 5 >Thursday Jan 4,2000 6 >Friday Jan 5,2000 2 >Monday Jan 8,2000 3 >Tuesday Jan 9,2000 4 >Wednseday Jan 10,2000 5 >The average will be 4.625 >and then > >Thursday Jan 11,2000 9 >Friday Jan 12,2000 7 >Friday Jan 19,2000 5 >Monday Jan 22,2000 2 >Tuesday Jan 23,2000 10 >Thursday Jan 25,2000 6 >Friday Jan 26,2000 8 >Monday Jan 29,2000 8 >The average will be 6.875 Simplest Sybase solution for "top n" rows: ========================================= set rowcount n go E.g., for first 8 prices by date in titles (pubs2): set rowcount 8 go select t1.price, t1.pubdate, (select count(*) from titles t2 where t2.pubdate< t1.pubdate) +1 Rank from titles t1 order by t1.pubdate go price pubdate Rank ------------------------ -------------------------- ----------- 11.95 Jun 12 1985 12:00AM 1 2.99 Jun 18 1985 12:00AM 2 2.99 Jun 30 1985 12:00AM 3 19.99 Jun 12 1986 12:00AM 4 22.95 Jun 30 1986 12:00AM 5 14.99 Jun 12 1987 12:00AM 6 20.00 Jun 12 1987 12:00AM 6 19.99 Jun 22 1987 12:00AM 8 (8 rows affected) *** Now turn rowcount off! *** set rowcount 0 go Solution to the two questions posed, in general terms: ===================================================== a. Create a view with 8 rows and calculate the average using the view. b. Select average in "ranking" query using the last (8th) date as a condition: 1> select avg(t1.price), t1.pubdate, 2> (select count(*) 3> from titles t2 4> where t2.pubdate < t1.pubdate) + 1 5> from titles t1 6> where t1.pubdate <= '6/22/1987' 7> order by t1.pubdate 8> 9> go pubdate ------------------------ -------------------------- ----------- 14.48 Jun 12 1985 12:00AM 1 14.48 Jun 18 1985 12:00AM 2 14.48 Jun 30 1985 12:00AM 3 14.48 Jun 12 1986 12:00AM 4 14.48 Jun 30 1986 12:00AM 5 14.48 Jun 12 1987 12:00AM 6 14.48 Jun 12 1987 12:00AM 6 14.48 Jun 22 1987 12:00AM 8 (8 rows affected) *** Remember to turn rowcount off! *** Solution proposed in the discussion group: ========================================== Moving averages are normally continuous: ie. the average of the eight records after Jan 12, then 8 after Jan 19, 8 after Jan 22, etc. One way to do this might be to apply a ranking query to your data first, to get the following result set: Rank StartDate Stock 1 Jan 1,2000 5 2 Jan 2,2000 7 3 Jan 3,2000 5 4 Jan 4,2000 6 5 Jan 5,2000 2 6 Jan 8,2000 3 7 Jan 9,2000 4 8 Jan 10,2000 5 The SQL to calculate the moving average would then be something like: SELECT StartDate,Stock, (SELECT AVG(Stock) FROM MyQuery AS Q2 WHERE Q2.Rank > Q1.Rank AND Q2.Rank < Q1.Rank + 8) AS MovingAverage FROM MyQuery AS Q1 Which would generate the following results: StartDate Stock MovingAverage Jan 1,2000 5 4.25 Jan 2,2000 7 5.125 'Did these by hand Jan 3,2000 5 5.125 'and might have got Jan 4,2000 6 5.125 'them wrong!!! Jan 5,2000 2 4.25 Jan 8,2000 3 5.675 Jan 9,2000 4 6.00 Jan 10,2000 5 5.375 Last response from original user: ================================ It works great, thanks, Final solution SELECT Q1.ID, Q1.DATE, Q1.STOCK, (SELECT AVG(STOCK) FROM THETABLE Q2 WHERE Q2.ID > Q1.ID-1 AND Q2.ID < Q1.ID-1 + (SELECT TOP 1 [P].[DELTA] FROM [P];);) AS MA FROM THETABLE AS Q1; Note: - P.DELTA is a variable number for the moving average in the table P - ID is the autonumber set by access as a primary key (set to 1 by default) - I use Q1.ID-1 in order to have the moving average include the current day (since the autonumber starts at 1) /*=======================================================================*/