MySQL: how to sort rows by a field and assign another field a counter? - mysql

Imagine there's a table with the following contents:
x y z
aa 5 null
bb 2 null
cc 5 null
dd 1 null
I want to sort the rows by y and assign an auto-incremented field to z, so in this case the end result (the changed table) would be
x y z
dd 1 1
bb 2 2
aa 5 3
cc 5 4
or
x y z
aa 5 3
bb 2 2
cc 5 4
dd 1 1
How do I do that?
So to make it clear, I want to change the table, not get that stuff to code.
As requested, http://sqlfiddle.com/#!2/cd610/1

update your_table t1
inner join
(
select id, #rank := #rank + 1 as r
from your_table, (select #rank := 0) r
order by y
) t2 on t2.y = t.y
set z = r
SQLFiddle demo

This is what I might call the "painful" solution. It uses a subquery to calculate the rank for each row:
update t
set z = (select count(*) as cnt
from (select t.* from t) t2
where t2.y < t.y or t2.y = t.y and t2.x <= t.x
)
Note that MySQL generates an error when you use the target table in a subquery in update and delete. There is a way around this. Just embed the table in a subquery.
You can also do this using a variable with a join:
update t join
(select t.*, (#rank := #rank + 1) as seqnum
from t cross join (select #rank := 0) const
order by y
) t2
on t.x = t2.x
set t.z = t2.seqnum;
The subquery calculates the sequence number with the order by. The outer update then assigns the value to the appropriate row. This assumes that x uniquely identifies each row.

Related

get the range of sequence values in table column

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

Setting and using a variable in a MySQL WHERE

If I do the following query on MySQL 5.7.16, then the resultset contains one row with the value 2, which is expected:
SELECT *
FROM (SELECT 1 as x UNION SELECT 2 UNION SELECT 3) AS t
WHERE ( 2 IS NULL OR t.x = 2 )
;
-- Resultset: 1 row, x = 2
Now, I would like to use this logic in a prepared statement, like ? IS NULL OR t.x = ?. So you see that the same parameter appears twice. Hence, I applied an advice I found on SO (I don't remember the exact location): put the parameter in a MySQL session variable, and use it:
SELECT *
FROM (SELECT 1 as x UNION SELECT 2 UNION SELECT 3) AS t
WHERE ( (#x := 2) IS NULL OR t.x = #x )
;
-- Resultset: 0 row
But this fails: no row is returned. When I also select the #x variable, in order to see what's going on, I get a NULL value for #x:
SELECT #x
FROM (SELECT 1 as x UNION SELECT 2 UNION SELECT 3) AS t
WHERE ( (#x := 2) IS NULL OR t.x = 2 )
;
-- Resultset: 1 row, #x = NULL
So it seems that the variable is not set when put in the WHERE? What is going on?
I could make an INNER JOIN (SELECT 2 AS x) AS params and use params.x inside the WHERE, but I would like to understand what's happening in that WHERE ( (#x = 2) IS NULL OR t.x = #x ).
What is happening here is that the order of execution inside WHERE is arbitrary. This means that using
(#x := 2) IS NULL OR t.x = 2
there is no way to guarantee that #x := 2 will be executed first.
To correctly initialize the variable use a CROSS JOIN:
SELECT *
FROM (SELECT 1 as x UNION SELECT 2 UNION SELECT 3) AS t
CROSS JOIN (SELECT #x := 2) AS v
WHERE ( #x IS NULL OR t.x = #x )

Mysql query get SUM() specific row?

Is it possible to get specific row in query using like SUM?
Example:
id tickets
1 10 1-10 10=10
2 35 11-45 10+35=45
3 45 46-90 10+35+45=90
4 110 91-200 10+35+45+110=200
Total: 200 tickets(In SUM), I need to get row ID who have ticket with number like 23(Output would be ID: 2, because ID: 2 contains 11-45tickets in SUM)
You can do it by defining a local variable into your select query (in form clause), e.g.:
select id, #total := #total + tickets as seats
from test, (select #total := 0) t
Here is the SQL Fiddle.
You seem to want the row where "23" fits in. I think this does the trick:
select t.*
from (select t.*, (#total := #total + tickets) as running_total
from t cross join
(select #total := 0) params
order by id
) t
where 23 > running_total - tickets and 23 <= running_total;
SELECT
d.id
,d.tickets
,CONCAT(
TRIM(CAST(d.RunningTotal - d.tickets + 1 AS CHAR(10)))
,'-'
,TRIM(CAST(d.RunningTotal AS CHAR(10)))
) as TicketRange
,d.RunningTotal
FROM
(
SELECT
id
,tickets
,#total := #total + tickets as RunningTotal
FROM
test
CROSS JOIN (select #total := 0) var
ORDER BY
id
) d
This is similar to Darshan's answer but there are a few key differences:
You shouldn't use implicit join syntax, explicit join has more functionality in the long run and has been a standard for more than 20 years
ORDER BY will make a huge difference on your running total when calculated with a variable! if you change the order it will calculate differently so you need to consider how you want to do the running total, by date? by id? by??? and make sure you put it in the query.
finally I actually calculated the range as well.
And here is how you can do it without using variables:
SELECT
d.id
,d.tickets
,CONCAT(
TRIM(d.LowRange)
,'-'
,TRIM(
CAST(RunningTotal AS CHAR(10))
)
) as TicketRange
,d.RunningTotal
FROM
(
SELECT
t.id
,t.tickets
,CAST(COALESCE(SUM(t2.tickets),0) + 1 AS CHAR(10)) as LowRange
,t.tickets + COALESCE(SUM(t2.tickets),0) as RunningTotal
FROM
test t
LEFT JOIN test t2
ON t.id > t2. id
GROUP BY
t.id
,t.tickets
) d

SELECT Current and Previous row WHERE condition

id value
---------
1 a
2 b
3 c
4 a
5 t
6 y
7 a
I want to select all rows where the value is 'a' and the row before it
id value
---------
1 a
3 c
4 a
6 y
7 a
I looked into
but I want to get all such rows in one query.
Please help me start
Thank you
I think the easiest way might be to use variables:
select t.*
from (select t.*,
(rn := if(value = 'a', 1, #rn + 1) as rn
from table t cross join
(select #rn := 0) params
order by id desc
) t
where rn in (1, 2)
order by id;
An alternative method uses a correlated subquery to get the previous value and then uses this in the where clause:
select t.*
from (select t.*,
(select t2.value
from table t2
where t2.id < t.id
order by t2.id desc
limit 1
) as prev_value
from table t
) t
where value = 'a' or prev_value = 'a';
With an index on id, this might even be faster than the method using variables.

my sql query to find sort two columns independently

there is a table having two columns say id and name , i want both columns to be sorted.
table :
id name
3 y
2 z
1 x
output should be
id name
1 x
2 y
3 z
can anybody do it in single sql query ???
You need need to do weird stuff. because what you want to do is weird.
select b1.id, b2.name from
(
select #row := #row +1 as row, id
from broken, (select #row := 0) rr
order by id asc
) b1
inner join
(
select #row2 := #row2 + 1 as row, name
from broken, (select #row2 := 0) rr
order by name asc
) b2
on b1.row = b2.row
demo fiddle: http://sqlfiddle.com/#!9/4d47c/7
Select *
, row_number() over (order by ID) as IDRow
, row_number() over (order by name) as NameRow
into #temp
from table
select a.ID, b.Name from #temp a
full outer join #temp b
on a.IDRow = b.NameRow
order by IDRow, NameRow
If you wanted, you could do this with subqueries instead of the temp table, but it'll probably be faster this way.