1

I have a list of value in my column. And want to query the range. Eg. If values are 1,2,3,4,5,9,11,12,13,14,17,18,19

I want to display 1-5,9,11-14,17-19

GMB
  • 216,147
  • 25
  • 84
  • 135
Zen
  • 21
  • 7
  • Are you actually storing comma-separated list of values in your column, or they belong to different rows? – GMB Aug 28 '20 at 12:41

2 Answers2

4

Assuming that each value is stored on a separate row, you can use some gaps-and-island technique here:

select case when min(val) <> max(val)
    then concat(min(val), '-', max(val))
    else min(val)
end val_range
from (select val, row_number() over(order by val) rn from mytable) t
group by val - rn
order by min(val)

The idea is to build groups of consecutive values by taking the difference between the value and an incrementing rank, which is computed using row_number() (available in MySQL 8.0):

Demo on DB Fiddle:

| val_range |
| :-------- |
| 1-5       |
| 9         |
| 11-14     |
| 17-19     |

In earlier versions, you can emulate row_number() with a correlated subquery, or a user variable. The second option goes like:

select case when min(val) <> max(val)
    then concat(min(val), '-', max(val))
    else min(val)
end val_range
from (select @rn := 0) x
cross join (
    select val, @rn := @rn + 1 rn 
    from (select val from mytable order by val) t
) t
group by val - rn
order by min(val)
GMB
  • 216,147
  • 25
  • 84
  • 135
  • As I try your query I'm getting an error for val_range. I'm using mysql 5.7.19 – Zen Aug 28 '20 at 12:59
  • @Zen: as I mentioned, this solution requires MySQL 8.0. I updated my answer with an alternative for earlier versions. – GMB Aug 28 '20 at 13:02
  • 1
    @GMB It took me a while to learn the `rn - val` trick to find groups. It's quite useful. – The Impaler Aug 28 '20 at 13:05
  • @Zen . . . I'm surprised that the assignment of `rn` working in 5.17.19. I think in that version, you should be using a subquery for the `order by`, and this happens to work because the table is read "in order". – Gordon Linoff Aug 28 '20 at 13:15
  • @GordonLinoff: I am always unsure that we need to pre-order before using variables when a single table is involved. Do you have a documentation that specifies that? – GMB Aug 28 '20 at 13:26
  • @GMB Unfortunately, val_range is still showing token error. As i try it to works but wrong range. I'm talking about the second query. – Zen Aug 28 '20 at 13:32
  • @Zen: this seems to work fine, as you can see [in this MySQL 5.7.29 DB Fiddle](https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=064861ab8a4172859785ae49f0348c6a). Please make sure to run the query exactly as provided. – GMB Aug 28 '20 at 13:36
  • @GMB mine is displaying 1-2, 2-3,3-4, 4-5 and more than thousands of rows appear which is not the case i'm looking. I noticed use MSQL 5.7.29 while mine is 5.7.19 a bit different – Zen Aug 28 '20 at 13:45
  • @Zen: ok, I went with Gordon Linoff's recommendation to pre-order the table before ranking. Does that last query in my answer works better for you? [Demo](https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=d68fa22ba5c0675adcf28288a047d4b1) – GMB Aug 28 '20 at 13:51
  • @GMB still getting same result. – Zen Aug 28 '20 at 13:59
  • @GMB . . . I don't know if there is documentation. Somewhere around 5.6, you need a subquery with `group by`. Somewhere around 5.7, you need a subquery with `order by`. The documentation cautions against this as early as 5.6: https://dev.mysql.com/doc/refman/5.6/en/user-variables.html. – Gordon Linoff Aug 28 '20 at 22:14
  • @GordonLinoff: *In a SELECT statement, each select expression is evaluated only when sent to the client. This means that in a HAVING, GROUP BY, or ORDER BY clause, referring to a variable that is assigned a value in the select expression list does not work as expected*. That's largely good enough for me, although that's not very specific about using a pre-sorted derived table. Thank you! – GMB Aug 28 '20 at 22:23
  • @GMB . . . MySQL says nothing about derived tables preserving the ordering, as far as I know -- and the documentation is a bit off because I'm pretty sure variables work with `ORDER BY` in 5.6. Happily, assignment of variables in `SELECT` statements is now deprecated, so the discussion is moot. – Gordon Linoff Aug 28 '20 at 22:25
  • @GordonLinoff: yes... or will be moot once everyone has upgraded to 8.0... – GMB Aug 28 '20 at 22:27
0

As a complement to other answers:

select dn.val as dnval, min(up.val) as upval 
from mytable up
join mytable dn
    on dn.val <= up.val
where not exists (select 1 from mytable a where a.val = up.val + 1)
  and not exists (select 1 from mytable b where b.val = dn.val - 1)
group by dn.val
order by dn.val;

1   5
9   9
11  14
17  19

Needless to say, but using an OLAP function like @GNB does, is orders of magnitude more efficient.

A short article on how to mimic OLAP functions in MySQL < 8 can be found at:

mysql-row_number

Fiddle

EDIT:

If another dimension is introduced (in this case p), something like:

select dn.p, dn.val as dnval, min(up.val) as upval 
from mytable up
join mytable dn
    on dn.val <= up.val
    and dn.p = up.p
where not exists (select 1 from mytable a where a.val = up.val + 1 and a.p = up.p)
  and not exists (select 1 from mytable b where b.val = dn.val - 1 and b.p = dn.p)
group by dn.p, dn.val
order by dn.p, dn.val;

can be used, see Fiddle2

Lennart - Slava Ukraini
  • 6,936
  • 1
  • 20
  • 32
  • Sounds neat but i can't tell it works with my huge data. What if I wanted to add another filtering column. – Zen Aug 31 '20 at 08:59
  • Not sure what you mean by another filtering column, do you mean a column that is used as a partition for your sequence of numbers? – Lennart - Slava Ukraini Aug 31 '20 at 11:14
  • @Zen, I have extended the example, see edit. The larger your data set is, the more expensive the traditional SQL92 solution is going to be. As you can see mytable is referenced 4 times in the query, whereas it is only a matter of `row_number() over(partition by p order by val)`in GMB:s question. – Lennart - Slava Ukraini Aug 31 '20 at 11:27
  • I was trying to make a general question maybe i should be specific. I print cards using pos machine. Table sales has list of serial_no, pos_machine_no, and card_amount. I want to get the range of serial_no sold by a specific pos machine grouped by card_amount. – Zen Sep 04 '20 at 14:18
  • Ask another question. Make sure to include create table statements and insert statements with sample data that Illustrates the problem. Db<>fiddle is an excellent tool for tvat – Lennart - Slava Ukraini Sep 04 '20 at 18:16
  • https://stackoverflow.com/questions/63775220/trying-to-get-column-values-as-range – Zen Sep 07 '20 at 10:02