简体   繁体   中英

LIMIT SQL within WHERE clause?

Is it possible to put a limit within a WHERE clause in sql?

Table name: _mct_dot

Columns: a_ID (auto inc) number (index) qty (index) com_val

SELECT
        *
    FROM
        _mct_dot
    WHERE
        (
            number = 4
            OR
            number = 7
            OR
            number = 13
        )
        AND 
        (
            qty = 3
            OR
            qty = 5
            OR
            qty = 7
        )
    ORDER BY
        number ASC, qty ASC

So the above query, it will essentially return an array grouped in order. However it will pull out all of the 'qty' of values 3, 5 and 7. What I need is a sample of 5 results from 'qty' 3, 5 and 7. Ie. I need the results to look like:

'number' 4's with 'qty' = 3's where 'qty' limited to max 5 
'number' 4's with 'qty' = 5's where 'qty' limited to max 5 
'number' 4's with 'qty' = 7's where 'qty' limited to max 5 
'number' 7's with 'qty' = 3's where 'qty' limited to max 5 
'number' 7's with 'qty' = 5's where 'qty' limited to max 5 
'number' 7's with 'qty' = 7's where 'qty' limited to max 5 
'number' 13's with 'qty' = 3's where 'qty' limited to max 5 
'number' 13's with 'qty' = 5's where 'qty' limited to max 5 
'number' 13's with 'qty' = 7's where 'qty' limited to max 5 

No, it's not possible to add a LIMIT clause within a WHERE clause.

It is possible to achieve the resultset you want, but the SQL to do that isn't pretty. It's going to require either a JOIN, a correlated subquery, or an inline view.


If there's an "order" to the rows in _mct_dot, you could use a correlated subquery to check for the number of rows "before" the row you pulled, and take rows only that have fewer than four rows.

SELECT d.*
  FROM _mct_dot d
  JOIN ( SELECT n.number
              , q.qty
           FROM (SELECT 4 AS `number` UNION ALL SELECT 7 UNION ALL SELECT 13) n
          CROSS
           JOIN (SELECT 3 AS `qty` UNION ALL SELECT 5 UNION ALL SELECT 7) q
       ) p
    ON p.number = d.number
   AND p.qty = d.qty
   AND 5 > ( SELECT SUM(1)
               FROM _mct_dot c
              WHERE c.number = d.number
                AND c.qty = d.qty
                AND c.a_ID < d.a_ID
           )
 ORDER BY ...

The correlated subquery could wind up being executed a LOT of times, so for best performance, you are going to want an index with leading columns of number and qty and including the a_id column.

Either:

... ON `_mct_dot` (`number`, `qty`, `a_ID`)

or

... ON `_mct_dot` (`qty`, `number`, `a_ID`)

Another option is to use MySQL user variables, to emulate a row_number() analytic function, something like this:

SELECT t.*
  FROM ( SELECT d.a_ID
              , IF(d.number = @prev_number AND d.qty = @prev_qty
                  , @rn := @rn + 1
                  , @rn := 1
                ) AS rn
              , @prev_number := d.number
              , @prev_qty := d.qty
           FROM (SELECT @prev_number := NULL, @prev_qty := NULL, @rn := 0 ) i
          CROSS 
           JOIN ( SELECT n.number
                       , q.qty
                    FROM (SELECT 4 AS `number` UNION ALL SELECT 7 UNION ALL SELECT 13) n
                   CROSS
                    JOIN (SELECT 3 AS `qty` UNION ALL SELECT 5 UNION ALL SELECT 7) q
                ) p
           JOIN _mct_dot d
             ON d.number = p.number
            AND d.qty = p.qty
          ORDER BY d.number, d.qty
       ) s
  JOIN _mct_dot t
    ON t.a_ID = s.a_ID
 WHERE s.rn <= 5 
 ORDER BY t.number ASC, t.qty ASC

(These queries are desk checked only; haven't setup a SQL Fiddle demo.)

FOLLOWUP

For the first query, I've just used an inline view (aliased as "p"), that generates the set of all pairs of number and qty values that are being requested.

And we can use a JOIN operation to locate all the rows that match each pair from _mct_dot table.

The tricky part is the correlated subquery. (There's a couple of approaches we could use.) The approach in the query above is to get a "count" of the rows with a matching "number" and "qty", but with an id value less than the id value of the current row, basically finding out how many rows are "before" the current row. And we're comparing that to a literal 5, because we want to return only the first 5 rows in each group.


For the second query,

the inline view aliased as i is initializing some MySQL user variables. We don't really care what's returned by the query, except that it returns exactly one row (because we're referencing it in a JOIN operation)... what we're really interested in is getting the variables initialized at the start of the execution. And that happens because MySQL materializes the inline view (derived table), before the outer query that references the view is executed.

The inline view aliased as p gets us the pairs of number,qty that we want to retrieve, and we use a JOIN operation against _mct_dot to get the matching rows.

The "trick" in the inline view aliased as s is the use of the MySQL user variables. We're doing a check of the current values against the values from the previous row... if the number and qty match, then we're in the same "group", so we can increment the row number counter by 1. If either of the values change, then it's a new group, so we reset the row number counter to 1, since the current row is the "first" row in the new group.

We can run the query for the inline view s , and see that we're getting row numbers ( rn col) 1, 2, 3, etc. for each group.

Then the outermost query just filters out all the rows that have an rn row number greater than five. Actually, from s , we're returning just the unique identifier for the row; that outermost query is also doing a JOIN operation to retrieve the entire row, based on the unique id.


As I mentioned at the top of my answer, the SQL to do this is not pretty. (It does take a bit of work to unwind what those queries are doing.)

I'm not sure I understand your question correctly as it's not very clear, but based on what I've understood I think you can try something like this:

SELECT     attribute_number,
           grade_number //, other columns

FROM       _mct_dot
WHERE      (number = 4 AND qty IN (3, 5 ,7))
OR         (number = 7 AND qty IN (3, 5, 7))
OR         (number = 13 AND qty IN (3, 5, 7))
GROUP BY   attribute_number, grade_number
ORDER BY   attribute_number ASC, grade_number ASC
LIMIT      5

Hope this helps!!!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM