mysql: how to save ORDER BY after LEFT JOIN without reorder? - mysql

I've two table:
1) profiles
+----+---------+
| id | name |
+----+---------+
| 1 | WILLIAM |
| 2 | JOHN |
| 3 | ROBERT |
| 4 | MICHAEL |
| 5 | JAMES |
| 6 | DAVID |
| 7 | RICHARD |
| 8 | CHARLES |
| 9 | JOSEPH |
| 10 | THOMAS |
+----+---------+
2) request_for_friendship
+----+---------+-------+
| id | from_id | to_id |
+----+---------+-------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 8 |
| 5 | 4 | 1 |
| 6 | 9 | 1 |
+----+---------+-------+
I need to get all profiles with some sorting and join it with request_for_friendship
For example, get all users with some sorting:
mysql> SELECT *
-> FROM profiles
-> ORDER BY name ASC;
+----+---------+
| id | name |
+----+---------+
| 8 | CHARLES |
| 6 | DAVID |
| 5 | JAMES |
| 2 | JOHN |
| 9 | JOSEPH |
| 4 | MICHAEL |
| 7 | RICHARD |
| 3 | ROBERT |
| 10 | THOMAS |
| 1 | WILLIAM | <-- WILLIAM IS LAST!
+----+---------+
Everything looks good, sorting is present. After that I join with request_for_friendship and my sotring will breaks:
mysql> SELECT * FROM
-> (
-> SELECT *
-> FROM profiles
-> ORDER BY name ASC
-> ) as users
-> LEFT JOIN request_for_friendship
-> AS request_for_friendship_copy
-> ON
-> (
-> request_for_friendship_copy.from_id = 1
-> AND
-> request_for_friendship_copy.to_id = users.id
-> )
-> OR
-> (
-> request_for_friendship_copy.from_id = users.id
-> AND
-> request_for_friendship_copy.to_id = 1
-> );
+----+---------+------+---------+-------+
| id | name | id | from_id | to_id |
+----+---------+------+---------+-------+
| 2 | JOHN | 1 | 1 | 2 |
| 3 | ROBERT | 2 | 1 | 3 |
| 8 | CHARLES | 3 | 1 | 8 |
| 4 | MICHAEL | 5 | 4 | 1 |
| 9 | JOSEPH | 6 | 9 | 1 |
| 1 | WILLIAM | NULL | NULL | NULL | <-- WILLIAM IN THE MIDDLE!
| 5 | JAMES | NULL | NULL | NULL |
| 6 | DAVID | NULL | NULL | NULL |
| 7 | RICHARD | NULL | NULL | NULL |
| 10 | THOMAS | NULL | NULL | NULL |
+----+---------+------+---------+-------+
How to JOIN LEFT with original sorting saving?
I can't sort result after JOIN LEFT besause when I do ORDER BY before JOIN it takes ~0.02s in my db (~1 000 000 users) but when I do ORDER BY after JOIN it takes ~3.2s, it's very big time :(
Demo: rextester.com/DLLM29415
Demo: http://sqlfiddle.com/#!9/167792/1
In sqlfiddle order is saved! But how? MySQL 5.6 saved order?

(Explaining the loss of ORDER BY)
The SQL standard essentially says that a subquery is an unordered set of rows. This implies that the Optimizer is free to ignore the ORDER BY in the 'derived' table: FROM ( SELECT ... ORDER BY ). In "recent" versions of MySQL and MariaDB, such ORDER BYs are being dropped. There are other cases where ORDER BY is ignored.
In some situations (not sure about this one), adding a LIMIT 99999999 (big number) after the ORDER BY tricks the Optimizer into doing the ORDER BY. However, it is still free to ignore the "order" later.
A general rule for MySQL: Avoid subqueries. (There are cases where subqueries are faster, but not yours.)
A strong rule: You must have an ORDER BY on the outermost if you want the results to be sorted.
If you had added LIMIT 3 to the derived table in your first query, you would get only CHARLES, DAVID, JAMES, but not necessarily in that order. That is, you would need two ORDER BYs - one in the derived table, one at the very end.

SELECT *
FROM profiles p
LEFT
JOIN request_for_friendship r
ON (r.from_id = p.id AND r.to_id = 1)
OR (r.from_id = 1 AND r.to_id = p.id)
ORDER
BY name;
+----+---------+------+---------+-------+
| id | name | id | from_id | to_id |
+----+---------+------+---------+-------+
| 8 | CHARLES | 3 | 1 | 8 |
| 6 | DAVID | NULL | NULL | NULL |
| 5 | JAMES | NULL | NULL | NULL |
| 2 | JOHN | 1 | 1 | 2 |
| 9 | JOSEPH | 6 | 9 | 1 |
| 4 | MICHAEL | 5 | 4 | 1 |
| 7 | RICHARD | NULL | NULL | NULL |
| 3 | ROBERT | 2 | 1 | 3 |
| 10 | THOMAS | NULL | NULL | NULL |
| 1 | WILLIAM | NULL | NULL | NULL |
+----+---------+------+---------+-------+
10 rows in set (0.02 sec)
mysql>

Try this:
SELECT
a.name as `from_name`,
b.name as `to_name`,
c.from_id,
c.to_id
FROM profiles a
LEFT JOIN request_for_friendship c
ON a.id = c.from_id
LEFT JOIN profiles b
ON c.to_id = b.id
GROUP BY a.name,b.name
ORDER BY a.name,b.name;
Or, if you want one row per "from" name:
SELECT
a.name as `from_name`,
IFNULL(GROUP_CONCAT(b.name),'-none-') as `to_name`,
IFNULL(c.from_id,'-none-') as `from_id`,
IFNULL(GROUP_CONCAT(c.to_id),'-none-') as `to_id`
FROM profiles a
LEFT JOIN request_for_friendship c
ON a.id = c.from_id
LEFT JOIN profiles b
ON c.to_id = b.id
GROUP BY a.name
ORDER BY a.name,b.name

I know this question is a couple of years old, but I didn't find this possible solution already offered. This is the solution that worked best for me to keep the subquery results in the correct order.
Consider adding a "row_number" to your subquery. Then use ORDER BY on row_number.
This explains how to add the row_number:
select increment counter in mysql
In my case, I have an unknown number of possible rows in a hierarchical recursive query that I need to keep the order results of the subquery to remain the same in the outer query.
This is my query:
SELECT l.row_number, l.userid, l.child, p.id, p.username
FROM (
SELECT #rownum := #rownum + 1 AS row_number, u.parent AS userid, _id AS child
FROM (
SELECT #r AS _id, (SELECT #r := parent FROM new_clean WHERE userid = _id) AS parent
FROM (SELECT #r := ?) AS vars, new_clean h
WHERE #r <> 0
) u
CROSS JOIN (SELECT #rownum := 0) r
WHERE u.parent <> 0
) l
LEFT JOIN profile p ON p.userid = l.userid
ORDER BY row_number

Related

MySQL: Finding the most efficient use of INNER JOIN with subquery

I have a working query using INNER JOIN and a subquery but was wondering if there is a more effient way of writing it.
with prl
as
(
SELECT `number`, creator, notes
FROM ratings
INNER JOIN
projects on ratings.project_id = projects.project_id
WHERE ratings.rating = 5 AND projects.active = 1
)
SELECT prl.`number`, creator, notes
FROM prl
INNER JOIN(
SELECT `number`
HAVING COUNT(creator) > 1
)temp ON prl.`number` = temp.`number`
ORDER BY temp.`number`
projects table
project_id| number | creator | active |
| 1 | 3 | bob | 1 |
| 2 | 4 | mary | 1 |
| 3 | 5 | asi | 1 |
rating table
project_id| notes | rating |
| 1 | note1 | 5 |
| 1 | note2 | 5 |
| 3 | note3 | 5 |
| 1 | note4 | 1 |
| 2 | note5 | 5 |
| 3 | note6 | 2 |
result
| number | creator | notes |
| 3 | bob | note1 |
| 3 | bob | note2 |
It seems like you're using MySQL version that support window function. If so, then try this:
SELECT number, creator, notes
FROM
(SELECT p.number, p.creator, r.notes,
COUNT(creator) OVER (PARTITION BY creator) AS cnt
FROM project p
JOIN rating r ON p.project_id=r.project_id
WHERE r.rating=5
AND p.active = 1) v
WHERE cnt=2;
As far as whether this is more efficient, I'm not really sure because it depends in your table indexes but for a small dataset, I assume this will do well.
Demo fiddle

mysql table ordering incorrect with group by and order by

table 1: forum_threads
+-----+------+-------+
| id | title| status|
+-----+------+-------+
| 1 | a | 1 |
| 2 | b | 1 |
| 3 | c | 1 |
| 4 | d | 1 |
| 5 | e | 1 |
| 6 | f | 1 |
+-----+------+-------+
table 2: forum_comments
+-----+----------+--------------------+
| id | thread_id| comment |
+-----+----------+--------------------+
| 1 | 4 | hai |
| 2 | 4 | hello |
| 3 | 2 | welcome |
| 4 | 2 | whats your name |
| 5 | 6 | how are you |
| 6 | 5 | how old are you |
| 7 | 5 | good |
+-----+----------+--------------------+
wanted output
+-----------+----------+-----------------+
| thread_id | title | comment_count |
+-----------+----------+-----------------+
| 5 | e | 2 |
| 6 | f | 1 |
| 2 | b | 2 |
| 4 | d | 2 |
+-----------+----------+-----------------+
my Query
SELECT forum_threads.*,forum_comments.*,count(forum_comments.id) as comment_count
FROM forum_comments
LEFT JOIN forum_threads ON forum_comments.thread_id = forum_threads.id
GROUP BY forum_threads.id
ORDER BY forum_comments.id desc
Here I am trying to get the titles by the latest comment.
when I give ORDER BY forum_comments.id this returns the wrong order.
I need to order by the latest comments in the forum_comments table.
this query returns the wrong order please help me to find out the correct order.
how could I solve this easily?
This query should give you the expected result:
select t2.thread_id, t1.title, t2.comment_count from forum_threads as t1,
(SELECT id, thread_id, count(comment) as comment_count from forum_comments group by thread_id) as t2
where t1.id = t2.thread_id order by t2.id desc;
Instead of using forum_threads.* and forum_comments.* can you give specific column names and try.
If that doesn't work you should try explicitly assigning primary and foreign keys.

How to get last added field from MySQL database JOIN

My database has two tables
MariaDB [testnotes]> describe contactstbl;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(6) | YES | | NULL | |
| name | varchar(30) | YES | | NULL | |
| phone | varchar(20) | YES | | NULL | |
| email | varchar(40) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
MariaDB [testnotes]> describe notestbl;
+-----------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+----------+------+-----+---------+-------+
| id | int(6) | YES | | NULL | |
| notes | blob | YES | | NULL | |
| dateadded | datetime | YES | | NULL | |
+-----------+----------+------+-----+---------+-------+
I want a query that will show the last notes in the notestbl table for the give ID
contactstbl has about 100ish records I want to show them all even without notes
MariaDB [testnotes]> select * from contactstbl;
+------+------+-------+--------+
| id | name | phone | email |
+------+------+-------+--------+
| 1 | fran | 12335 | gf#g.m |
| 2 | tony | 45355 | ck#g.m |
| 3 | samm | 46545 | fs#g.m |
+------+------+-------+--------+
MariaDB [testnotes]> select * from notestbl;
+------+------------------+---------------------+
| id | notes | dateadded |
+------+------------------+---------------------+
| 1 | 2 days ago notes | 2020-01-12 00:00:00 |
| 3 | 5 days ago notes | 2020-01-09 00:00:00 |
| 3 | 3 days ago notes | 2020-01-11 00:00:00 |
| 1 | 1 days ago notes | 2020-01-13 00:00:00 |
| 1 | 3 days ago notes | 2020-01-11 00:00:00 |
+------+------------------+---------------------+
5 rows in set (0.00 sec)
I have tried a couple different queries and just cannot seem to get it right.
SELECT c.id,c.name,c.email,n.id,n.dateadded,n.notes FROM contactstbl c left join notestbl n using(id) GROUP BY c.id ORDER BY n.dateadded ASC;
Which is very close.
+------+------+--------+------+---------------------+------------------+
| id | name | email | id | dateadded | notes |
+------+------+--------+------+---------------------+------------------+
| 2 | tony | ck#g.m | NULL | NULL | NULL |
| 3 | samm | fs#g.m | 3 | 2020-01-09 00:00:00 | 5 days ago notes |
| 1 | fran | gf#g.m | 1 | 2020-01-12 00:00:00 | 2 days ago notes |
+------+------+--------+------+---------------------+------------------+
What I want to see is:
+------+------+--------+------+---------------------+------------------+
| id | name | email | id | dateadded | notes |
+------+------+--------+------+---------------------+------------------+
| 2 | tony | ck#g.m | NULL | NULL | NULL |
| 3 | samm | fs#g.m | 3 | 2020-01-11 00:00:00 | 3 days ago notes |
| 1 | fran | gf#g.m | 1 | 2020-01-13 00:00:00 | 1 days ago notes |
+------+------+--------+------+---------------------+------------------+
Just use subquery in SELECT clause:
SELECT
c.id,
c.name,
c.email,
(SELECT n.id FROM notestbl n WHERE n.id=c.id ORDER BY n.dateadded DESC LIMIT 1) nid,
(SELECT n.dateadded FROM notestbl n WHERE n.id=c.id ORDER BY n.dateadded DESC LIMIT 1) ndateadded,
(SELECT n.notes FROM notestbl n WHERE n.id=c.id ORDER BY n.dateadded DESC LIMIT 1) nnotes
FROM
contactstbl c
GROUP BY c.id
ORDER BY ndateadded ASC;
Result:
MariaDB [test]> SELECT
-> c.id,
-> c.name,
-> c.email,
-> (SELECT n.id FROM notestbl n WHERE n.id=c.id ORDER BY n.dateadded DESC LIMIT 1) nid,
-> (SELECT n.dateadded FROM notestbl n WHERE n.id=c.id ORDER BY n.dateadded DESC LIMIT 1) ndateadded,
-> (SELECT n.notes FROM notestbl n WHERE n.id=c.id ORDER BY n.dateadded DESC LIMIT 1) nnotes
-> FROM
-> contactstbl c
-> GROUP BY c.id
-> ORDER BY ndateadded ASC;
+----+------+--------+------+---------------------+------------------+
| id | name | email | nid | ndateadded | nnotes |
+----+------+--------+------+---------------------+------------------+
| 2 | tony | ck#g.m | NULL | NULL | NULL |
| 3 | sam | fs#g. | 3 | 2020-01-11 00:00:00 | 3 days ago notes |
| 1 | fran | gf#g.m | 1 | 2020-01-13 00:00:00 | 1 days ago notes |
+----+------+--------+------+---------------------+------------------+
3 rows in set (0.07 sec)
SELECT C.ID,
C.NAME,
C.EMAIL,
N1.ID,
N1.DATEADDED,
N1.NOTES
FROM CONTACTSTBL C
LEFT JOIN NOTESTBL N1 USING(ID)
LEFT JOIN NOTESTBL N2 ON N1.ID = N2.ID
AND N1.DATEADDED < N2.DATEADDED
WHERE N2.ID IS NULL
ORDER BY N1.DATEADDED;
also try some ideas from here
how do I query sql for a latest record date for each user
First, I think that you should change the schema of your notestbl table as it doesn't have its own id field, but instead relies purely on the id of the contactstbl table. This is bad design and should be normalised so as to prevent you pain in the future :)
I'd recommend it is changed to something like this:
mysql> select * from notestbl;
+------+------------+------------------+---------------------+
| id | contact_id | notes | dateadded |
+------+------------+------------------+---------------------+
| 1 | 1 | 2 days ago notes | 2020-01-12 00:00:00 |
| 2 | 3 | 5 days ago notes | 2020-01-09 00:00:00 |
| 3 | 3 | 3 days ago notes | 2020-01-11 00:00:00 |
| 4 | 1 | 1 days ago notes | 2020-01-13 00:00:00 |
| 5 | 1 | 3 days ago notes | 2020-01-11 00:00:00 |
+------+------------+------------------+---------------------+
5 rows in set (0.00 sec)
Then you can use this single line query to get the result you're after:
select c.id, c.name, c.email, n.id, n.dateadded, n.notes from contactstbl c left join (select t1.id, t1.contact_id, t1.dateadded, t1.notes from notestbl t1, (select contact_id, max(dateadded) as maxdate from notestbl group by contact_id) t2 where t1.contact_id=t2.contact_id and t1.dateadded=t2.maxdate) n on c.id=n.contact_id;
+------+------+--------+------+---------------------+------------------+
| id | name | email | id | dateadded | notes |
+------+------+--------+------+---------------------+------------------+
| 1 | fran | gf#g.m | 4 | 2020-01-13 00:00:00 | 1 days ago notes |
| 2 | tony | ck#g.m | NULL | NULL | NULL |
| 3 | samm | fs#g.m | 3 | 2020-01-11 00:00:00 | 3 days ago notes |
+------+------+--------+------+---------------------+------------------+
3 rows in set (0.00 sec)
A more visually pleasing representation of the query:
select c.id,
c.name,
c.email,
n.id,
n.dateadded,
n.notes
from contactstbl c
left join (select t1.id,
t1.contact_id,
t1.dateadded,
t1.notes
from notestbl t1,
(select contact_id, max(dateadded) as maxdate from notestbl group by contact_id) t2
where t1.contact_id=t2.contact_id
and t1.dateadded=t2.maxdate) n
on c.id=n.contact_id;

Mysql join request

I need to get emtpy fields where data is repeated
For example an customer can have two or more contact persons, so query return (just shorted qyery resul):
CUSTOMER_NAME| CONTACT_PERSON|ETC..
dell | Ighor |etc..
dell | Dima |etc..
but I'm need :
CUSTOMER_NAME| CONTACT_PERSON|etc...
dell | Ighor |etc..
NULL | Dima |etc..
SELECT
`contact`.*,
`branch_has_equipment`.*,
`branch_has_contact`.*,
`equipment`.*,
`customer_has_branch`.*,
`branch`.*,
`customer`.*,
`ip`.*
FROM `customer`
INNER JOIN `customer_has_branch`
ON `customer`.`customer_id` = `customer_has_branch`.`customer_id`
INNER JOIN `branch`
ON `customer_has_branch`.`branch_id` = `branch`.`branch_id`
INNER JOIN `branch_has_equipment`
ON `branch`.`branch_id` = `branch_has_equipment`.`branch_id`
INNER JOIN `equipment`
ON `branch_has_equipment`.`equipment_id` = `equipment`.`equipment_id`
INNER JOIN `branch_has_contact`
ON `branch`.`branch_id` = `branch_has_contact`.`branch_id`
INNER JOIN `contact`
ON `branch_has_contact`.`contact_id` = `contact`.`contact_id`
INNER JOIN `equipment_has_ip`
ON `equipment`.`equipment_id` = `equipment_has_ip`.`equipment_id`
INNER JOIN `ip`
ON `equipment_has_ip`.`equipment_id` = `ip`.`ip_id`
WHERE `customer`.`inservice` = 'Yes'
ORDER BY `customer`.`customer_name`
in additional, tables ^
Customer
customer_id
customer_name
inservice
service_type
comment
Branch
branch_id
city
address
Equipment
equipment_id
brand
model
connection_param
connection_type
serial_number
id
release
Contact
contact_id
name
surname
phone_mobile
phone_work
phone_other
position
customer_has_branch_id
customer_id
branch_id
Since I have no idea how any of those tables relate to one another, my only answer to you is to use an OUTER JOIN, which will keep NULL results.
I'm not seriously advocating this solution because really i think this sort of thing should be handled in application level code (e.g. a bit of PHP), but anyway, consider the following:
SELECT * FROM my_table;
+------+--------+--------+
| game | points | player |
+------+--------+--------+
| 1 | 5 | Adam |
| 1 | 8 | Alan |
| 1 | 7 | Brian |
| 1 | 6 | John |
| 2 | 2 | Adam |
| 2 | 3 | Alan |
| 2 | 4 | Brian |
| 2 | 6 | John |
+------+--------+--------+
SELECT IF(game= #prev,'',game)
, #prev := game
FROM my_table ORDER BY game,player;
+-------------------------+---------------+
| IF(game= #prev,'',game) | #prev := game |
+-------------------------+---------------+
| 1 | 1 |
| | 1 |
| | 1 |
| | 1 |
| 2 | 2 |
| | 2 |
| | 2 |
| | 2 |
+-------------------------+---------------+

MySQL Union on Dissimilar Fields without Dummy Columns

So lets say I have 2 or more tables consisting of dissimilar columns in which a shared key (id) is not necessarily present :
Alpha:
+----+-------+-------+-------+
| id | paula | randy | simon |
+----+-------+-------+-------+
| 1 | 8 | 7 | 2 |
| 2 | 9 | 6 | 2 |
| 3 | 10 | 5 | 2 |
+----+-------+-------+-------+
Beta:
+----+---------+-----+------------+------+
| id | is_nice | sex | dob | gift |
+----+---------+-----+------------+------+
| 2 | 1 | F | 1990-05-25 | iPod |
| 3 | 0 | M | 1990-05-25 | coal |
+----+---------+-----+------------+------+
Gamma:
+----+---------+--------+
| id | is_tall | is_fat |
+----+---------+--------+
| 1 | 1 | 1 |
| 99 | 0 | 1 |
+----+---------+--------+
The desired effect is to mash the tables together on id inserting NULLs where data is not available:
+----+-------+-------+-------+---------+-----+------------+------+---------+--------+
| id | paula | randy | simon | is_nice | sex | dob | gift | is_tall | is_fat |
+----+-------+-------+-------+---------+-----+------------+------+---------+--------+
| 1 | 8 | 7 | 2 | | | | | 1 | 1 |
| 2 | 9 | 6 | 2 | 1 | F | 1990-05-25 | iPod | | |
| 3 | 10 | 5 | 2 | 0 | M | 1990-05-25 | coal | 1 | 1 |
| 99 | | | | | | | | 0 | 0 |
+----+-------+-------+-------+---------+-----+------------+------+---------+--------+
I can use NULL 'dummy' columns and UNION (MySql SELECT union for different columns?) but that seems like a royal pain if the number of tables is great. I'd like to think there is a JOIN method I can use to accomplish this, but I need some help to figure this out.
This works:
SELECT `id`, `paula`, `randy`, ..., NULL AS `is_nice`, ... FROM `Alpha`
UNION SELECT `id`, NULL AS `paula`, ..., FROM `Beta`
UNION SELECT `id`, NULL AS `paula`, ..., `is_fat` FROM `Gamma` ;
but it sure feels like the wrong way to do it. How can I get the same results without having to edit lines and lines of SQL inserting NULL AS whatever all over the place whenever I want to tack on additional tables?
Thanks in advance!
SELECT
allid.id
, a.paula, a.randy a.simon
, b. ...
, c. ...
FROM
( SELECT id
FROM Alpha
UNION
SELECT id
FROM Beta
UNION
SELECT id
FROM Gamma
) AS allid
LEFT JOIN
Alpha AS a
ON a.id = allid.id
LEFT JOIN
Beta AS b
ON b.id = allid.id
LEFT JOIN
Gamma AS g
ON g.id = allid.id
If the tables share no other column except the id, you could use the simple to write (but easier to break):
SELECT
*
FROM
( SELECT id
FROM Alpha
UNION
SELECT id
FROM Beta
UNION
SELECT id
FROM Gamma
) AS allid
NATURAL LEFT JOIN
Alpha
NATURAL LEFT JOIN
Beta
NATURAL LEFT JOIN
Gamma
You want to use LEFT JOINs.
http://dev.mysql.com/doc/refman/5.0/en/join.html
In your example:
SELECT id_t.id, a.paula, a.randy, a.simon, b.is_nice, b.sex, b.dob, b.gift, g.is_tall, g.is_fat
FROM (SELECT DISTINCT id FROM alpha,beta,gamma) as id_t
LEFT JOIN alpha a ON a.id = id_t.id
LEFT JOIN beta b on b.id = id_t.id
LEFT JOIN gamma g on g.id = id_t.id