Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MySQL - Combining two select statements into one result with LIMIT efficiently

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.

like image 488
Wonka Avatar asked Apr 25 '12 21:04

Wonka


People also ask

How do I combine two SQL queries in one result without a UNION?

You need to create two separate queries and join their result not JOIN their tables. Show activity on this post. JOIN and UNION are differents. In your query you have used a CROSS JOIN operation, because when you use a comma between two table you apply a CROSS JOIN.

How do you fetch data that is common in two query results?

Use the UNION ALL clause to join data from columns in two or more tables. In our example, we join data from the employee and customer tables. On the left of the UNION ALL keyword, put the first SELECT statement to get data from the first table (in our example, the table employee ).


1 Answers

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).

like image 178
eggyal Avatar answered Sep 20 '22 17:09

eggyal