How to get a regular rank without OLAP functions - sql

I want to do regular rank rows without OLAP functions(no RANK() or sth like that). I want to create a "Top-10" list of the users with the shortes average response time. I got this query below:
WITH table_avg (NICKNAME, AVG_RESPONSETIME) AS
(SELECT r.nickname,
AVG ((timestampdiff(32,char(timestamp(e1.date) -timestamp(e2.date)))))AS AVG_RESPONSETIME
FROM email e1,
email e2,
address a,
use u,
user r
WHERE e2.id=e1.in_reply_to
AND e1.from=a.id
AND a.id=u.address
AND u.user=r.id
GROUP BY r.nickname HAVING count(r.nickname)>=3
ORDER BY 1 ASC,2 ASC)
SELECT a.NICKNAME,
a.AVG_RESPONSETIME,
count(DISTINCT b.AVG_RESPONSETIME) AS RANK
FROM table_avg a,
table_avg b
WHERE b.AVG_RESPONSETIME<=a.AVG_RESPONSETIME
GROUP BY a.NICKNAME,
a.AVG_RESPONSETIME HAVING count(DISTINCT b.AVG_RESPONSETIME) <=10
ORDER BY RANK ASC,
a.NICKNAME ASC
The result is:
NICKNAME AVG_RESPONSETIME RANK
----------- ---------------- ----
cyber426 1 1
neo927 1 1
neo259 3 2
cypher15 4 3
fool28 5 4
cyber974 6 5
hacker285 6 5
dau719 7 6
trinity407 7 6
fool380 8 7
wiesel509 8 7
dau814 10 8
morpheus462 10 8
neo517 10 8
drago831 11 9
drago861 13 10
How can I get the rank to skip the number 2? It should be number 3, the result that I'd like is:
NICKNAME AVG_RESPONSETIME RANK
---------- ------------------- -----
cyber426 1 1
neo927 1 1
neo259 3 3
cypher15 4 4
fool28 5 5
cyber974 6 6
hacker285 6 6
dau719 7 8
trinity407 7 8
fool380 8 10
wiesel509 8 10
I cannot use RANK function, this is a exercise that I should do.
Thanks in advance!!!

I found a complicated way to do what you wanted to do with a standard select statement. You still have to find out why it works (I know why). I don't give you the explanation, so that you need to think about it and have a chance to learn. An easier option would be to use cursors.
SELECT a.NICKNAME,
a.AVG_RESPONSETIME,
count(b.AVG_RESPONSETIME) + 1 - count(case (a.AVG_RESPONSETIME - b.AVG_RESPONSETIME) when 0 then 1 else NULL end) as rank
FROM table_avg a, table_avg b
WHERE b.AVG_RESPONSETIME<=a.AVG_RESPONSETIME
GROUP BY a.NICKNAME, a.AVG_RESPONSETIME HAVING count(b.AVG_RESPONSETIME) <=10
ORDER BY RANK ASC, a.NICKNAME ASC;
tested with ideone.com - http://ideone.com/9F4gFo

Related

calculate avg(value) for last 10 records postgresql

i have a tricky task,
lets assume we have table "Racings", and there we have columns TRACK, CAR, CIRCLE_TIME
here is an example how data could be look like:
id
track
car
circle_time
10
1
10
15
9
1
10
14
8
1
10
16
7
1
10
15
6
1
10
13
5
2
10
7
4
2
10
4
3
2
10
5
2
3
10
8
1
3
10
10
what i need, i to add one more coumn like avg3_circle_time which will show me an average time from last 3 circle_time from each track, example:
id
track
car
circle_time
avg3_circle_time
10
1
10
15
15
9
1
10
14
15
8
1
10
16
14.6
7
1
10
15
null
6
1
10
13
null
5
2
10
7
5.3
4
2
10
4
null
3
2
10
5
null
2
3
10
8
null
1
3
10
10
null
I know how it could works in oracle, you could use something like rowid, but in case of postgresql i don't know, i have a draft like .....avg(circle_time) OVER(PARTITION BY track,car.....) as avg3_circle_time..... help me to solve that task please
You can use window functions to calculate moving averages:
SELECT track, id, car, circle_time, AVG(circle_time) OVER (
PARTITION BY track
ORDER BY id
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
)
FROM t
ORDER BY track, id
Depending on your definition of previous three, the window could be ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING.
If you want only values when at least 3 circles available
select *
, case when lag(id, 2) over(partition by TRACK, CAR order by id) is not null then
avg(CIRCLE_TIME) over(partition by TRACK, CAR order by id rows between 2 preceding and current row) end a
from Racing
order by id desc;
db<>fiddle
Output
id track car circle_time a
10 1 10 15 15.0000000000000000
9 1 10 14 15.0000000000000000
8 1 10 16 14.6666666666666667
7 1 10 15 null
6 1 10 13 null
5 2 10 7 5.3333333333333333
4 2 10 4 null
3 2 10 5 null
2 3 10 8 null
1 3 10 10 null
Use LAED() then checking one of the next 2 rows is NULL or not. THEN sum of three values for calculating average.
-- PostgreSQL
SELECT *
, CASE WHEN next_circle_time IS NULL OR next_next_circle_time IS NULL
THEN NULL
ELSE ((t.circle_time + COALESCE(next_circle_time, 0) + COALESCE(next_next_circle_time, 0)) / 3 :: DECIMAL) :: DECIMAL(10, 1)
END avg_circle_time
FROM (SELECT *
, LEAD(circle_time, 1) OVER (PARTITION BY track ORDER BY id DESC) next_circle_time
, LEAD(circle_time, 2) OVER (PARTITION BY track ORDER BY id DESC) next_next_circle_time
FROM Racings) t
Another way Use AVG()
SELECT *
, CASE WHEN LEAD(circle_time, 2) OVER (PARTITION BY track ORDER BY id DESC) IS NULL
OR LEAD(circle_time, 1) OVER (PARTITION BY track ORDER BY id DESC) IS NULL
THEN NULL
ELSE AVG(circle_time) OVER (PARTITION BY track ORDER BY id DESC ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
END :: DECIMAL(10, 2) avg_circle_time
FROM Racings
Please check from url where both query exists https://dbfiddle.uk/?rdbms=postgres_11&fiddle=f0cd868623725a1b92bf988cfb2deba3
Several of the posted answers end up repeating the window definition. You can avoid this with the window clause:
select *,
case when row_number() over(track_window) > 2
then trunc(avg(CIRCLE_TIME) over(track_window rows 2 preceding), 1)
end a
from Racing
window track_window as (partition by track order by id)
order by id desc
Note how, in this sample, track_window is defined once, then reused for both row_number and avg. In the latter case, the window clause is embellished with a frame as well (rows 2 preceding).

SQL get all children

So I have hierarchical data that is organized in departments, with each department having a parent department except for the top one. I now want to write a SELECT statement to select this hierarchical structure in a cumulative way. That means that for every level in the hierarchy I want to see all the entries that are children of that.
For example, if I have the following tables:
Departments
ID PARENT_ID
1 null
2 1
3 1
4 2
5 2
6 3
7 3
Employees
ID DEPT
1 2
2 2
3 3
4 4
5 5
6 6
7 7
8 2
9 3
10 4
11 5
12 6
13 7
14 2
15 3
16 4
17 5
I would like to have something like the following result:
ID_E ROOT DEPT
1 null 1
1 1 2
2 null 1
2 1 2
3 null 1
3 1 3
4 null 1
4 1 2
4 2 4
5 null 1
...
I have looked around and fiddled a bit but just cannot get it to work.
I thought this would do the trick but it gives weird results (meaning many duplicate rows):
SELECT connect_by_root(dept.id) AS dept_id,
CONNECT_BY_ROOT(dept.parent_id) AS parent_id,
emp.id AS id_e
FROM emp
RIGHT JOIN dept ON emp.dept = dept.id
CONNECT BY PRIOR dept.id = dept.parent_id
EDIT: Here is a fiddle of the scenario
I came up with a solution, using a recursive CTE to parse the hierarchy and retrieve each possible way a department can connect to the root, which is then joined with the employee table.
Could you give it a try, and let me know if it works?
WITH RCTE_DEPT(id,root,parent_id) AS(
SELECT id,parent_id, id
FROM dept
UNION ALL
SELECT dept.id,root,RCTE_DEPT.parent_id
FROM dept
JOIN RCTE_DEPT ON
dept.parent_ID = RCTE_DEPT.id)
SELECT emp.id as ID_E, RCTE_DEPT.root as ROOT, RCTE_DEPT.parent_id as DEPT
FROM emp
JOIN RCTE_DEPT ON emp.DEPT = RCTE_DEPT.id
ORDER BY ID_E ASC, ROOT ASC, DEPT ASC
Here is a fiddle.

Row Number with specific window size

I want to group records by row numbers.
Like from row 1-3 in group 1 , 4-6 in group 2 , 7-9 in group 3 and so on.
Suppose below is the table structure:
Row NumberDataValue
1 A 10
2 A 5
3 A 1
4 A 33
5 A 2
6 A 127
1 B 1
2 B 0
3 B 7
4 B 7
5 B 5
6 B 8
7 B 1
8 B 0
I want a output like this:
GroupValue
1 10
1 5
1 1
2 33
2 2
2 127
1 1
1 0
1 7
2 7
2 5
2 8
3 1
3 0
I am using Oracle 11G.
I can achieve this using PL/SQL. But I have to use SQL only. As I have to use this query in a reporting tool.
If this is a duplicate question please provide the link of the answered question.
Subtract 1 from the column "RowNumber" and divide by 3.
Then use TRUNC() to get the integer part:
SELECT TRUNC(("RowNumber" - 1) / 3) + 1 "Group",
"Value"
FROM tablename
See the demo.
I would assume the name of the first column is ordering.
You can do:
select
1 + trunc(row_number() over(partition by data order by ordering) - 1) / 3,
value
from t
What you show looks like the output from something like this:
select ceil(rn/3) as grp, value
from your_table
order by rn;
Note that "row number" and "group" are reserved words/phrases which should not be used as column names. I used rn and grp instead.
I think the ceiling function is the simplest way to arrive at what you want. If you want to base it on the RowNumber column:
select ceil( RowNumber / 3.0) as grouping
If you want to calculate it yourself using row_number():
select ceil( row_number() over (order by RowNumber) / 3.0 ) as grouping

How to get average runs for each over in SQL?

The first six balls mean first over, next six balls mean second over & so on than how to get average runs for each over.
input as
Ball no Runs
1 4
2 6
3 3
4 2
5 6
6 1
1 2
2 4
3 6
4 3
5 1
6 1
1 2
output should be:
Over no avg runs
1 3.66
2 2.83
As Gordon Linoff suggested, SQL table represents unordered sets, So you have to use an ordered column in your table. If you can use such a column you may use below query -
SELECT Over_no AVG(Runs) avg_runs
FROM (SELECT Ball_no, Runs, CEIL(ROW_NUMBER() OVER(ORDER BY ORDER_COLUMN, Ball_no) RN / 6) Over_no
FROM YOUR_TABLE)
GROUP BY Over_no;
I have managed to solve my problem with the following query:
SELECT ROWNUM OVER_NO, AVG_RUNS
FROM(
SELECT ROWNUM RN,
ROUND(AVG(RUNS)OVER(ORDER BY ROWNUM RANGE BETWEEN CURRENT ROW AND 5 FOLLOWING),2) AVG_RUNS
FROM TABLE_NAME
)
WHERE RN=1 OR RN=7;

Queries to fetch items based on top Ranks

I have a table which rank the items which i have.
I need a queries which will pick up only the top 2 ranks for a given item, the rank may not be in sequential order.
I need to fetch the item with least two ranks, there will same rank for two items as well.
Here is the snap shot of my table.
Item Id Supp Id Rank
1 2 2
1 1 7
1 7 5
1 9 11
2 67 4
2 9 14
2 10 14
2 34 4
2 25 3
2 60 3
2 79 5
my requirement is if I enter 2 i should get the result as below
Item Id Supp_id Rank
2 25 3
2 60 3
2 67 4
2 34 4
I am using oracle 10g version.
As one of the approaches it can be done as follows. Here we are using dense_rank() over() analytic function to assign a rank for a row in a ordered by rank group of rows .
select t.item_id
, t.supp_id
, t.rank
from (select item_id
, supp_id
, rank
, dense_rank() over(partition by item_id
order by rank) as rn
from t1
where item_id = 2
) t
where t.rn <= 2
Result:
ITEM_ID SUPP_ID RANK
---------- ---------- ----------
2 25 3
2 60 3
2 67 4
2 34 4
SQLFiddle Demo