Order by logic and get mixed results? - mysql

I have this table structure
id | status
-----+------+
1 | a |
2 | b |
3 | b |
4 | b |
5 | a |
6 | a |
7 | b |
8 | a |
I have two statuses "a" and "b".
I need to order this by logic that says this: for each two "a", show one "b".
So something like this (the "a" is more important, so if there are a lot of "b"s they will just be left in the end)
id | status
-----+------+
1 | a |
5 | a |
2 | b |
6 | a |
8 | a |
3 | b |
4 | b |
7 | b |
Is there a way to do this?

select id
,status
from (select status
,id
,#prev_status := #status
,#status := status
,#rn := case when #prev_status = status
then #rn + 1
else 1
end as rn
from mytable t1,(select #status:=null,#rn:=0) x
order by status
,id
) t
order by floor((rn-1) / case status when 'a' then 2 else 1 end)
,case status when 'a' then 1 else 2 end
,rn
;
+----+--------+
| id | status |
+----+--------+
| 1 | a |
| 5 | a |
| 2 | b |
| 6 | a |
| 8 | a |
| 3 | b |
| 4 | b |
| 7 | b |
+----+--------+
This will help you to understand the solution:
(group_id = floor((rn-1) / case status when 'a' then 2 else 1 end))
+--------+----+----+----------+
| status | id | rn | group_id |
+--------+----+----+----------+
| a | 1 | 1 | 0 |
| a | 5 | 2 | 0 |
| a | 6 | 3 | 1 |
| a | 8 | 4 | 1 |
| b | 2 | 1 | 0 |
| b | 3 | 2 | 1 |
| b | 4 | 3 | 2 |
| b | 7 | 4 | 3 |
+--------+----+----+----------+

Use variables to enumerate the values and then some simple logic:
select id, status
from (select t.*,
(#rn := if(#s = status, #rn + 1,
if(#s := status, 1, 1)
)
) as rn
from t cross join
(select #rn := 0, #s := '') params
where status in ('a', 'b')
order by status, id
) ab
order by (case when status = a then floor( (rn - 1) / 2) else (rn - 1) end),
status, id;

Related

mysql sequence number by value coloumn (query UPDATE)

example:
I have a table with the columns
______________________
|field_id|Code|seq_num|
| 1 | a | 1 |
| 1 | a | 2 |
| 1 | a | 3 |
| 2 | a | 4 |
| 2 | a | 5 |
| 3 | a | 6 |
| 3 | a | 7 |
| 3 | a | 8 |
how to query it, so sequence number look like this
_____________________
|field_id|Code|seq_num|
| 1 | a | 1 |
| 1 | a | 2 |
| 1 | a | 3 |
| 2 | a | 1 |
| 2 | a | 2 |
| 3 | a | 1 |
| 3 | a | 2 |
| 3 | a | 3 |
please help!!
One method is to get the minimum sequence for the field:
select t.field_id, t.code,
(seq_num - min_seqnum + 1) as seqnum
from t join
(select field_id, min(seq_num) as min_seq_num
from t
group by field_id
) f
on t.field_id = f.field_id;
You can also do this using variables, if you don't trust the current sequence numbers to have no gaps:
select . . .,
(#rn := if(#f = field_id, #rn + 1,
if(#f := field_id, 1, 1)
)
) as seq_no
from (select t.*
from t
order by field_id, seq_no
) t cross join
(select #f := '', #rn := 0) params;

mysql, update foreach category

it is a really simple question. it is only 1 table!
i have a table of books which each book have a category and a number.
SO I WANNA TRANSFORM THIS
+--------------------------+
| book_id | category | num |
+--------------------------+
| 1 | a | 7 |
| 2 | a | 5 |
| 3 | a | 3 |
| 4 | b | 9 |
| 5 | b | 8 |
| 6 | b | 1 |
+--------------------------+
INTO THIS,
+--------------------------+
| book_id | category | num |
+--------------------------+
| 3 | a | 3 |
| 2 | a | 5 |
| 1 | a | 7 |
| 6 | b | 1 |
| 5 | b | 8 |
| 4 | b | 9 |
+--------------------------+
AND THEN THIS!
+--------------------------+
| book_id | category | num |
+--------------------------+
| 3 | a | 1 |
| 2 | a | 2 |
| 1 | a | 3 |
| 6 | b | 1 |
| 5 | b | 2 |
| 4 | b | 3 |
+--------------------------+
BUT HOW?!?!?!?!
script to create table...
drop table if exists books;
CREATE TABLE books(
id int AUTO_INCREMENT,
category varchar(30),
num int,
PRIMARY KEY (id)
);
insert into books (category,num)
values
('a',7),
('a',5),
('a',3),
('b',9),
('b',8),
('b',1);
You can use user variables to generate the sequence numbers within each category in the order of increasing id.
If you just want to query the table, use:
select
b.id,
b.category,
#rn := if(#category = category, #rn + 1, if (#category := category, 1, 1)) num
from books b, (select #category := null, #rn := 0) t2
order by b.category, b.id
If you want to update your table, use:
update books b1
join (
select
b.id,
#rn := if(#category = category, #rn + 1, if (#category := category, 1, 1)) num
from books b, (select #category := null, #rn := 0) t2
order by b.category, b.id
) b2 on b1.id = b2.id
set b1.num = b2.num;
Demo
EDIT:
As per the edited question, you can use order by b.category, b.num instead.

select sum of max values from multiple incrementing sequences

I want to calculate a sum of the max values from sequence of increment values.
for this data set:
time_stamp count
1467820429 6 *
1467820428 5
1467820427 4
1467820426 3
1467820416 2
1467820415 1
1467820413 0
1467820412 3 *
1467820411 2
1467820409 1
1467820408 0
1467820405 1 *
1467820404 0
1467820400 5 *
answer = 6 + 3 + 1 + 5 = 15
how can i write a MySQL compatible SQL statement to acheve this
As I mentioned in comments there is no efficient way to do this in Mysql atleast to my knowledge
Try this
SELECT Sum(CASE
WHEN `count` >= prev_cnt THEN `count`
ELSE 0
END)
FROM (SELECT *,
IFnull((SELECT `count`
FROM yourtable b
WHERE a.`time_stamp` < b.`time_stamp`
ORDER BY `time_stamp` LIMIT 1), `count`) AS prev_cnt
FROM yourtable a) c
you can get it in following method
mysql> select time_stamp,count,if (count=0,#curRank :=0,#curRank := #curRank + 1) as rank from ff,(SELECT #curRank := 0) r;
+------------+-------+------+
| time_stamp | count | rank |
+------------+-------+------+
| 1467820429 | 6 | 1 |
| 1467820428 | 5 | 2 |
| 1467820427 | 4 | 3 |
| 1467820426 | 3 | 4 |
| 1467820415 | 2 | 5 |
| 1467820415 | 1 | 6 |
| 1467820413 | 0 | 0 |
| 1467820412 | 3 | 1 |
| 1467820411 | 2 | 2 |
| 1467820409 | 1 | 3 |
| 1467820408 | 0 | 0 |
| 1467820405 | 1 | 1 |
| 1467820404 | 0 | 0 |
| 1467820408 | 5 | 1 |
+------------+-------+------+
14 rows in set (0.00 sec)
mysql> SELECT * FROM (select time_stamp,count,if (count=0,#curRank :=0,#curRank := #curRank + 1) as rank from ff,(SELECT #curRank := 0) r) t WHERE rank=1;
+------------+-------+------+
| time_stamp | count | rank |
+------------+-------+------+
| 1467820408 | 5 | 1 |
| 1467820412 | 3 | 1 |
| 1467820429 | 6 | 1 |
| 1467820405 | 1 | 1 |
+------------+-------+------+
4 rows in set (0.00 sec)
mysql> SELECT sum(count) as total FROM
(select time_stamp,count,if (count=0,#curRank :=0,#curRank := #curRank + 1) as rank from ff,
(SELECT #curRank := 0) r) t WHERE rank=1;
+-------+
| total |
+-------+
| 15 |
+-------+
1 row in set (0.00 sec)
you can get it with simple inner query
SELECT SUM(a.cnt)
FROM
( SELECT x.*
, MIN(y.time_stamp) next
FROM my_table x
LEFT
JOIN my_table y
ON y.time_stamp > x.time_stamp
GROUP
BY x.time_stamp
) a
LEFT
JOIN my_table b
ON b.time_stamp = a.next
AND b.cnt > a.cnt
WHERE b.cnt IS NULL;
You need to identify when a value shifts. One way to get the previous value uses variables:
select sum(count)
from (select t.*,
(if((#old_c := #c) is null, 0, -- never happens
if((#c := count) is not null, #old_c, #old_c)
)
) as prev_count
from t cross join
(select #c := -1) params
order by time_stamp
) t
where prev_count >= count;
The expression for getting the previous count is a bit complicated. MySQL does not guarantee the order of evaluation of expressions, so the assignment of the new value of count and returning the old value needs to be in a single expression.
You need GROUP BY and HAVING, like this:
select sum ( count )
from table
group by time_stamp
having count = max(count)
Very simple solution :
1- take the lag of the column time_stamp
2- take the difference of orif time_stamp column and the lag column
3- sum the values of count after filtering out the records for -1
+------------+-------+------+-----------------+-------+
| a | b | c | d | a-d |
| time_stamp | count | flag | lag_time_stamp | diff |
| 1467820429 | 6 | * | nulll | null |
| 1467820428 | 5 | | 1467820429 | -1 |
| 1467820427 | 4 | | 1467820428 | -1 |
| 1467820426 | 3 | | 1467820427 | -1 |
| 1467820416 | 2 | * | 1467820426 | -10 |
| 1467820415 | 1 | | 1467820416 | -1 |
| 1467820413 | 3 | * | 1467820415 | -2 |
| 1467820412 | 3 | | 1467820413 | -1 |
| 1467820411 | 2 | | 1467820412 | -1 |
| 1467820409 | 1 | * | 1467820411 | -2 |
| 1467820408 | 0 | | 1467820409 | -1 |
| 1467820405 | 1 | * | 1467820408 | -3 |
| 1467820404 | 0 | | 1467820405 | -1 |
| 1467820400 | 5 | * | 1467820404 | -4 |
+------------+-------+------+-----------------+-------+
--sum the values of the table that we got after filtering the records for -1
+------------+-------+
| time_stamp | count |
+------------+-------+
| 1467820429 | 6 |
| 1467820416 | 2 |
| 1467820413 | 3 |
| 1467820409 | 1 |
| 1467820405 | 1 |
| 1467820400 | 5 |
+------------+-------+

How to get top n records group by wildcards

Here is my table example:
+--------+-------------+
| id | city_detail |
+--------+-------------+
| 1 | 12_hyd_test |
| 2 | 13_blr_test |
| 3 | 15_blr_test |
| 4 | 18_hyd_test |
| 5 | 17_coi_test |
| 6 | 22_coi_test |
| 7 | 62_hyd_test |
| 8 | 72_blr_test |
| 9 | 92_blr_test |
| 10 | 42_hyd_test |
| 11 | 21_coi_test |
| 12 | 82_coi_test |
+--------+-------+-----+
From this table, how to use like condition with group by to select like this
+--------+-------------+
| id | city_detail |
+--------+-------------+
| 12 | 82_coi_test |
| 11 | 21_coi_test |
| 10 | 42_hyd_test |
| 7 | 62_hyd_test |
| 9 | 92_blr_test |
| 8 | 72_blr_test |
+--------+-------+-----+
In each city show only two result (%coi% or %hyd% or '%blr%') order by id DESC
Probably the simplest method is to use variables:
select e.*
from (select e.*,
(#rn := if(#c = substr(city_detail, 4), #rn + 1,
if(#c := substr(city_detail, 4), 1, 1
)
) as seqnum
from example e cross join
(select #c := '', #rn := 0) params
order by substr(city_detail, 4)
) e
where rn <= 2;

mysql split row to multiple columns and rows

I have a table with the following structure and these values in column assoc.
| id | assoc |
| 1 | |3|-1|107|-4|146|-6| |
| 2 | |19|-3|107|-5| |
| 3 | |42|-1| |
You can see it here
This is a wrong mysql table structure. So I think that the right structure it must be:
| id | assoc | attrib | order |
| 1 | 3 | 1 | 1 |
| 1 | 107 | 4 | 2 |
| 1 | 146 | 6 | 3 |
| 2 | 19 | 3 | 1 |
| 2 | 107 | 5 | 2 |
| 3 | 42 | 1 | 1 |
Is possible to do with a mysql script on phpmyadmin?
SET #prev := null;
SET #cnt := 0;
SELECT id,blah,mah,IF(#prev <> id, #cnt := 1, #cnt := #cnt + 1) AS rank, #prev := id
FROM(
SELECT id,REPLACE(SUBSTRING_INDEX(assoc,'|',2),'|','')*1 as blah,
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(assoc,'-0'),'|',3),'-',-1)as mah FROM table1
UNION ALL
SELECT id,
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(assoc,'|0'),'|',4),'|',-1)*1 as blah,
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(assoc,'-0'),'|',5),'-',-1)as mah FROM table1
UNION ALL
SELECT id,
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(assoc,'|0'),'|',6),'|',-1)*1 as blah,
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(assoc,'-0'),'|',7),'-',-1)as mah FROM table1
)x
WHERE x.mah !='0'
ORDER BY x.id ,x.blah
FIDDLE