I have a table with two columns [id, value] both numeric.
In this example:
[ id, value ]
[ 1, 6 ]
[ 2, 4 ]
[ 3, 10 ]
[ 4, 2 ]
[ 5, 7 ]
[ 6, 3 ]
For a given id I'd like to retrieve the top 3 id's (those with highest value), their top position and if the given id is not in the top 3, also get its position, id and value:
Example 1: ask_id = 5 Return:
[ position, id, value ]
[ 1, 3, 10 ]
[ 2, 5, 7 ]
[ 3, 1, 6 ]
Example 2: ask_id = 4. Return:
[ position, id, value ]
[ 1, 3, 10 ]
[ 2, 5, 7 ]
[ 3, 1, 6 ]
[ 6, 4, 2 ]
So the important points are:
How to get for the position column?
How to get the additional row if possible (anyway there's no problem if I need two queries)?
select t2.pos, t1.id, t1.value
from test as t1
inner join
(select id, value, #pos:=if(#pos is null, 0, #pos)+1 as pos
from test order by value desc) as t2
on t1.id=t2.id
where t2.pos<=3 or t2.id={$ask_id}
order by t2.pos;
Basically, the idea is like this:
Rank the rows by value.
Retrieve rows where at least one of the following is true:
position BETWEEN 1 AND 3
id = #given_id
These posts give examples of how you could substitute ranking functions (at least the most fundamental of them, ROW_NUMBER()) in MySQL:
ROW_NUMBER() in MySQL
MSSQL Row_Number() over(order by) in MySql
This method should be used with caution, though, as this article explains.
That said, one possible implementation of the above steps might look like this:
SET #pos = 0;
SELECT
position,
id,
value
FROM (
SELECT
id,
value,
#pos := #pos + 1 AS position
FROM atable
ORDER BY value DESC
) s
WHERE position BETWEEN 1 AND 3
OR id = #given_id
ORDER BY position
Tested in MySQL
to retrieve the top 3 id's (those with highest value) with position in ascending order.
set #num = 0;
SELECT #num := #num + 1 as position_sequence,id,value FROM tablename
ORDER BY value desc
limit 3;
I've not (yet) tested the selected answer in MySQL on the interesting cases where there are ties in the top three places, but I have tested this code in Informix on those cases, and it produces the answer I think should be produced.
Assuming that the table is called leader_board:
CREATE TABLE leader_board(id INTEGER NOT NULL PRIMARY KEY, value INTEGER NOT NULL);
INSERT INTO leader_board(id, value) VALUES(1, 6);
INSERT INTO leader_board(id, value) VALUES(2, 4);
INSERT INTO leader_board(id, value) VALUES(3, 10);
INSERT INTO leader_board(id, value) VALUES(4, 2);
INSERT INTO leader_board(id, value) VALUES(5, 7);
INSERT INTO leader_board(id, value) VALUES(6, 3);
This query works on the data shown, assuming that the special ID is 4:
SELECT b.position - c.tied + 1 AS standing, a.id, a.value
FROM leader_board AS a
JOIN (SELECT COUNT(*) AS position, d.id
FROM leader_board AS d
JOIN leader_board AS e ON (d.value <= e.value)
GROUP BY d.id
) AS b
ON a.id = b.id
JOIN (SELECT COUNT(*) AS tied, f.id
FROM leader_board AS f
JOIN leader_board AS g ON (f.value = g.value)
GROUP BY f.id
) AS c
ON a.id = c.id
WHERE (a.id = 4 OR (b.position - c.tied + 1) <= 3) -- Special ID = 4; Top N = 3
ORDER BY position, a.id;
Output on original data:
standing id value
1 3 10
2 5 7
3 1 6
6 4 2
Explanation
The two sub-queries are closely related, but they produce different answers. At one time, I used two temporary tables to hold those results. In particular, the first sub-query (AS b) produces a position, but when there are ties, the position is the lowest rather than the highest of the tied positions. That is, given:
ID Value
1 10
2 7
3 7
4 7
The outputs will be:
Position ID
1 1
4 2
4 3
4 4
However, we would like to count them as:
Position ID
1 1
2 2
2 3
2 4
So, the corrected position is the original position minus the number of tied values (3 for ID ∈ { 2, 3, 4 }, 1 for ID 1) plus 1. The second sub-query returns the number of tied values for each ID. There might be a neater way to do that calculation, but I'm not sure what it is at the moment.
Special cases
However, the code should demonstrate that it handles the cases where:
There are 2 or more ID values with the same top value.
There are 2 or more ID values with the same second highest top score (but the top one is unique).
There are 2 or more ID values with the same third highest top score (but the top two are unique).
To save rewriting the query each time, I converted it into an Informix-style stored procedure which take both the Special ID and the Top N (defaulting to 3) values that should be displayed and made them into parameters of the procedure. (Yes, the notation in the RETURNING clause is weird.)
CREATE PROCEDURE leader_board_standings(extra_id INTEGER, top_n INTEGER DEFAULT 3)
RETURNING INTEGER AS standing, INTEGER AS id, INTEGER AS value;
DEFINE standing, id, value INTEGER;
FOREACH SELECT b.position - c.tied + 1 AS standing, a.id, a.value
INTO standing, id, value
FROM leader_board AS a
JOIN (SELECT COUNT(*) AS position, d.id
FROM leader_board AS d
JOIN leader_board AS e ON (d.value <= e.value)
GROUP BY d.id
) AS b
ON a.id = b.id
JOIN (SELECT COUNT(*) AS tied, f.id
FROM leader_board AS f
JOIN leader_board AS g ON (f.value = g.value)
GROUP BY f.id
) AS c
ON a.id = c.id
WHERE (a.id = extra_id OR (b.position - c.tied + 1) <= top_n)
ORDER BY position, a.id
RETURN standing, id, value WITH RESUME;
END FOREACH;
END PROCEDURE;
This can be invoked to produce the same result as before:
EXECUTE PROCEDURE leader_board_standings(4);
To illustrate the various cases outlined above, add and remove extra rows:
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
3 1 6
6 4 2
INSERT INTO leader_board(id, value) VALUES(10, 10);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
1 10 10
3 5 7
7 4 2
INSERT INTO leader_board(id, value) VALUES(11, 10);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
1 10 10
1 11 10
8 4 2
INSERT INTO leader_board(id, value) VALUES(12, 10);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
1 10 10
1 11 10
1 12 10
9 4 2
DELETE FROM leader_board WHERE id IN (10, 11, 12);
EXECUTE PROCEDURE leader_board_standings(6, 4); -- Special ID 6; Top 4
1 3 10
2 5 7
3 1 6
4 2 4
5 6 3
INSERT INTO leader_board(id, value) VALUES(7, 7);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
2 7 7
7 4 2
INSERT INTO leader_board(id, value) VALUES(13, 7);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
2 7 7
2 13 7
8 4 2
INSERT INTO leader_board(id, value) VALUES(14, 7);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
2 7 7
2 13 7
2 14 7
9 4 2
DELETE FROM leader_board WHERE id IN(7, 13, 14);
INSERT INTO leader_board(id, value) VALUES(8, 6);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
3 1 6
3 8 6
7 4 2
INSERT INTO leader_board(id, value) VALUES(9, 6);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
3 1 6
3 8 6
3 9 6
8 4 2
INSERT INTO leader_board(id, value) VALUES(15, 6);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
3 1 6
3 8 6
3 9 6
3 15 6
9 4 2
EXECUTE PROCEDURE leader_board_standings(3); -- Special ID 3 appears in top 3
1 3 10
2 5 7
3 1 6
That all looks correct to me.
Related
Here is a simplified version of my table:
It contains the columns
employee_id column2 column3 x y
and they are all NUMERIC
I am having trouble writing a complicated MySQL query. I want to get all the employeeid and column3 values for all employees that have the same value in column2 as another employee and have DIFFERENT values for x and y PAIR from every other employee. For example, if the following 4 rows were in table:
2 100 123.456 5 7
1 234 123.456 5 7
3 100 456.789 5 10
4 100 123.456 5 7
The rows 2 100 123.456 5 7 and 3 100 456.789 5 10 should be obtained because they have different employee ids (2 vs 3), the same value for column2 (100 and 100), and different x, y pair: (Employee 2 has x = 5 and y = 7, which is distinct from x = 5 and y = 10).
How can I compare the documents of table with other documents within itself?
SELECT e1.*
FROM employee e1
JOIN employee e2
ON e1.employee_id <> e2.employee_id
AND e1.column2 = e2.column2
AND e1.X <> e2.X
AND e1.Y <> e2.Y
But I guess your last condition should be
AND (e1.X <> e2.X OR e1.Y <> e2.Y)
Here is a simplified version of my table:
It contains the columns
employee_id column2 column3 x y
and they are all NUMERIC
I am having trouble writing a complicated MySQL query. I want to get all the employeeid and column3 values for all employees that have the same value in column2 as another employee and have DIFFERENT values for x and y PAIR from every other employee. For example, if the following 4 rows were in table:
2 100 123.456 5 7
1 234 123.456 5 7
3 100 456.789 5 10
4 100 123.456 5 7
The rows 2 100 123.456 5 7 and 3 100 456.789 5 10 should be obtained because they have different employee ids (2 vs 3), the same value for column2 (100 and 100), and different x, y pair: (Employee 2 has x = 5 and y = 7, which is distinct from x = 5 and y = 10).
How can I compare the documents of table with other documents within itself?
SELECT e1.*
FROM employee e1
JOIN employee e2
ON e1.employee_id <> e2.employee_id
AND e1.column2 = e2.column2
AND e1.X <> e2.X
AND e1.Y <> e2.Y
But I guess your last condition should be
AND (e1.X <> e2.X OR e1.Y <> e2.Y)
I have a table:
id firstval secondval
1 4 5
2 5 4
3 3 3
4 6 6
5 7 8
6 9 8
7 3 3
8 3 3
The first thing I need to do is count the number of times secondval > firstval. This is obviously no problem.
However, the thing I'm struggling with is how to then count how many times (for each instance of secondval > firstval) the next row satisfies the condition secondval < firstval
So in this example there are two rows that would satisfy the first rule id 1 & 5 and two for the second rule, the next rows id 2 and 6.
SELECT id, #prevGreater AND secondval < firstval AS discrepancy,
#prevGreater := secondval > firstval AS secondGreater
FROM (SELECT * FROM YourTable ORDER BY id) AS x
CROSS JOIN (SELECT #prevGreater := false) AS init
DEMO
SELECT * from table t1
INNER JOIN table t2 on t1.ID+1=t2.ID -- here we join on t2.ID is t1.ID+1
WHERE t1.secondval>t1.firstval AND t2.secondval<t2.firstval
Now you can use COUNT statement as you want :)
DECLARE #YourTable TABLE
(id int, firstval int, secondval int)
INSERT INTO #YourTable
SELECT 1, 4, 5
UNION ALL
SELECT 2, 5, 4
UNION ALL
SELECT 3, 3, 3
UNION ALL
SELECT 4, 6, 6
UNION ALL
SELECT 5, 7, 8
UNION ALL
SELECT 6, 9, 8
UNION ALL
SELECT 7, 3, 3
UNION ALL
SELECT 8, 3, 3
SELECT ID
,CASE
WHEN SECONDVAL>FIRSTVAL THEN 0
WHEN FIRSTVAL>SECONDVAL THEN 1
ELSE 0
END AS DISCREPANCY
,CASE
WHEN SECONDVAL>FIRSTVAL THEN 1
WHEN FIRSTVAL>SECONDVAL THEN 0
ELSE 0
END AS SECONDGREATER
FROM #YourTable
You could try this one.
run_software
runID Release
1 X
2 X
3 Y
4 Z
5 Y
6 X
7 Y
8 Z
9 X
10 Z
testcase
testID runID Result
T_1 1 PASS
T_2 1 FAIL
T_3 1 PASS
T_4 2 PASS
T_5 2 FAIL
T_6 3 PASS
T_7 4 FAIL
T_8 3 PASS
T_9 3 FAIL
T_10 5 PASS
T_11 5 FAIL
T_12 3 PASS
1) From run_software table we can understand X software run on runID's 1, 2,6,9
2) Take runID - 1 and come to testcase table.
Here we have 7 testID's with runID 1.
From these 7 testID's we need to measure the TC count and percentage of PASS/FAIL using group by Result and runID.
AIM: Ultimate aim is to find the latest 3 Release's and its runID's with PASS percentage by considering max testcase count.
Eg. if 'X' release executed on runID's 1, 2, 3, 4 with each 10, 12, 9, 21 testcases respectively, we should consider runID 4 for release 'X' to measure the 'PASS %'
Desired OutPut:
considering PASS% is > 60
Release runID Result PASS %
X 1 PASS 66.66
Y 3 PASS 75
Z 4 FAIL 0
To be understanding
Release 'X' has runID's - 1, 2 , 6, 9 with 3, 2, 0 , 0 TestID's respecively
Hence, X finalized runID '1' with 66.66 as PASS% (2 PASS & 1 FAIL)
Your question is not very clear actually, but it should be something like that I guess:
SELECT r.Release, r.runID, (CASE WHEN subq.PassPerc >= 60 THEN 'PASS' ELSE 'FAIL' END) AS Result, subq.PASSPerc
FROM run_software AS r
INNER JOIN (SELECT p.runID, (100 * p.passed / t.total) AS PASSPerc
FROM (SELECT runID, COUNT(*) AS passed FROM testcase WHERE Result = 'PASS' GROUP BY runID) AS p
INNER JOIN (SELECT runID, COUNT(*) AS total FROM testcase GROUP BY runID) AS t ON t.runID = p.runID
GROUP BY p.runID, p.passed, t.total) AS subq
ON subq.runID = r.runID
GROUP BY r.Release, r.runID, subq.PASSPerc
First of all, I don't think my title is good, but I couldn't think of a better one. Please feel free to change it.
I have a table that keeps record of a pair of rows.
The following is a sample table structure.
table History
user_id row_1 row_2
2 1 2
2 1 3
table Rows
row_id
1
2
3
4
5
6
I would like to query to get only a pair of rows that are not in the 'History' table.
so..I like to get the following result.
row pairs:
1,4
1,5
1,6
2,3
2,4
2,5
2,6
and so on
Can I do it with one query?
Just added:
I just made a query that works, but I am not sure about the performance.
select r1.row_id, r2.row_id from rows as r1 cross join rows as r2
where r1.row_id!=r2.row_id and ( r1.row_id + r2.row_id) not in (select row_1 + row_2 from history)
order by r1.row_id desc
Would it be super slow?
Something like this. You haven't made the correlations clear between the fields but this should be easy to adapt.
select h.row_id r1, r.row_id r2
from rows h
cross join rows r
left join history h2 on h2.row_1=h.row_id and h2.row_2=r.row_id
where h2.row_1 is null
The CROSS JOIN produces all the possible combinations of row_id x row_id
THE LEFT JOIN attempts to find the combination in the history table
The WHERE clause picks out where the combination is not found
I think this might work:
SELECT DISTINCT
CASE WHEN r1.row_id < r2.row_id THEN r1.row_id ELSE r2.row_id END AS row_id_1,
CASE WHEN r1.row_id < r2.row_id THEN r2.row_id ELSE r1.row_id END AS row_id_2
FROM Rows AS r1
INNER JOIN Rows AS r2 /* ON r1.row_id <> r2.row_id */
WHERE (r1.row_id, r2.row_id) NOT IN (
SELECT row_1, row_2
FROM history
UNION
SELECT row_2, row_1
FROM history
)
ORDER BY 1, 2
Returns:
1, 1
1, 4
1, 5
1, 6
2, 2
2, 3
2, 4
2, 5
2, 6
3, 3
3, 4
3, 5
3, 6
4, 4
4, 5
4, 6
5, 5
5, 6
6, 6
This query will be super slow.