How to query the ordinal index of each row in MySQL - mysql

Let say I have this table:
| Order ID | User | Order Date |
-----------------------------------
| 1 | Dave | 03/01/2017 |
| 2 | Jim | 03/09/2017 |
| 3 | John | 03/15/2017 |
| 4 | John | 03/18/2017 |
| 5 | Jim | 03/18/2017 |
| 6 | Dave | 03/30/2017 |
| 7 | John | 04/04/2017 |
| 8 | Jim | 04/16/2017 |
Things I want is to put one more column to indicate the n-th order per individual, because I need to analyze the repeated behavior of users.
So the output should be like this:
| Order ID | User | Order Date | n-th |
-----------------------------------------
| 1 | Dave | 03/01/2017 | 1 |
| 2 | Jim | 03/09/2017 | 1 |
| 3 | John | 03/15/2017 | 1 |
| 4 | John | 03/18/2017 | 2 |
| 5 | Jim | 03/18/2017 | 2 |
| 6 | Dave | 03/30/2017 | 2 |
| 7 | John | 04/04/2017 | 3 |
| 8 | Jim | 04/16/2017 | 3 |
That means:
order #1 is Dave's 1st order
order #2 is Jim's 1st order
...
order #5 is Jim's 2nd order
and so on!
How can I get this using raw query in SQL?
Thank you.

In most databases, you would use row_number(). In MySQL, this is best done using variables:
select t.*,
(#rn := if(#u = user, #rn + 1,
if(#u := user, 1, 1)
)
) as nth
from t cross join
(select #u := '', #rn := 0) params
order by user, orderdate;
You can also formulate this query using a correlated subquery, but the method using variables should have better performance.
To get the data back in its original format, use the above as a subquery and then sort by the order id.

Related

Query to fetch sequences of distinct records with LIMIT and OFFSET

So, my question may be confusing and you may ask what do I mean exactly?
Let's assume I've the following table (contacts) in my database:
+----+----------+-----------+
| id | provider | name |
+----+----------+-----------+
| 1 | MSC | Roger |
| 2 | DLL | Steven |
| 3 | DLL | Pensi |
| 4 | DLL | Kendswill |
| 5 | MSC | Mark |
| 6 | LAPTON | Steffan |
| 7 | MSC | John |
| 8 | MSC | Cori |
| 9 | LAPTON | Jason |
+----+----------+-----------+
I'm looking to fetch for example 3 records, so it must be in a specific sequence - the query should yield a sequence (or sequences) of one record for each provider, distinct ones in each sequence (based on the provider in this example), for example, if the LIMIT set to 4, I should get:
+----+----------+-----------+
| id | provider | name |
+----+----------+-----------+
| 1 | MSC | Roger |
| 4 | DLL | Kendswill |
| 6 | LAPTON | Steffan |
| 7 | MSC | John |
+----+----------+-----------+
And if I would set the LIMIT to 4 and set an OFFSET of 4, now I should get:
+----+----------+--------+
| id | provider | name |
+----+----------+--------+
| 5 | MSC | Mark |
| 2 | DLL | Steven |
| 9 | LAPTON | Jason |
| 8 | MSC | Cori |
+----+----------+--------+
And so on and so forth.
As you may see, it should be consistent and should be organized always in the same way (not a random way).
I've heard about gap and islands, but wonder if someone could shed some light on the subject?
Assign each row that has the same provider with an increasing number. Then you can sort by that number. The rows that have an 1 for each provider will show up first, followed by those with the number two.
select *
from (
select #rn := case when provider = #lastprovider then #rn + 1 else 1 end
, #lastprovider := provider
, #rn as provider_row_num
, provider
, name
from Table1
, (select #rn := 0, #lastprovider := '') init
order by
provider
, id
) sub
order by
provider_row_num
, provider
limit 4
Working example at SQL Fiddle.

How to do such a mysql query?

I will ask for help from yours.
Example table:
| ID | NAME |POINT|
| 1 | alex | 2 |
| 2 | alex | 2 |
| 3 | jenn | 4 |
| 4 | shama| 3 |
| 5 | jenn | 4 |
| 6 | Mike | 1 |
I want to find repetitive name and change name value and sum repetitive value.
Like
| ID | NAME |POINT|
| 1 | alexander| 4 |
| 2 | jennifer | 8 |
Is it possible mysql query?
Thanks.
try this
select id,
case when name = 'alex' then replace(NAME,'alex','alexander')
when name = 'jenn' then replace(NAME,'jenn','jennifer')
end as d,sum(point)
from name group by d having d is not null;
Is this what you want?
select (#rn := #rn + 1) as id, name, sum(point) as point
from t cross join
(select #rn := 0) params
group by name
having count(*) >= 2;

Counting the number of times something appears in a column and recording it

So in order to know how many people in a table are called Johnny I would need to excecute the following query.
Query:
Select count(*) from mytable where first = 'Johnny';
It would give me 2 as the result.
What I wish to do however is record this number in the count column so that the end result comes out like this.
+--------+----------+
| First | COUNT |
+--------+----------+
| Johnny | 2 |
| Diane | 1 |
| Johnny | 2 |
| Harold | 1 |
| Amy | 3 |
| Roy | 2 |
| Amy | 3 |
| Amy | 3 |
| Roy | 2 |
+--------+----------+
Is there any query or procedure capable of resulting in this type of output?
To get your exact output, you need to use a subquery:
select
mytable.First,
counts.`COUNT`
from
mytable
join (
select
First,
count(*) `COUNT`
from
mytable
group by
First
) counts on mytable.First = counts.First;
Try this:
SELECT T1.First, T2.COUNT
FROM mytable T1 JOIN
(SELECT First, COUNT(*) as COUNT
FROM mytable
GROUP BY First) as T2 ON T1.First=T2.First
The result will be:
+--------+----------+
| First | COUNT |
+--------+----------+
| Johnny | 2 |
| Diane | 1 |
| Johnny | 2 |
| Harold | 1 |
| Amy | 3 |
| Roy | 2 |
| Amy | 3 |
| Amy | 3 |
| Roy | 2 |
+--------+----------+

Create a column that counts the number of times a value shows up on another column, from the same table - MySQL

In MySQL:
Lets say I've this table:
id | name | count |
1 | John | |
2 | John | |
3 | John | |
4 | Mary | |
5 | Lewis| |
6 | Lewis| |
7 | Max | |
8 | Max | |
The names are already grouped, so the same name comes up together.
Now I want the table to be like this:
id | name | count |
1 | John | 1 |
2 | John | 2 |
3 | John | 3 |
4 | Mary | 1 |
5 | Lewis| 1 |
6 | Lewis| 2 |
7 | Max | 1 |
8 | Max | 2 |
Notice it auto increments the value of count everytime there is a repetition of the same name.
Thanks!
You can use a user variable.
Something like this:-
UPDATE somepeople a
INNER JOIN (
SELECT id, name, IF(#PrevName=name, #aCnt := #aCnt + 1, #aCnt := 1) AS sequence, #PrevName:=name
FROM somepeople,
(SELECT #aCnt:=1, #PrevName:='') Sub1
ORDER BY name, id) b
ON a.id = b.id
SET a.count = b.sequence

Top 'n' results for each keyword

I have a query to get the top 'n' users who commented on a specific keyword,
SELECT `user` , COUNT( * ) AS magnitude
FROM `results`
WHERE `keyword` = "economy"
GROUP BY `user`
ORDER BY magnitude DESC
LIMIT 5
I have approx 6000 keywords, and would like to run this query to get me the top 'n' users for each and every keyword we have data for. Assistance appreciated.
Since you haven't given the schema for results, I'll assume it's this or very similar (maybe extra columns):
create table results (
id int primary key,
user int,
foreign key (user) references <some_other_table>(id),
keyword varchar(<30>)
);
Step 1: aggregate by keyword/user as in your example query, but for all keywords:
create view user_keyword as (
select
keyword,
user,
count(*) as magnitude
from results
group by keyword, user
);
Step 2: rank each user within each keyword group (note the use of the subquery to rank the rows):
create view keyword_user_ranked as (
select
keyword,
user,
magnitude,
(select count(*)
from user_keyword
where l.keyword = keyword and magnitude >= l.magnitude
) as rank
from
user_keyword l
);
Step 3: select only the rows where the rank is less than some number:
select *
from keyword_user_ranked
where rank <= 3;
Example:
Base data used:
mysql> select * from results;
+----+------+---------+
| id | user | keyword |
+----+------+---------+
| 1 | 1 | mysql |
| 2 | 1 | mysql |
| 3 | 2 | mysql |
| 4 | 1 | query |
| 5 | 2 | query |
| 6 | 2 | query |
| 7 | 2 | query |
| 8 | 1 | table |
| 9 | 2 | table |
| 10 | 1 | table |
| 11 | 3 | table |
| 12 | 3 | mysql |
| 13 | 3 | query |
| 14 | 2 | mysql |
| 15 | 1 | mysql |
| 16 | 1 | mysql |
| 17 | 3 | query |
| 18 | 4 | mysql |
| 19 | 4 | mysql |
| 20 | 5 | mysql |
+----+------+---------+
Grouped by keyword and user:
mysql> select * from user_keyword order by keyword, magnitude desc;
+---------+------+-----------+
| keyword | user | magnitude |
+---------+------+-----------+
| mysql | 1 | 4 |
| mysql | 2 | 2 |
| mysql | 4 | 2 |
| mysql | 3 | 1 |
| mysql | 5 | 1 |
| query | 2 | 3 |
| query | 3 | 2 |
| query | 1 | 1 |
| table | 1 | 2 |
| table | 2 | 1 |
| table | 3 | 1 |
+---------+------+-----------+
Users ranked within keywords:
mysql> select * from keyword_user_ranked order by keyword, rank asc;
+---------+------+-----------+------+
| keyword | user | magnitude | rank |
+---------+------+-----------+------+
| mysql | 1 | 4 | 1 |
| mysql | 2 | 2 | 3 |
| mysql | 4 | 2 | 3 |
| mysql | 3 | 1 | 5 |
| mysql | 5 | 1 | 5 |
| query | 2 | 3 | 1 |
| query | 3 | 2 | 2 |
| query | 1 | 1 | 3 |
| table | 1 | 2 | 1 |
| table | 3 | 1 | 3 |
| table | 2 | 1 | 3 |
+---------+------+-----------+------+
Only top 2 from each keyword:
mysql> select * from keyword_user_ranked where rank <= 2 order by keyword, rank asc;
+---------+------+-----------+------+
| keyword | user | magnitude | rank |
+---------+------+-----------+------+
| mysql | 1 | 4 | 1 |
| query | 2 | 3 | 1 |
| query | 3 | 2 | 2 |
| table | 1 | 2 | 1 |
+---------+------+-----------+------+
Note that when there are ties -- see users 2 and 4 for keyword "mysql" in the examples -- all parties in the tie get the "last" rank, i.e. if the 2nd and 3rd are tied, both are assigned rank 3.
Performance: adding an index to the keyword and user columns will help. I have a table being queried in a similar way with 4000 and 1300 distinct values for the two columns (in a 600000-row table). You can add the index like this:
alter table results add index keyword_user (keyword, user);
In my case, query time dropped from about 6 seconds to about 2 seconds.
You can use a pattern like this (from Within-group quotas (Top N per group)):
SELECT tmp.ID, tmp.entrydate
FROM (
SELECT
ID, entrydate,
IF( #prev <> ID, #rownum := 1, #rownum := #rownum+1 ) AS rank,
#prev := ID
FROM test t
JOIN (SELECT #rownum := NULL, #prev := 0) AS r
ORDER BY t.ID
) AS tmp
WHERE tmp.rank <= 2
ORDER BY ID, entrydate;
+------+------------+
| ID | entrydate |
+------+------------+
| 1 | 2007-05-01 |
| 1 | 2007-05-02 |
| 2 | 2007-06-03 |
| 2 | 2007-06-04 |
| 3 | 2007-07-01 |
| 3 | 2007-07-02 |
+------+------------+