SO,
The problem
My question is about - how to join table in MySQL with itself in reverse order? Suppose I have:
id name 1 First 2 Second 5 Third 6 Fourth 7 Fifth 8 Sixth 9 Seventh 13 Eight 14 Nine 15 Tenth
-and now I want to create a query, which will return joined records in reverse order:
left_id name right_id name 1 First 15 Tenth 2 Second 14 Nine 5 Third 13 Eight 6 Fourth 9 Seventh 7 Fifth 8 Sixth 8 Sixth 7 Fifth 9 Seventh 6 Fourth 13 Eight 5 Third 14 Nine 2 Second 15 Tenth 1 First
My approach
I have now this query:
SELECT
l.id AS left_id,
l.name,
(SELECT COUNT(1) FROM sequences WHERE id<=left_id) AS left_order,
r.id AS right_id,
r.name,
(SELECT COUNT(1) FROM sequences WHERE id<=right_id) AS right_order
FROM
sequences AS l
LEFT JOIN
sequences AS r ON 1
HAVING
left_order+right_order=(1+(SELECT COUNT(1) FROM sequences));
-see this fiddle for sample structure & code.
Some background
There's no use case for that. I was doing that in application before. Now it's mostly curiosity if there's a way to do that in SQL - that's why I'm seeking not just 'any solution' (like mine) - but as simple as possible solution. Source table will always be small (<10.000 records) - so performance is not a thing to care, I think.
The question
Can my query be simplified somehow? Also, it's important not to use variables. Order could be included in result (like in my fiddle) - but that's not mandatory.
Answers. You can use Except and Not Exists and LEFT OUTER JOIN.
a LEFT OUTER JOIN basically returns all rows from table A, with or without matching rows from table B. when there is no matching row from B, then the columns from B are all set to NULL in the result row. those are the rows we want to keep, and that's what the WHERE clause does.
If you consider an inner join as the rows of two tables that meet a certain condition, then the opposite would be the rows in either table that don't.
The RIGHT JOIN keyword returns all records from the right table (table2), and the matching records from the left table (table1). The result is 0 records from the left side, if there is no match.
Without the SQL standard RANK() OVER(...), you have to compute the ordering yourself as you discovered.
The RANK() of a row is simply 1 + the COUNT() of all better-ranked rows. (DENSE_RANK(), for comparison, is 1 + the COUNT() of all DISTINCT better ranks.) While RANK() can be computed as a scalar subquery in your SELECT projection — as, e.g., you have done with SELECT (SELECT COUNT(1) ...), ...
— I tend to prefer joins:
SELECT lft.id AS "left_id", lft.name AS "left_name",
rgt.id AS "right_id", rgt.name AS "right_name"
FROM ( SELECT s.id, s.name, COUNT(1) AS "rank" -- Left ranking
FROM sequences s
LEFT JOIN sequences d ON s.id <= d.id
GROUP BY 1, 2) lft
INNER JOIN ( SELECT s.id, s.name, COUNT(1) AS "rank" -- Right ranking
FROM sequences s
LEFT JOIN sequences d ON s.id >= d.id
GROUP BY 1, 2) rgt
ON lft.rank = rgt.rank
ORDER BY lft.id ASC;
The only thing i can think to be improved is
SELECT
l.id AS left_id,
l.name ln,
(SELECT COUNT(1) FROM sequences WHERE id<=left_id) AS left_order,
r.id AS right_id,
r.name rn,
(SELECT COUNT(1) FROM sequences WHERE id>=right_id) AS right_order
FROM
sequences AS l
LEFT JOIN
sequences AS r ON 1
HAVING
left_order=right_order;
There are 2 changes that should make this a little bit faster:
1) Calculating right order in reverse order in the first place
2) avoid using SELECT COUNT
in the last line.
Edit: I aliased the ln,rn because i couldn't see the columns in fiddle
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With