MySQL - Combining two select statements into one result with LIMIT efficiently
database, join, mysql, select
Solution
You can combine multiple queries with `UNION`, but only if the queries have the same number of columns. Ideally the columns are the same, not only in data type, but also in their semantic meaning; however, MySQL doesn't care about the semantics and will handle differing datatypes by casting up to something more generic - so if necessary you could overload the columns to have different meanings from each table, then determine what meaning is appropriate in your higher level code (although I don't recommend doing it this way).
When the number of columns differs, or when you want to achieve a better/less overloaded alignment of data from two queries, you can insert dummy literal columns into your `SELECT` statements. For example:
SELECT t.cola, t.colb, NULL, t.colc, NULL FROM t;
You could even have some columns reserved for the first table and others for the second table, such that they are `NULL` elsewhere (but remember that the column names come from the first query, so you may wish to ensure they're all named there):
SELECT a, b, c, d, NULL AS e, NULL AS f, NULL AS g FROM t1
UNION ALL -- specify ALL because default is DISTINCT, which is wasted here
SELECT NULL, NULL, NULL, NULL, a, b, c FROM t2;
You could try aligning your two queries in this fashion, then combining them with a `UNION` operator; by applying `LIMIT` to the `UNION`, you're close to achieving your goal:
(SELECT ...)
UNION
(SELECT ...)
LIMIT 10;
The only issue that remains is that, as presented above, 10 or more records from the first table will "push out" any records from the second. However, we can utilise an `ORDER BY` in the outer query to solve this.
Putting it all together:
(
SELECT
dr.request_time AS event_time, m.member_name, -- shared columns
dr.request_id, dr.member1, dr.member2, -- request-only columns
NULL AS alert_id, NULL AS alerter_id, -- alert-only columns
NULL AS alertee_id, NULL AS type
FROM dating_requests dr JOIN members m ON dr.member1=m.member_id
WHERE dr.member2=:loggedin_id
ORDER BY event_time LIMIT 10 -- save ourselves performing excessive UNION
) UNION ALL (
SELECT
da.alert_time AS event_time, m.member_name, -- shared columns
NULL, NULL, NULL, -- request-only columns
da.alert_id, da.alerter_id, da.alertee_id, da.type -- alert-only columns
FROM
dating_alerts da
JOIN dating_alerts_status das USING (alert_id, alertee_id)
JOIN members m ON da.alerter_id=m.member_id
WHERE
da.alertee_id=:loggedin_id
AND da.type='platonic'
AND das.viewed='0'
AND das.viewed_time<da.alert_time
ORDER BY event_time LIMIT 10 -- save ourselves performing excessive UNION
)
ORDER BY event_time
LIMIT 10;
Of course, now it's up to you to determine what type of row you're dealing with as you read each record in the resultset (suggest you test `request_id` and/or `alert_id` for `NULL` values; alternatively one could add an additional column to the results that explicitly states from which table each record originated, but it should be equivalent provided those `id` columns are `NOT NULL`).
Problem
For a dating application, I have a few tables that I need to query for a single output with a LIMIT 10 of both queries combined. It seems difficult to do at the moment, even though it's not an issue to query them separately, but the LIMIT 10 won't work as the numbers are not exact (ex. not LIMIT 5 and LIMIT 5, one query may return 0 rows, while the other 10, depending on the scenario). ``` members table member_id | member_name ------------------------ 1 Herb 2 Karen 3 Megan dating_requests request_id | member1 | member2 | request_time ---------------------------------------------------- 1 1 2 2012-12-21 12:51:45 dating_alerts alert_id | alerter_id | alertee_id | type | alert_time ------------------------------------------------------- 5 3 2 platonic 2012-12-21 10:25:32 dating_alerts_status status_id | alert_id | alertee_id | viewed | viewed_time ----------------------------------------------------------- 4 5 2 0 0000-00-00 00:00:00 ``` Imagine you are Karen and just logged in, you should see these 2 items: ``` 1. Herb requested a date with you. 2. Megan wants a platonic relationship with you. ``` In one query with a LIMIT of 10. Instead here are two queries that need to be combined: ``` 1. Herb requested a date with you. -> query = "SELECT dr.request_id, dr.member1, dr.member2, m.member_name FROM dating_requests dr JOIN members m ON dr.member1=m.member_id WHERE dr.member2=:loggedin_id ORDER BY dr.request_time LIMIT 5"; 2. Megan wants a platonic relationship with you. -> query = "SELECT da.alert_id, da.alerter_id, da.alertee_id, da.type, da.alert_time, m.member_name FROM dating_alerts da JOIN dating_alerts_status das ON da.alert_id=das.alert_id AND da.alertee_id=das.alertee_id JOIN members m ON da.alerter_id=m.member_id WHERE da.alertee_id=:loggedin_id AND da.type='platonic' AND das.viewed='0' AND das.viewed_time<da.alert_time ORDER BY da.alert_time LIMIT 5"; ``` Again, sometimes both tables may be empty, or 1 table may be empty, or both full (where LIMIT 10 kicks in) and ordered by time. Any ideas on how to get a query to perform this task efficiently? Thoughts, advice, chimes, optimizations are welcome.