MySQL get item number on select - mysql

I am not an expert in MySQL and not sure how to solve this using MySQL only.
I have table like this:
mysql> describe items;
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| itemID | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| date | timestamp | NO | | | | |
+-------------+---------------------+------+-----+---------+----------------+
And I would like to get the position of an item as if I will make a selection and sort (or filter). For example, I sort by date and item with itemID=79 will be item with index 126 if I will make a loop. So I would like to get this number 126 directly from database. This is probably similar to ranking but I am not sure but I am not sure if it is possible.

SELECT itemID, date
FROM (
SELECT itemID, date, #rn:= #rn +1 as rank
FROM items, (SELECT #rn := 0) as para
ORDER BY date
) T
WHERE rank = 126
OR
SELECT itemID, date, rank
FROM (
SELECT itemID, date, #rn:= #rn +1 as rank
FROM items, (SELECT #rn := 0) as para
ORDER BY date
) T
WHERE itemID = 79

Related

mysql avg length of a date squence

I have a report i'm trying to figure out, but I would like to do it all with in a SQL statement instead of needing to iterate over a bunch of data in script to do it.
I have a table that is structured like:
CREATE TABLE `batch_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`record_id` int(11) DEFAULT NULL,
`created` DATE NOT NULL,
PRIMARY KEY (`id`),
KEY `record_id` (`record_id`)
);
The Date field is always YEAR-MONTH-01. Data looks something like:
+------+-----------+------------+
| id | record_id | created |
+------+-----------+------------+
| 1 | 1 | 2019-01-01 |
| 2 | 2 | 2019-01-01 |
| 3 | 3 | 2019-01-01 |
| 4 | 1 | 2019-02-01 |
| 5 | 2 | 2019-02-01 |
| 6 | 1 | 2019-03-01 |
| 7 | 3 | 2019-03-01 |
| 8 | 1 | 2019-04-01 |
| 9 | 2 | 2019-04-01 |
+------+-----------+------------+
So what I'm trying to do, with out having to create a looping script, is find the AVG number of sequential months for each record. Example with the data above would be:
Record_id 1 would have a avg of 4 months.
Record_id 2 would be 1.5
Record_id 3 would be 1
I can write a script to iterate through all the records. I just would rather avoid that.
This is a gaps-and-islands problem. You simply need an enumeration of the rows for this to work. In MySQL 8+, you would use row_number() but you can use a global enumeration here:
select record_id, min(created) as min_created, max(created) as max_created, count(*) as num_months
from (select bi.*, (created - interval n month) as grp
from (select bi.*, (#rn := #rn + 1) as n -- generate some numbers
from batch_item bi cross join
(select #rn := 0) params
order by bi.record_id, bi.month
) bi
) bi
group by record_id, grp;
Note that when using row_number(), you would normally partition by record_id. However that is not necessary, if the numbers are created in the correct sequence.
The above query gets the islands. For your final results, you need one more level of aggregation:
select record_id, avg(num_months)
from (select record_id, min(created) as min_created, max(created) as max_created, count(*) as num_months
from (select bi.*, (created - interval n month) as grp
from (select bi.*, (#rn := #rn + 1) as n -- generate some numbers
from batch_item bi cross join
(select #rn := 0) params
order by bi.record_id, bi.month
) bi
) bi
group by record_id, grp
) bi
group by record_id;
This is not a tested solution. It should work in MySQL 8.x with minor tweaks, since I don't remember date arithmetic in MySQL:
with
a as ( -- the last row of each island
select *
from batch_item
where lead(created) over(partition by record_id order by created) is null
or lead(created) over(partition by record_id order by created)
> created + 1 month -- Fix the date arithmetic here!
),
e as ( -- each row, now with the last row of its island
select b.id, b.record_id, min(a.last_created) as end_created
from batch_item b
join a on b.record_id = a.record_id and b.created <= a.created
group by b.id, b.record_id
),
m as ( -- each island with the number of months it has
select
record_id, end_created, count(*) as months
from e
group by record_id, end_created
)
select -- the average length of islands for each record_id
record_id, avg(months) as avg_months
from m
group by record_id

Get user's highest score from a table

I have a feeling this is a very simple question but maybe i'm having brain fart right now and just can't seem to figure out how to go about it.
I have a MySQL table structure like below
+---------------------------------------------------+
| id | date | score | speed | user_id |
+---------------------------------------------------+
| 1 | 2016-11-17 | 2 | 133291 | 17 |
| 2 | 2016-11-17 | 6 | 82247 | 17 |
| 3 | 2016-11-17 | 6 | 21852 | 17 |
| 4 | 2016-11-17 | 1 | 109338 | 17 |
| 5 | 2016-11-17 | 7 | 64762 | 61 |
| 6 | 2016-11-17 | 8 | 49434 | 61 |
Now i can get a particular user's best performance by doing this
SELECT *
FROM performance
WHERE user_id = 17 AND date = '2016-11-17'
ORDER BY score desc,speed asc LIMIT 1
This should return the row with ID = 3. Now what I want is a single query to run to be able to return that 1 such row for each unique user_id in the table. So the resulting result would be something like this
+---------------------------------------------------+
| id | date | score | speed | user_id |
+---------------------------------------------------+
| 3 | 2016-11-17 | 6 | 21852 | 17 |
| 6 | 2016-11-17 | 8 | 49434 | 61 |
Also further more, can I have another question within this same query that would further sort this eventual resulting table by the same criteria of sort (score desc, speed asc). Thanks
A simple method uses a correlated subquery:
select p.*
from performance p
where p.date = '2016-11-17' and
p.id = (select p2.id
from performance p2
where p2.user_id = p.user_id and p2.date = p.date
order by score desc, speed asc
limit 1
);
This should be able to take advantage of an index on performance(date, user_id, score, speed).
Is easy using variable to emulate row_number() over (partition by Order by)
Explanation:
First create two variables in the subquery.
Order by user_id so when user change the #rn reset to 1
Order by score desc, speed asc so each row will have a row_number, and the one you want always will have rn = 1
#rn := you change #rn for each row
if you have a new user_id then #rn is set to 1
otherwise #rn is set to #rn+1
SQL Fiddle Demo
SELECT `id`, `date`, `score`, `speed`, `user_id`
FROM (
SELECT *,
#rn := if(#user_id = `user_id`,
#rn + 1 ,
if(#user_id := `user_id`,1,1)
) as rn
FROM Table1
CROSS JOIN (SELECT #user_id := 0, #rn := 0) as param
WHERE date = '2016-11-17'
ORDER BY `user_id`, `score` desc, `speed` asc
) T
where T.rn =1
OUTPUT
For mysql
You can try with a double in subselect and group by
select * from performance
where (user_id, score,speed ) in (
SELECT user_id, max_score, max(speed)
FROM performance
WHERE (user_id, score) in (select user_id, max(score) max_score
from performance
group by user_id)
group by user_id, max_score
);

Return multiple rows from multi SELECT subquery

query
SELECT
(SELECT NAME FROM product_component) AS pcNAME,
(SELECT PROJECT_NAME FROM jira_project) AS jpNAME,
(SELECT FILTER_NAME FROM jira_filter) AS jfNAME
Each SELECT will return an indeterminate number of rows. I get the error Subquery returns more than 1 row. My desired output will be something like this (quick sketch):
=======================================
| pcNAME | jpNAME | jfNAME |
=======================================
| data | data | data |
+------------+------------+-----------+
| data | data | data |
+------------+------------+-----------+
| data | data | data |
+------------+------------+-----------+
| | data | data |
+------------+------------+-----------+
| | data | data |
+------------+------------+-----------+
| | data | |
+------------+------------+-----------+
Each column may produce a different number of rows than the others. So I will want to produce the amount of rows from the max and then blank out the others that don't fill the max number of rows.
NOTE: None of these tables have a shared column so cannot achieve as INNER JOIN
Any ideas on how this can be achieved?
One way to handle this in MySQL to use to variables, union all and aggregation:
SELECT MAX(NAME) as NAME, MAX(PROJECT_NAME) as PROJECT_NAME,
MAX(FILTER_NAME) as FILTER_NAME
FROM ((SELECT (#rnpc := #rnpc + 1) as rn, NAME, NULL as PROJECT_NAME, NULL as FILTER_NAME
FROM product_component CROSS JOIN
(SELECT #rnpc := 0) params
) UNION ALL
(SELECT (#rnpn := #rnpn + 1) as rn, NULL, PROJECT_NAME, NULL as FILTER_NAME
FROM jira_project CROSS JOIN
(SELECT #rnpn := 0) params
) UNION ALL
(SELECT (#rnf := #rnf + 1) as rn, NAME, NULL as PROJECT_NAME, NULL as FILTER_NAME
FROM jira_filter CROSS JOIN
(SELECT #rnf := 0) params
)
) t
GROUP BY rn
ORDER BY rn;

MySQL - Enumerate all rows within a minute and keep the first one

I have a MySQL table with the following columns :
+----------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| date | varchar(11) | NO | | NULL | |
| time | varchar(12) | NO | | NULL | |
| gmt_offset | varchar(2) | YES | | NULL | |
| type | varchar(10) | YES | | NULL | |
| yield_b | varchar(10) | YES | | NULL | |
| yield_d | varchar(10) | YES | | NULL | |
+----------------+-------------+------+-----+---------+----------------+
And I am trying to keep one row per minute (the first one of every minute) using the following query:
create table temp_table1 as
select t.*
from (select t1.*,
(#rn := if(#prevd <> date or minute(time) <> #prevm, 1,
if(#prevd := date, if(#prevm := minute(time), 1, 1), 1)
)
) as seqnum
from table1 t1 cross join
(select #rn := 0, #prevd := 0, #prevm := 0) vars
order by date, time
) t
where seqnum = 1;
truncate table table1;
insert into table1(col1, . . ., coln)
select col1, . . . , coln
from temp_table1;
I try with this query to enumerate all rows within a minute, and then keep the first row for every minute.. But this doesn't work, it simply puts 1 in seqnum for every row, and does not get rid of any rows at all.
Can anybody help me make this query work and keep the first row of every minute?
Thanks in advance!
I don't know why the logic was inverted in the first if(). I think I was confused. Apologies for that.
create table temp_table1 as
select t.*
from (select t1.*,
(#rn := if(#prevd = date and minute(time) = #prevm, #rn + 1,
if(#prevd := date, if(#prevm := minute(time), 1, 1), 1)
)
) as seqnum
from table1 t1 cross join
(select #rn := 0, #prevd := 0, #prevm := 0) vars
order by date, time
) t
where seqnum = 1;
The logic for the variable assignment in this case is:
First clause does the comparisons and increments the #rn value.
The next clauses assign the new values of the variables using nested if() calls, returning 1 for all possibilities.
The purpose of putting all the assignments in a single expression is not to create arbitrarily complicated SQL. MySQL does not guarantee the order of evaluation of expressions in a select. If you have multiple expressions with variable assignments, then they may evaluated in the wrong order.

MySQL Group By Consecutive Rows

I have a feed application that I am trying to group results from consecutively.
My table looks like this:
postid | posttype | target | action | date | title | content
1 | userid | NULL | upgrade | 0000-01-00 00:00:00 | Upgraded 1 | exmple
1 | userid | NULL | upgrade | 0000-01-00 00:00:01 | Upgraded 2 | exmple
1 | userid | NULL | downgrade | 0000-01-00 00:00:02 | Downgraded | exmple
1 | userid | NULL | upgrade | 0000-01-00 00:00:03 | Upgraded | exmple
What I would like the outcome to be is:
postid | posttype | target | action | date | title | content
1 | userid | NULL | upgrade | 0000-01-00 00:00:01 | Upgrade 1 | exmple,exmple
1 | userid | NULL | downgrade | 0000-01-00 00:00:02 | Downgraded | exmple
1 | userid | NULL | upgrade | 0000-01-00 00:00:03 | Upgraded | exmple
So as you can see because Upgrade 1 & Upgrade 2 were sent Consecutively, it groups them together. The "Action" table is a reference, and should be used for the consecutive grouping as well as the postid & posttype.
I looked around on SO but didnt see anything quite like mine. Thanks in advance for any help.
Here's another version that works with MySQL Variables and doesn't require 3 level nesting deep. The first one pre-sorts the records in order by postID and Date and assigns them a sequential number per group whenever any time a value changes in one of the Post ID, Type and/or action. From that, Its a simple group by... no comparing record version T to T2 to T3... what if you wanted 4 or 5 criteria... would you have to nest even more entries?, or just add 2 more #sql variables to the comparison test...
Your call on which is more efficient...
select
PreQuery.postID,
PreQuery.PostType,
PreQuery.Target,
PreQuery.Action,
PreQuery.Title,
min( PreQuery.Date ) as FirstActionDate,
max( PreQuery.Date ) as LastActionDate,
count(*) as ActionEntries,
group_concat( PreQuery.content ) as Content
from
( select
t.*,
#lastSeq := if( t.action = #lastAction
AND t.postID = #lastPostID
AND t.postType = #lastPostType, #lastSeq, #lastSeq +1 ) as ActionSeq,
#lastAction := t.action,
#lastPostID := t.postID,
#lastPostType := t.PostType
from
t,
( select #lastAction := ' ',
#lastPostID := 0,
#lastPostType := ' ',
#lastSeq := 0 ) sqlVars
order by
t.postid,
t.date ) PreQuery
group by
PreQuery.postID,
PreQuery.ActionSeq,
PreQuery.PostType,
PreQuery.Action
Here's my link to SQLFiddle sample
For the title, you might want to adjust the line...
group_concat( distinct PreQuery.Title ) as Titles,
At least this will give DISTINCT titles concatinated... much tougher to get let without nesting this entire query one more level by having the max query date and other elements to get the one title associated with that max date per all criteria.
There is no primary key in your table so for my example I used date. You should create an auto increment value and use that instead of the date in my example.
This is a solution (view on SQL Fiddle):
SELECT
postid,
posttype,
target,
action,
COALESCE((
SELECT date
FROM t t2
WHERE t2.postid = t.postid
AND t2.posttype = t.posttype
AND t2.action = t.action
AND t2.date > t.date
AND NOT EXISTS (
SELECT TRUE
FROM t t3
WHERE t3.date > t.date
AND t3.date < t2.date
AND (t3.postid != t.postid OR t3.posttype != t.posttype OR t3.action != t.action)
)
), t.date) AS group_criterion,
MAX(title),
GROUP_CONCAT(content)
FROM t
GROUP BY 1,2,3,4,5
ORDER BY group_criterion
It basically reads:
For each row create a group criterion and in the end group by it.
This criterion is the highest date of the rows following the current one and having the same postid, posttype and action as the current one but there may be not a row of different postid, posttype or action between them.
In other words, the group criterion is the highest occurring date in a group of consecutive entries.
If you use proper indexes it shouldn't be terribly slow but if you have a lot of rows you should think of caching this information.