I’m not sure how I should have titled this question. I have a table called products
. A simplified schema is:
+--------+-----------+----------+
| id | name | priority |
+--------+-----------+----------+
| 1 | Product 1 | null |
| 2 | Product 2 | 1 |
| 3 | Product 3 | null |
| 4 | Product 4 | null |
| 5 | Product 5 | null |
Every time a user clicks a button, I aim to retrieve a single, random product. However, my preference is to return any product with a priority before I begin returning random products. (I have additional logic in place to prevent the same product from being selected more than once.)
An example is:
$product = Product::where('priority', '>', 0)->sortBy('priority')->first();
if(!$product) {
$product = Product::OrderByRaw('Rand()')->first();
}
This works just fine, but it seems odd that I always have to run two queries to get one result.
So, my question is, is there a single query that I can use to achieve the same thing? I know I could do just one DB query to get all results and then get a product at random from the result set, but in my real-word application, the query is triggered as part of a much bigger AJAX call. Doing that would require a lot of rewriting of perfectly functioning code.
2
Answers
You can use the
union
method to combine two queries. Theunion
method will execute the first query; if it doesn’t return any results, it will execute the second query.Yes, you can potentially get the data you want with one query.
However, having your query split up as you do may be slightly more efficient, depending on the size of your dataset. An
order by rand()
statement is not very efficient, especially on larger datasets. By using two statements as you have done, you are completely avoiding that second inefficient query when a result is found by the first efficient one.That said, below is one method for doing it in one query. This answer also assumes
MySQL
, where nulls are sorted higher than non-nulls (which is not what you want in this case).You can use one query, order it in the proper way, and then take the first result. In your case, it seems like you want your items ordered:
This can be accomplished with the following
order by
clause:priority is null
will return 1 (true) when null and 0 (false) when not null. Since this sort is in ascending order, this sorts all non-null priorities higher than null priorities.priority
field will sort all non-null priorities in ascending order.rand()
sort will sort all of the null priorities in random order.With all your records sorted in that order, take the first result.
If you have a non-null priority, it will be first (ordered by priority asc). If you do not have a non-null priority, the first record will be a null priority that was sorted in random order.