Implementation of Rank and Dense Rank in MySQL - mysql

Hi I am a Begineer in MySql and I want to Implement Rank and Dense Rank Function on MySql for the given problem statement stated below.
I have a Table called Transaction which has following Columns:
Transaction_no | Register|Adult|child
The Input Data is as follows:
INPUT
+----------------+----------+-------+-------+
| transaction_no | register | adult | child |
+----------------+----------+-------+-------+
| 1234 | A | 0 | 1 |
| 1234 | A | 1 | 2 |
| 1234 | A | 1 | 1 |
| 3456 | B | 1 | 0 |
| 5678 | B | 1 | 0 |
| 2468 | C | 1 | 0 |
| 2468 | C | 0 | 1 |
+----------------+----------+-------+-------+
My Requirement is to add another column namely rn using mySQL which will use Rank and dense rank like logic to generate the following intermediate output
INTERMEDIATE
+----------------+----------+-------+-------+----+
| transaction_no | register | adult | child | rn |
+----------------+----------+-------+-------+----+
| 1234 | A | 0 | 1 | 1 |
| 1234 | A | 1 | 2 | 2 |
| 1234 | A | 1 | 1 | 3 |
| 3456 | B | 1 | 0 | 1 |
| 5678 | B | 1 | 0 | 1 |
| 2468 | C | 1 | 0 | 1 |
| 2468 | C | 0 | 1 | 2 |
+----------------+----------+-------+-------+----+
Here the partition is done on transaction number.
The Final Query Output should contain all the rows whose rn=1 and the rn value should not displayed.
OUTPUT
+----------------+----------+-------+-------+
| transaction_no | register | adult | child |
+----------------+----------+-------+-------+
| 1234 | A | 0 | 1 |
| 3456 | B | 1 | 0 |
| 5678 | B | 1 | 0 |
| 2468 | C | 1 | 0 |
+----------------+----------+-------+-------+
Oracle Documentation for Reference : OracleDocument
I have also added SQL fiddle for Reference.SqlFiddle
Please help me on this.

MySQL Solution:
SELECT transaction_no, register, adult, child
FROM (
SELECT
( CASE WHEN #prev_tno != transaction_no THEN #rn:=1
ELSE #rn:=(#rn+1) END ) AS rn
, #prev_tno:=transaction_no AS transaction_no
, register, adult, child
FROM instructor
, (SELECT #rn:=0, #prev_tno:=NULL) AS row_nums
) src
WHERE rn = 1
ORDER BY register, transaction_no
Note: Desired ordering can only be achieved on explicit request, hence ORDER BY is used in the query.

declare #t table (TransactionId int,register VARCHAR(1),Adult INT,Child INT )
insert into #t (TransactionId,register,Adult,Child)values (1234,'A',0,1),
(1234,'A',1,2),(1234,'A',1,1),(3456,'B',1,0),(5678,'B',1,0),(2468,'C',1,0),(2468,'C',0,1)
;with cte as (
select TransactionId,register,Adult,Child,ROW_NUMBER()OVER(PARTITION BY TransactionId,register ORDER BY register ) RN from #t
)
Select TransactionId,register,Adult,Child from cte where RN = 1

create table TRANSACTION
(
Transaction_no int,
Register varchar2(2),
Adult int,
child int
);
Insert rows in TRANSACTION table.
Sqlquery for Intermediate output:-
select TRANSACTION_NO,REGISTER,Adult,child, DENSE_RANK() over(PARTITION BY REGISTER ORDER BY Adult,child) as rnk from TRANSACTION;
Sqlquery for Final output:-
select TRANSACTION_NO,REGISTER,Adult,child from (
select TRANSACTION_NO,REGISTER,Adult,child, DENSE_RANK() over(PARTITION BY TRANSACTION_NO ORDER BY Adult,child) as rnk from TRANSACTION)
where rnk=1;
I tried it on Oracle.

Related

How to query MIN value of MAX subquery with two distinct columns?

I have a table like this:
+---------------+--------------+------+-----+----------+
| Field | Type | Null | Key | Default |
+---------------+--------------+------+-----+----------+
| id | smallint(6) | NO | PRI | NULL |
| Book | tinyint(4) | NO | | NULL |
| Chapter | smallint(6) | NO | | NULL |
| Paragraph | smallint(6) | NO | | NULL |
| Text | text | YES | | NULL |
| RevisionNum | mediumint(9) | NO | PRI | NULL |
+---------------+--------------+------+-----+----------+
mysql> select id,Book,Chapter,Paragraph,RevisionNum FROM MyTable ORDER BY id LIMIT 11;
+-----+------+---------+-----------+-------------+
| id | Book | Chapter | Paragraph | RevisionNum |
+-----+------+---------+-----------+-------------+
| 1 | 1 | 1 | 1 | 0 |
| 1 | 1 | 1 | 1 | 1 |
| 1 | 1 | 1 | 1 | 2 |
| 2 | 1 | 2 | 2 | 0 |
| 2 | 1 | 2 | 2 | 1 |
| 2 | 1 | 2 | 2 | 2 |
| 2 | 1 | 2 | 2 | 3 |
| 3 | 1 | 2 | 3 | 0 |
| 4 | 1 | 2 | 4 | 0 |
| 4 | 1 | 2 | 4 | 1 |
| 5 | 1 | 3 | 5 | 0 |
+-----+------+---------+-----------+-------------+
To find a book or chapter which has no unrevised paragraph,
I wish to query either the minimum value of the maximums of
all the distinct id's for that chapter or book, or else in
some fashion determine that no id remains unedited (with a
MAX(RevisionNum) of zero).
Most of my attempts to date have ended in errors like this one:
SELECT DISTINCT Book,RecordNum FROM MyTable
-> WHERE 0 < ALL (SELECT DISTINCT RecordNum,MAX(RevisionNum)
FROM MyTable
WHERE MAX(RevisionNum) > 0);
ERROR 1111 (HY000): Invalid use of group function
...And I wasn't using the "GROUP BY" function at all!
The following query produces results, but simply
gives ALL id's, and does not actually show a unique
set of Book records, as requested. How could this happen?
SELECT DISTINCT Book,id,MAX(RevisionNum) FROM MyTable GROUP BY id LIMIT 5;
+------+----+------------------+
| Book | id | MAX(RevisionNum) |
+------+----+------------------+
| 1 | 1 | 30 |
| 1 | 2 | 16 |
| 1 | 3 | 15 |
| 1 | 4 | 10 |
| 1 | 5 | 9 |
+------+----+------------------+
What would the correct query be to give results more like this:
+------+-----+-----------------------+
| Book | id | MIN(MAX(RevisionNum)) |
+------+-----+-----------------------+
| 1 | 5 | 3 |
| 2 | 17 | 1 |
| 3 | 33 | 2 |
| 4 | 147 | 0 |
| 5 | 225 | 2 |
+------+-----+-----------------------+
Are you looking for two levels of aggregation?
select id, book, min(max_revisionnum)
from (select id, book, chapter, paragraph, max(revisionnum) as max_revisionnum
from mytable
group by id, book, chapter, paragraph
) t
group by id, book;
EDIT:
Based on your comment, you can use:
select *
from (select id, book, chapter, paragraph, max(revisionnum) as max_revisionnum,
row_number() over (partition by book order by max(revisionnum) desc) as seqnum
from mytable
group by id, book, chapter, paragraph
) t
where seqnum = 1;
Here is a db<>fiddle.
In older versions of MariaDB, you can use a correlated subquery:
select t.*
from mytable t
where (id, book, chapter, paragraph, revisionnum) = (select t2.id, t2.book, t2.chapter, t2.paragraph, t2.revisionnum
from mytable t2
where t2.book = t.book
order by t2.revisionnum desc
limit 1
);
For this query, try adding an index on (book, revisionnum desc).

sort data by specific order sequence (mysql)

So, let say I have this data
id | value | group
1 | 100 | A
2 | 120 | A
3 | 150 | B
4 | 170 | B
I want to sort it so it become like this
id | value | group
1 | 100 | A
3 | 150 | B
2 | 120 | A
4 | 170 | B
there will be more group than that, so if I the data ordered the group like (A,C,B,D,B,C,A), it will become (A,B,C,D,A,B,C)
You can add a counter column to the table, which will be used to sort the table:
select t.id, t.value, t.`group`
from (
select t.id, t.value, t.`group`,
(select count(*) from tablename
where `group` = t.`group` and id < t.id) counter
from tablename t
) t
order by t.counter, t.`group`
See the demo.
Results:
| id | value | group |
| --- | ----- | ----- |
| 1 | 100 | A |
| 3 | 150 | B |
| 2 | 120 | A |
| 4 | 170 | B |
You can approach this as
SELECT *
FROM `tablename`
ORDER BY
row_number() OVER (PARTITION BY `group` ORDER BY `group`), `group`

Counting and limiting number of rows per field value not working

I'm trying to limit the number of rows per field value of a given query. I've found this answered question:
here
As in the first answer of the link, I've created the following table:
create table mytab (
id int not null auto_increment primary key,
first_column int,
second_column int
) engine = myisam;
Inserted this data:
insert into mytab (first_column,second_column) values
(1,1),
(1,4),
(2,10),
(3,4),
(1,4),
(2,5),
(1,6);
And finally run this query
select
id,
first_column,
second_column,
row_num
from
(select
*,
#num := if(#first_column = first_column, #num + 1, 1) as row_num,
#first_column:=first_column as c
from mytab
order by first_column,id) as t,
(select #first_column:='',#num:=0) as r;
But instead of getting this result, where the row_num increases whenever first_column is repeated,
+----+--------------+---------------+---------+
| id | first_column | second_column | row_num |
+----+--------------+---------------+---------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 4 | 2 |
| 5 | 1 | 4 | 3 |
| 7 | 1 | 6 | 4 |
| 3 | 2 | 10 | 1 |
| 6 | 2 | 5 | 2 |
| 4 | 3 | 4 | 1 |
+----+--------------+---------------+---------+
I get this result:
+----+--------------+---------------+---------+
| id | first_column | second_column | row_num |
+----+--------------+---------------+---------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 4 | 1 |
| 5 | 1 | 4 | 1 |
| 7 | 1 | 6 | 1 |
| 3 | 2 | 10 | 1 |
| 6 | 2 | 5 | 1 |
| 4 | 3 | 4 | 1 |
+----+--------------+---------------+---------+
I literally copied the code from the link. I checked in SQL Fiddle and code works fine. I'm using XAMPP. Could that be the reason? If it is, is there any workaround to get something like the above working?
I'd really appreciate some help. Thanks in advance.
The variable assignment has to be in the sub-query.
select
id,
first_column,
second_column,
row_num
from
(select
m.*,
#num := if(#first_column = first_column, #num + 1, 1) as row_num,
#first_column:=first_column as c
from mytab m
cross join (select #first_column:='',#num:=0) r --this was in the outer query previously
order by first_column,id
) t

Get last balance sign change in (My)SQL

I have a Transaction table that records every amount added to or subtracted from the balance of a Customer, with the new balance:
+----+------------+------------+--------+---------+
| id | customerId | timestamp | amount | balance |
+----+------------+------------+--------+---------+
| 1 | 1 | 1000000001 | 10 | 10 |
| 2 | 1 | 1000000002 | -20 | -10 |
| 3 | 1 | 1000000003 | -10 | -20 |
| 4 | 2 | 1000000004 | -5 | -5 |
| 5 | 2 | 1000000005 | -5 | -10 |
| 6 | 2 | 1000000006 | 10 | 0 |
| 7 | 3 | 1000000007 | -5 | -5 |
| 8 | 3 | 1000000008 | 10 | 5 |
| 9 | 3 | 1000000009 | 10 | 15 |
| 10 | 4 | 1000000010 | 5 | 5 |
+----+------------+------------+--------+---------+
The Customer table stores the current balance, and looks like:
+----+---------+
| id | balance |
+----+---------+
| 1 | -20 |
| 2 | 0 |
| 3 | 15 |
| 4 | 5 |
+----+---------+
I would like to add a balanceSignSince column, that would store the timestamp at which the balance sign last changed. Transitioning to and from positive, negative, or zero counts as a balance change.
After the update, based on the above data, the Customer table should contain:
+----+---------+------------------+
| id | balance | balanceSignSince |
+----+---------+------------------+
| 1 | -20 | 1000000002 |
| 2 | 0 | 1000000006 |
| 3 | 15 | 1000000008 |
| 4 | 5 | 1000000010 |
+----+---------+------------------+
How can I write a SQL query that updates every Customer with the last time the balance sign changed, based on the Transaction table?
I suspect I can't do this without a quite complex stored procedure, but am curious to see if any clever ideas come up.
This uses a simulated rank() function.
select customerId, min(tstamp) from
(
select tstamp,
if (#cust = customerId and sign(#bal) = sign(balance), #rn := #rn,
if (#cust = customerId and sign(#bal) <> sign(balance), #rn := #rn + 1, #rn := 0)) as rn,
#cust := customerId as customerId, #bal := balance as balance
from
(select #rn := 0) x,
(select id, #cust := customerId as customerId, tstamp, amount, #bal := balance as balance
from trans order by customerId, tstamp desc) y
) z
where rn = 0
group by customerId;
Check it: http://rextester.com/XJVKK61181
This script returns a table like this:
+------------+----+------------+---------+
| tstamp | rn | customerId | balance |
+------------+----+------------+---------+
| 1000000003 | 0 | 1 | -20 |
| 1000000002 | 0 | 1 | -10 |
| 1000000001 | 1 | 1 | 10 |
| 1000000006 | 0 | 2 | 0 |
| 1000000005 | 2 | 2 | -10 |
| 1000000004 | 2 | 2 | -5 |
| 1000000009 | 0 | 3 | 15 |
| 1000000008 | 2 | 3 | 5 |
| 1000000007 | 3 | 3 | -5 |
| 1000000010 | 0 | 4 | 5 |
+------------+----+------------+---------+
Then selecting min(timestamp) of files where rn = 0:
+------------+-------------+
| customerId | min(tstamp) |
+------------+-------------+
| 1 | 1000000002 |
+------------+-------------+
| 2 | 1000000006 |
+------------+-------------+
| 3 | 1000000009 |
+------------+-------------+
| 4 | 1000000010 |
+------------+-------------+
Updated answer with the restriction that this needs to work on the existing data
The following query should work for most cases, there is still an issue with customers having only a single transaction or no sign change. As this is a one time update, I would run the query below and then do a simple update for all users not having a timestamp set, for them it's going to be the timestamp of the first transaction:
# Find the smallest timestamp, e.g. the
# transaction which changed the signum.
SELECT
p.customerId as customerId,
MIN(t.timestamp) as balanceSignSince
FROM
transaction as t,
(
# find the latest timestamp having
# a different sign for each user.
# Here is the issue with users having
# only a single transaction or no sign
# changes.
SELECT
u.customerId as customerId,
MAX(t.timestamp) as balanceSignSince
FROM
transaction as t,
customer as c,
(
# find the timestamp of the very last
# transaction for every user.
SELECT
t.customerId as customerId,
MAX(t.timestamp) as lastTransaction
FROM
transaction as t
GROUP BY
t.customerId
) as u
WHERE
u.customerId = c.id
AND u.customerId = t.customerId
AND SIGN(c.balance) <> SIGN(t.balance)
GROUP BY
u.customerId
) as p
WHERE
p.customerId = t.customerId
AND p.balanceSignSince < t.timestamp
GROUP BY
p.customerId;
Fiddle: http://sqlfiddle.com/#!9/bd0760/13
Original Answer
This should work to get the timestamp of a sign change:
SELECT
c.id as id,
MAX(t.timestamp) as balanceSignSince
FROM
transaction as t,
customer as c
WHERE
t.customerId = c.id
AND SIGN(t.balance) <> SIGN(c.balance)
This needs to be executed before the customer table is updated with the new balance. If you have a trigger on transation:insert you should probably put the above into the query updating the customer table.

SQL insert into select with parameter for using with event scheduler

Hello I would like to make event for my application that works by insert new 3 record for 1 userID every midnight so the quantity of rows must have
n x 3 n is userIDs
+---------+-----------+-------+------------+------+----------+-------------+-------+
| userID | userNAME | chaID | chaNAME | goal | gender | row_number | dummy |
+---------+-----------+-------+------------+------+----------+-------------+-------+
| 1 | Nanyang | 1 | blahblah | 1 | 2 | 1 | 1 |
| 1 | Nanyang | 21 | something | 1 | 2 | 2 | 1 |
| 1 | Nanyang | 2 | anything | 1 | 2 | 3 | 1 |
| 2 | Julie | 3 | x | 2 | 1 | 1 | 2 |
| 2 | Julie | 12 | y | 2 | 1 | 2 | 2 |
| 2 | Julie | 23 | z | 2 | 1 | 3 | 2 |
| 3 | Kingkong | 4 | a | 1 | 2 | 1 | 3 |
| 3 | Kingkong | 5 | b | 1 | 2 | 2 | 3 |
| 3 | Kingkong | 6 | c | 1 | 2 | 3 | 3 |
+---------+-----------+-------+------------+------+----------+-------------+-------+
the row_number will be looped until they're <= 3
from my written ..
set #num := 0, #type := ‘';
CREATE TABLE random
as
(
SELECT
*
FROM (
select userID,userNAME, chaID, chaNAME,goal,gender,
#num := if(#type = userID, #num +1,1) as row_number,
#type := userID as dummy
from userchar
order by userID
) as x where x.row_number <= 3)
Anyway I used to try to create table with Select the first/least/max row per group in SQL
and it works very well and get the result like i shown. then I need to insert into in event instead of create table so i got this code below.. because I can't use SET #parameterfor insert
INSERT INTO random(userID, userNAME, chaID, chaNAME, goal, gender,row_number,dummy,status)
select *
from (select userID, userNAME, chaID, chaNAME, goal, gender,
(#num := if(#type = userID, #num +1,1)
) as row_number,
userID as dummy,
#stat as status
from hb_usercha u cross join
(select #type = '', #num := 0, #stat := '') params
order by userID,rand()
)
where row_number <= 3;
and this is a result what I got
+---------+-----------+-------+------------+------+----------+-------------+-------+
| userID | userNAME | chaID | chaNAME | goal | gender | row_number | dummy |
+---------+-----------+-------+------------+------+----------+-------------+-------+
| 1 | Nanyang | 1 | blahblah | 1 | 2 | 1 | 1 |
| 1 | Nanyang | 21 | something | 1 | 2 | 1 | 1 |
| 1 | Nanyang | 2 | anything | 1 | 2 | 1 | 1 |
| 1 | Nanyang | 3 | s | 2 | 1 | 1 | 1 |
| 1 | Nanyang | 12 | o | 2 | 1 | 1 | 1 |
| 1 | Nanyang | 23 | m | 2 | 1 | 1 | 1 |
| 1 | Nanyang | 4 | e | 1 | 2 | 1 | 1 |
| 2 | Julie | 5 | xoxo | 1 | 2 | 1 | 2 |
| 2 | Julie | 6 | xxx | 1 | 2 | 1 | 2 |
+---------+-----------+-------+------------+------+----------+-------------+-------+
.
.
.
.
It seems row_number loop isn't working
And I have no idea what's happening
Both code are same just changed parameters path
So It would be very good if someone can explain to me
Thank you so much
Assuming you want to do it for all users exists in user_char table.
Using Union all to get 3 records of each user and store the result in temporary table.
DELIMITER $$
CREATE EVENT `event_run_midnight` ON SCHEDULE EVERY 24 HOUR STARTS '2016-02-09 00:00:00' ON COMPLETION NOT PRESERVE ENABLE DO
BEGIN
drop temporary table if exists temp_users_1;
create Temporary table temp_users_1
select users.*,#row_num:=#row_num+1 as row_num,userID as dummy
from(
select * from user_char
union all
select * from user_char
union all
select * from user_char
) as users,(select #row_num:=0) as row_number
order by UserID;
/*
Copy temp table data to other temp table as MySQL
not allows to use same temp table in single query.
*/
drop temporary table if exists temp_users_2;
create Temporary table temp_users_2
select * from temp_users_1;
-- final query which returns desired output :
INSERT INTO random(userID, userNAME, chaID, chaNAME, goal, gender,row_number,dummy)
Select usr.userID,
usr.userNAME,
usr.chaID,
usr.chaNAME,
usr.goal,
usr.gender,
usr.row_num-usr_grp.min_row_num+1 as row_number,
usr.userID as dummy
from temp_users_1 usr
inner join (
Select userID,
min(row_num) as min_row_num
from temp_users_2 group by userID
) usr_grp on usr.UserID=usr_grp.userID
where usr.row_num-usr_grp.min_row_num+1 <=3; -- Condition to show only 3 records of each users.
END$$
DELIMITER ;
Updates:
1- Added Event Creation.
2- Assuming you are inserting repetitive user records into table name random.
3- This Event will execute daily at 12:00 AM.
Note:
1- You may have to change the table names if tables names are different as in answer.
2- Make sure that you have enabled MySQL Event scheduler in Configuration file otherwise event will not run automatically.
Here is the link which help you enabling MySQL event scheduler if not enabled:
http://geeksterminal.com/enable-mysql-event-scheduler-status/1711/