How to get top n records group by wildcards - mysql

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;

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;

Order by logic and get mixed results?

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;

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

SQL Query Conditional accumulation

it is possible to display accumulated data, resetting the count based on a condition?
I would like to create a script to accumulate if there is value 1 in cell number, but if another value the count should be restarted. Something like what is displayed in the column cumulative_with_condition.
+----+------------+--------+
| id | release | number |
+----+------------+--------+
| 1 | 2016-07-08 | 4 |
| 2 | 2016-07-09 | 1 |
| 3 | 2016-07-10 | 1 |
| 4 | 2016-07-12 | 2 |
| 5 | 2016-07-13 | 1 |
| 6 | 2016-07-14 | 1 |
| 7 | 2016-07-15 | 1 |
| 8 | 2016-07-16 | 2-3 |
| 9 | 2016-07-17 | 3 |
| 10 | 2016-07-18 | 1 |
+----+------------+--------+
select * from version where id > 1 and id < 9;
+----+------------+--------+---------------------------+
| id | release | number | cumulative_with_condition |
+----+------------+--------+---------------------------+
| 2 | 2016-07-09 | 1 | 1 |
| 3 | 2016-07-10 | 1 | 2 |
| 4 | 2016-07-12 | 2 | 0 |
| 5 | 2016-07-13 | 1 | 1 |
| 6 | 2016-07-14 | 1 | 2 |
| 7 | 2016-07-15 | 1 | 3 |
| 8 | 2016-07-16 | 2-3 | 0 |
+----+------------+--------+---------------------------+
You want something like row_number() (not exactly, but like that). You can do that using variables:
select t.*,
(#rn := if(number = 1, #rn + 1,
if(#n := number, 0, 0)
)
) as cumulative_with_condition
from t cross join
(select #n := '', #rn := 0) params
order by t.id;
As an alternative to using user variables, as demonstrated by Gordon Linoff, in this case it's also possible to self-join, group and count:
SELECT t.id, t.release, t.number, COUNT(version.id) AS cumulative_with_condition
FROM version RIGHT JOIN (
SELECT highs.*, MAX(lows.id) min
FROM version lows RIGHT JOIN version highs ON lows.id <= highs.id
WHERE lows.number <> '1'
GROUP BY highs.id
) t ON version.id > t.min AND version.id <= t.id
WHERE t.id > 1 AND t.id < 9
GROUP BY t.id
See it on sqlfiddle.
But, frankly, neither approach is particularly elegant—as I commented previously, you're probably best off implementing this within your application code.

Recursive query with loop counter

In MySQL I have the following object table:
| id | parent_id | name |
|---:|----------:|:-----|
| 1 | NULL | root |
| 2 | 9 | obj1 |
| 3 | 10 | obj2 |
| 4 | 7 | obj3 |
| 5 | 8 | obj4 |
| 6 | 4 | obj5 |
| 7 | 2 | obj6 |
| 8 | 3 | obj7 |
| 9 | 1 | obj8 |
| 10 | 1 | obj9 |
And the following Query to get a path from my element (id 6) to root:
SELECT #id :=
(
SELECT parent_id
FROM object
WHERE id = #id
) AS tree
FROM (
SELECT #id := 6
) a
STRAIGHT_JOIN object
WHERE #id IS NOT NULL
The result is:
| tree |
|-----:|
| 4 |
| 7 |
| 2 |
| 9 |
| 1 |
| NULL |
But I need a counter in the result, that represents the travertion through the path (like a counter in a for-loop):
| tree | ctr |
|-----:|----:|
| 4 | 1 |
| 7 | 2 |
| 2 | 3 |
| 9 | 4 |
| 1 | 5 |
| NULL | 6 |
Is it possible to add the counter and how?
I'm not sure if your query is guaranteed to work, but it might (between the straight join and subquery, the order of execution might be guaranteed). You can get a counter by using another variable:
SELECT #id := (SELECT parent_id
FROM object
WHERE id = #id
) AS tree,
(#rn := #rn + 1) as ctr
FROM (SELECT #id := 6, #rn := 0) a STRAIGHT_JOIN
object
WHERE #id IS NOT NULL;