tags:

views:

90

answers:

3

I got mysql query like this:

SELECT name, 
   (SELECT timePing 
    FROM TerminalPings 
    WHERE terminalsId = Terminals.id
    ORDER BY timePing DESC LIMIT 1) 
 as timePing 
FROM Terminals`

It works good, but i need to select top 2 timePing's from TerminalPings, and name them like timePing1 and timePing2. What is the best way to do it?

EDIT: opps, sorry guys, but there actually is a relation between TerminalPings and Terminals... my bad :( I feel so stupid about forgetting to write WHERE statement :(

+1  A: 

Why are you using a subquery? This situation is the perfect candidate for a JOIN.

tylerl
I don't think TerminalPings and Terminals are related. That's why the OP is using a subquery. One could argue, that two separate queries could be used instead.
Daniel Vassallo
I would definitely argue that. :-) I don't understand the urge to shoehorn random unconnected queries into one complex, hard-to-maintain, subquery-ridden, probably-badly-optimised megaquery.
bobince
+2  A: 

2nd UPDATE:

Deriving @OMG Ponies' solution for the updated question:

SELECT     t.name,
           MAX(CASE WHEN x.rank = 1 THEN x.timeping END) AS timeping1,
           MAX(CASE WHEN x.rank = 2 THEN x.timeping END) AS timeping2
FROM       terminals t
LEFT JOIN  (
              SELECT   tp.timeping, tp.terminalsId,
                       IF(@curId <> terminalsId, @rownum := 0, 0),
                       @rownum := @rownum + 1 AS rank,
                       @curId := terminalsId
              FROM     terminalPings tp
              JOIN     (SELECT @rownum := 0, @curId := 0) r
              ORDER BY tp.terminalsId, tp.timeping DESC
           ) x ON (x.terminalsId = t.id AND x.rank <= 2)
GROUP BY   t.name;

New test case:

CREATE TABLE terminals (id int, name varchar(20));
INSERT INTO terminals VALUES (1, 'Terminal 1');
INSERT INTO terminals VALUES (2, 'Terminal 2');
INSERT INTO terminals VALUES (3, 'Terminal 3');

CREATE TABLE terminalPings (terminalsId int, timePing int);
INSERT INTO terminalPings VALUES (1, 5);
INSERT INTO terminalPings VALUES (1, 8);
INSERT INTO terminalPings VALUES (1, 4);
INSERT INTO terminalPings VALUES (2, 6);
INSERT INTO terminalPings VALUES (2, 5);
INSERT INTO terminalPings VALUES (3, 4);
INSERT INTO terminalPings VALUES (3, 7);
INSERT INTO terminalPings VALUES (3, 2);

New result:

+------------+-----------+-----------+
| name       | timeping1 | timeping2 |
+------------+-----------+-----------+
| Terminal 1 |         8 |         5 |
| Terminal 2 |         6 |         5 |
| Terminal 3 |         7 |         4 |
+------------+-----------+-----------+
3 rows in set (0.00 sec)

EXPLAIN output (MySQL 5.1.45)

+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
| id | select_type | table      | type   | possible_keys | key  | key_len | ref  | rows | Extra                           |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
|  1 | PRIMARY     | t          | ALL    | NULL          | NULL | NULL    | NULL |    3 | Using temporary; Using filesort |
|  1 | PRIMARY     | <derived2> | ALL    | NULL          | NULL | NULL    | NULL |    8 |                                 |
|  2 | DERIVED     | <derived3> | system | NULL          | NULL | NULL    | NULL |    1 | Using filesort                  |
|  2 | DERIVED     | tp         | ALL    | NULL          | NULL | NULL    | NULL |    8 |                                 |
|  3 | DERIVED     | NULL       | NULL   | NULL          | NULL | NULL    | NULL | NULL | No tables used                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
5 rows in set (0.00 sec)

UPDATE: @OMG Ponies' solution is faster than the one I suggested earlier (see the comments to both answers for reasons). I'm leaving the answer here for reference (this answer still returns a correct result, just much slower when you have many rows in the outer query: in the terminals table).

I would still stuggest using two separate queries. While SQL is a very expressive language, there's no need to do everything in one query... just like there's no need to do a task in a single line of code in other programming languages! The fact that MySQL made the subqueries in the solution below uncachable is one form of potential problems that you may face when using complicated queries (which may easily be broken down into two or more very simply queries).


Previous Answer:

You may want to use two separate queries instead. But just for the challenge, you may want to try the following:

SELECT name, 
       (
          SELECT timePing FROM
          ( 
             SELECT    timePing, @rowid := @rowid + 1 rownum
             FROM      TerminalPings, (SELECT @rowid := 0) rn
             ORDER BY  timePing DESC 
             LIMIT 1
          ) t
          WHERE t.rownum = 1
       ) AS timePing_1,
       (
          SELECT timePing FROM
          ( 
             SELECT    timePing, @rowid := @rowid + 1 rownum
             FROM      TerminalPings, (SELECT @rowid := 0) rn
             ORDER BY  timePing DESC 
             LIMIT 2
          ) t
          WHERE t.rownum = 2
       ) AS timePing_2 
FROM   Terminals;

Test case:

CREATE TABLE terminals (name varchar(20));
INSERT INTO terminals VALUES ('Terminal 1');
INSERT INTO terminals VALUES ('Terminal 2');
INSERT INTO terminals VALUES ('Terminal 3');

CREATE TABLE terminalPings (timePing int);
INSERT INTO terminalPings VALUES (1);
INSERT INTO terminalPings VALUES (2);
INSERT INTO terminalPings VALUES (3);
INSERT INTO terminalPings VALUES (4);

Result:

+------------+------------+------------+
| name       | timePing_1 | timePing_2 |
+------------+------------+------------+
| Terminal 1 |          4 |          3 |
| Terminal 2 |          4 |          3 |
| Terminal 3 |          4 |          3 |
+------------+------------+------------+
3 rows in set (0.01 sec)

EXPLAIN output (MySQL 5.1.45) (see the comments for @OMG Ponies' answer):

+----+----------------------+---------------+--------+---------------+------+---------+------+------+----------------+
| id | select_type          | table         | type   | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+----------------------+---------------+--------+---------------+------+---------+------+------+----------------+
|  1 | PRIMARY              | Terminals     | ALL    | NULL          | NULL | NULL    | NULL |    3 |                |
|  5 | UNCACHEABLE SUBQUERY | <derived6>    | ALL    | NULL          | NULL | NULL    | NULL |    2 | Using where    |
|  6 | DERIVED              | <derived7>    | system | NULL          | NULL | NULL    | NULL |    1 | Using filesort |
|  6 | DERIVED              | TerminalPings | ALL    | NULL          | NULL | NULL    | NULL |    4 |                |
|  7 | DERIVED              | NULL          | NULL   | NULL          | NULL | NULL    | NULL | NULL | No tables used |
|  2 | UNCACHEABLE SUBQUERY | <derived3>    | system | NULL          | NULL | NULL    | NULL |    1 |                |
|  3 | DERIVED              | <derived4>    | system | NULL          | NULL | NULL    | NULL |    1 | Using filesort |
|  3 | DERIVED              | TerminalPings | ALL    | NULL          | NULL | NULL    | NULL |    4 |                |
|  4 | DERIVED              | NULL          | NULL   | NULL          | NULL | NULL    | NULL | NULL | No tables used |
+----+----------------------+---------------+--------+---------------+------+---------+------+------+----------------+
Daniel Vassallo
That is a *lot* of subqueries :)
BoltClock
@Bolt: But none of them is correlated to the outer query. That basically means that the database will evaluate those subqueries only once (which is fast)... Edit: I updated my answer with a test case.
Daniel Vassallo
I was wrong in my previous comment (for this particular case)... See updated answer.
Daniel Vassallo
+1: Thx for providing the info. Had no idea the user variable made the subquery uncacheable
OMG Ponies
Thanks for you answer and sorry for my mistake!
Denis
@xbit: No worries mate. I updated my answer with a new query.
Daniel Vassallo
+2  A: 

Use:

   SELECT t.name,
          MAX(CASE WHEN x.rank = 1 THEN x.timeping END) AS timeping1,
          MAX(CASE WHEN x.rank = 2 THEN x.timeping END) AS timeping2
     FROM TERMINALS t
     JOIN (SELECT tp.timeping,
                  @rownum := @rownum + 1 AS rank
             FROM TERMINALPINGS tp
             JOIN (SELECT @rownum := 0) r
         ORDER BY tp.timeping DESC
            LIMIT 2) x
 GROUP BY t.name

I don't like that there's no relation between TERMINALS and TERMINALPINGS. It means every TERMINALS.name value will have the same timeping values...

EXPLAIN output (MySQL 5.1.49)

id  select_type  table        type      possible_keys  key  key_len  ref rows  Extra
------------------------------------------------------------------------------------
1   'PRIMARY'   '<derived2>'  'ALL'     ''             ''            ''  2     'Using temporary; Using filesort'
1   'PRIMARY'   't'           'ALL'     ''             ''            ''  3     'Using join buffer'
2   'DERIVED'   '<derived3>'  'system'  ''             ''            ''  1     'Using filesort'
2,  'DERIVED'   'tp'          'ALL'     ''             ''            ''  4     ''
3   'DERIVED'   ''            ''        ''             ''            ''        'No tables used'

EXPLAIN output (MySQL 4.1)

id  select_type  table        type      possible_keys  key  key_len  ref rows  Extra
------------------------------------------------------------------------------------
1   'PRIMARY'   't'           'ALL'     ''             ''            ''  3     'Using temporary; Using filesort'
1   'PRIMARY'   '<derived2>'  'ALL'     ''             ''            ''  2     ''
2   'DERIVED'   '<derived3>'  'system'  ''             ''            ''  1     'Using filesort'
2,  'DERIVED'   'tp'          'ALL'     ''             ''            ''  4     ''
3   'DERIVED'   ''            ''        ''             ''            ''        'No tables used'
OMG Ponies
@Daniel Vassallo: Thx for the comma fix. I was going to mention your missing derived table alias, but you fixed already.
OMG Ponies
@OMG: Thanks... Your solution looks smarter +1 :) Maybe a bit tricky to understand for newcomers.
Daniel Vassallo
OMG Ponies
@Daniel Vassallo: I added EXPLAIN output - mind posting yours?
OMG Ponies
@OMG: Done: Yours looks better, doesn't it?... The `UNCACHEABLE SUBQUERY` in mine is strange. Why do you think MySQL is doing that?
Daniel Vassallo
... It's because of the `@rowid` I guess. It looks like MySQL won't understand that it's not correlated.
Daniel Vassallo
... Got it: From the [docs](http://dev.mysql.com/doc/refman/5.1/en/explain-output.html): **Cacheability of subqueries is subject to the restrictions detailed in Section 7.6.3.1, “How the Query Cache Operates”. For example, referring to user variables makes a subquery uncacheable.**
Daniel Vassallo
Thanks for you answer and sorry for my mistake! Now i think i need to dig more into joins...
Denis