Mysql derived table - mysql

Suppose i have a mysql table name table with fields
rank
date
id
The values are like:
10, 2012-01-01, 3
9, 2012-01-04, 3
5, 2012-01-07, 3
3, 2012-01-10, 3
10, 2012-01-01, 4
6, 2012-01-04, 4
7, 2012-01-07, 4
In a single sql, how can i get both last and first values sorted by date grouped by id?
I know how to get first one or last one
SELECT rank, id FROM
(SELECT rank, id FROM table ORDER BY date DESC) s GROUP BY id;
I would like that the fields returned to be somethink like: lastrank, firstrank and id.
Thank you

Try this:
select id,
max(if(MyOrder = 1, rank, null)) as FirstRank,
max(if(MyOrder = 2, rank, null)) as LastRank
from (
select t1.id, t1.rank, 1 MyOrder from t t1
left join t t2 on
t1.id = t2.id and t1.date > t2.date
where t2.date is null
union
select t1.id, t1.rank, 2 from t t1
left join t t2 on
t1.id = t2.id and t1.date < t2.date
where t2.date is null
) s
group by id
The result of this query taking your sampla data as input is:
+----+-----------+----------+
| ID | FIRSTRANK | LASTRANK |
+----+-----------+----------+
| 3 | 10 | 3 |
| 4 | 10 | 7 |
+----+-----------+----------+

I'm not fully sure that I understand your question, but I'm going to try to answer anyway.
SELECT min(rank), max(rank), id
FROM table
ORDER BY date DESC
GROUP BY id;
When grouping, you can use aggregate functions on the results to get specific samples from the groups.

Try this query -
SELECT
t1.*,
IF(t1.date = t2.min_date, 'FIRSTRANK', 'LASTRANK') rank_type
FROM table_rank t1
JOIN (
SELECT id, MAX(date) max_date, MIN(date) min_date FROM table_rank GROUP BY id
) t2
ON t1.id = t2.id AND (t1.date = t2.min_date OR t1.date = t2.max_date)

Involve GROUP_CONCAT(rank ORDER BY date), and use SUBSTRING_INDEX.

Related

Find rows with consecutive dates

Is it possible to know which customer came at the bar 3 consecutive days? (john in my case)
Thanks in advance
Name
Age
Date
Amount
Paul
12
2021-12-01
20
John
19
2021-12-01
10
Jack
17
2021-13-01
7
John
19
2021-13-01
8
John
19
2021-14-01
17
so I would approach this by
SELECT FROM Table
LEFT JOIN Table As PrevDay on PrevDay.Customer = Table.Customer
AND PrevDay.date = DATEADD(DAY,-1,Table.Date)
LEFT JOIN Table AS NextDay on NextDay,Customer = Table.Customer
AND NextDay.Date = DATEADD(DATE,1,Table.Date)
WHERE PrevDay.Customer is not NULL
AND NextDay.Customer is not null
Assuming that the data type of the column Date is DATE you can use a self join and aggregation:
SELECT DISTINCT t1.Name
FROM tablename t1 INNER JOIN tablename t2
ON t2.Name = t1.Name AND ABS(DATEDIFF(t1.Date, t2.Date)) = 1
GROUP BY t1.Name, t1.Date
HAVING COUNT(DISTINCT t2.Date) = 2;
Or, a more scalable solution:
SELECT DISTINCT t1.Name
FROM tablename t1 INNER JOIN tablename t2
ON t2.Name = t1.Name AND t2.Date BETWEEN t1.Date AND t1.Date + INTERVAL 2 DAY
GROUP BY t1.Name, t1.Date
HAVING COUNT(DISTINCT t2.Date) = 3;
You can remove DISTINCT from COUNT(DISTINCT t2.Date) if the combination of Name and Date is unique in the table.
See the demo.

Incrementing count ONLY for duplicates in MySQL

Here is my MySQL table. I updated the question by adding an 'id' column to it (as instructed in the comments by others).
id data_id
1 2355
2 2031
3 1232
4 9867
5 2355
6 4562
7 1232
8 2355
I want to add a new column called row_num to assign an incrementing number ONLY for duplicates, as shown below. Order of the results does not matter.
id data_id row_num
3 1232 1
7 1232 2
2 2031 null
1 2355 1
5 2355 2
8 2355 3
6 4562 null
4 9867 null
I followed this answer and came up with the code below. But following code adds a count of '1' to non-duplicate values too, how can I modify below code to add a count only for duplicates?
select data_id,row_num
from (
select data_id,
#row:=if(#prev=data_id,#row,0) + 1 as row_num,
#prev:=data_id
from my_table
)t
If you are running MySQL 8.0, you can do this more efficiently with window functions only:
select
data_id,
case when count(*) over(partition by data_id) > 1
then row_number() over(partition by data_id order by data_id) row_num
end
from mytable
When the window count returns more than 1, you know that the current data_id has duplicates, in which case you can use row_number() to assign the incrementing number.
Note that, in absence of an ordering columns to uniquely identify each record within groups sharing the same data_id, it is undefined which record will actually get each number.
I am assuming that id is the column that defines the order on the rows.
In MySQL 8 you can use row_number() to get the number of each data_id and a CASE with EXISTS to exclude the rows which have no duplicate.
SELECT t1.data_id,
CASE
WHEN EXISTS (SELECT *
FROM my_table t2
WHERE t2.data_id = t1.data_id
AND t2.id <> t1.id) THEN
row_number() OVER (PARTITION BY t1.data_id
ORDER BY t1.id)
END row_num
FROM my_table t1;
In older versions you can use a subquery counting the rows with the same data_id but smaller id. With an EXISTS in a HAVING clause you can exclude the rows that have no duplicate.
SELECT t1.data_id,
(SELECT count(*)
FROM my_table t2
WHERE t2.data_id = t1.data_id
AND t2.id < t1.id
HAVING EXISTS (SELECT *
FROM my_table t2
WHERE t2.data_id = t1.data_id
AND t2.id <> t1.id)) + 1 row_num
FROM my_table t1;
db<>fiddle
Join with a query that returns the number of duplicates.
select t1.data_id, IF(t2.dups > 1, row_num, '') AS row_num
from (
select data_id,
#row:=if(#prev=data_id,#row,0) + 1 as row_num,
#prev:=data_id
from my_table
order by data_id
) AS t1
join (
select data_id, COUNT(*) AS dups
FROM my_table
GROUP BY data_id
) AS t2 ON t1.data_id = t2.data_id
If you want to have the old "order" of the old table, you need much more code
SELECT
data_id, IF (row_num = 1 AND cntid = 1, NULL,row_num)
FROM
(SELECT
#row:=IF(#prev = t1.data_id, #row, 0) + 1 AS row_num,
cntid,
#prev:=t1.data_id data_id
FROM
(SELECT
*
FROM
my_table
ORDER BY data_id) t1
INNER JOIN (SELECT Count(*) cntid,data_id FROM my_table GROUP BY data_id)t2
ON t1.data_id = t2.data_id) t2
data_id | IF (row_num = 1 AND cntid = 1, NULL,row_num)
------: | -------------------------------------------:
1232 | 1
1232 | 2
2031 | null
2355 | 1
2355 | 2
2355 | 3
4562 | null
9867 | null
db<>fiddle here

SQL select rows with number in sequence

I have 1:N table where every entity may have asigned multiple numbers.
ID Number
1 10
1 13
1 11
1 12
1 16
2 11
2 12
2 13
2 10
Now,I want all IDs which have for example 3 numbers in ascending sequence. I do not specify which numbers I want, I just want the SQL to return me all possible combinations it can find but the numbers has to be in ascending sequence and the sequence must contain exactly 3 numbers. The numbers are allways integers of any value. The numbers in result have to be next to each other (12,13,16)is not valid result.
For 3 numbers in this example it would be :
ID 1 : (10,11,12),(11,12,13)
ID 2 : (11,12,13),(10,11,13)
For 2 numbers in this example it would be:
ID 1 : (10,11),(11,12),(12,13)
ID 2 : (11,12)(12,13)
Is this possible in SQL select? Thanx
A solution whats comes close to your expected output.
Involves using self inner joins incombination with CONCAT_WS, GROUP_CONCAT..
For group of three you use this query
Query
SET SESSION group_concat_max_len = ##max_allowed_packet
SELECT
records.ID
, GROUP_CONCAT(CONCAT('(', records.number, ')'))
FROM (
SELECT
DISTINCT
table11.ID
, CONCAT_WS(
','
, table11.Number
, table12.Number
, table13.Number
) AS number
FROM
Table1 AS table11
INNER JOIN
Table1 AS table12
ON
table11.Number + 1 = table12.Number
INNER JOIN
Table1 table13
ON
table12.Number + 1 = table13.Number
ORDER BY
table11.ID ASC
, table11.Number ASC
) AS records
GROUP BY
records.ID
Result
| ID | GROUP_CONCAT(CONCAT('(', records.number, ')')) |
|----|------------------------------------------------|
| 1 | (11,12,13),(10,11,12) |
| 2 | (11,12,13),(10,11,12) |
see demo http://sqlfiddle.com/#!9/c5dfce/39
Simply use join. This produces a result set with each examples of sequential numbers on a different row:
select id, t1.number, t2.number, t3.number
from t t1 join
t t2
on t2.id = t1.id and t2.number = t1.number + 1 join
t t3
on t3.id = t2.id and t3.number = t2.number + 1;
If you really wanted a list, you would simply do:
select id,
group_concat('(', t1.number, ',', t2.number, ',', t3.number, ')') as groups
from t t1 join
t t2
on t2.id = t1.id and t2.number = t1.number + 1 join
t t3
on t3.id = t2.id and t3.number = t2.number + 1
group by t1.id;

SQL query to join two tables with no repeated values?

Table 1
ID | NAME | WARD_ID|
1 A 1
2 B 1
3 C 2
4 D 2
Table 2
ID | MONTH1 | MONTH2 | WARD_ID|
1 9 10 1
2 6 11 1
3 5 12 2
4 13 14 2
I want to join this two table and produce the following output:
ID | NAME | MONTH1 | MONTH2 | WARD_ID|
1 A 9 10 1
2 B 6 11 1
3 C 5 12 2
4 D 13 14 2
In the ON condition of the query I have to keep WARD_ID equal for both the tables. I could not able to figure out the solution. Anyone have any experience with a query like this?
I think you want something like this:
select t1.*, t2.*
from (select t1.*,
(#rn1 := if(#w1 = ward_id, #rn1 + 1,
if#w1 := ward_id, 1, 1)
)
) as rn
from (select t1.* from table1 t1 order by ward_id, id ) t1 cross join
(select #w1 := -1, #rn1 := -1) params
) t1 join
(select t2.*,
(#rn2 := if(#w2 = ward_id, #rn2 + 1,
if#w2 := ward_id, 1, 1)
)
) as rn
from (select t2.* from table2 t2 order by ward_id, id ) t2 cross join
(select #w2 := -1, #rn1 := -1) params
) t1
on t2.ward_id = t1.ward_id and t2.rn = t1.rn;
The subqueries enumerate the rows in each table. The join then uses the enumeration.
This is much simpler in MySQL 8.0, using row_number().
I'm assuming here that ID is intended to be the same from both tables. If so, I think you can do a multi-condition join:
select * from table1 t1
inner join table2 t2
on t1.ID=t2.ID and t1.WARD_ID=t2.WARD_ID
You can do something like:
SET #rn:=0;
SET #rn2:=0;
SELECT *
FROM (
SELECT #rn:=#rn+1 AS rn1, t1.ID, t1.NAME, t1.WARD_ID
FROM t1
GROUP BY t1.WARD_ID, t1.NAME
ORDER BY t1.WARD_ID, t1.NAME
) s1
INNER JOIN (
SELECT #rn2:=#rn2+1 AS rn2, t2.ID, t2.MONTH1, t2.MONTH2, t2.WARD_ID
FROM t2
GROUP BY t2.WARD_ID, t2.MONTH1,t2.MONTH2
ORDER BY t2.WARD_ID, t2.MONTH1,t2.MONTH2
) s2 ON s1.WARD_ID = s2.WARD_ID
AND s1.rn1 = s2.rn2
But it really doesn't reliably sort the tables to join the same rows every time. I still think there isn't a reliable/repeatable way to join the two tables the same every time.
============================================================
http://sqlfiddle.com/#!9/aa2db0/1 <<<< If ID can be used to reliably sort the two tables, you can use it in the ORDER BYs. I've added it in this Fiddle, and included rows in the setup that would fall before the existing records and potentially change the sorting. This also includes more records in Table 2 than there are in Table 1, so would possibly result in duplicated rows. These new rows are ignored since they can't be matched between tables.

same column calculation / percentage

How does someone in MYSQL compare a users percentage from a dates entry and score to another dates entry and score, effectively returning a users percentage increase from one date to another?
I have been trying to wrap my head around this question for a few days and am running out of ideas and feel my sql knowledge is limited. Not sure if I'm supposed to use a join or a subquery? The MYSQL tables consist of 3 fields, name, score, and date.
TABLE: userData
name score date
joe 5 2014-01-01
bob 10 2014-01-01
joe 15 2014-01-08
bob 12 2014-01-08
returned query idea
user %inc last date
joe 33% 2014-01-08
bob 17% 2014-01-08
It seems like such a simple function a database would serve yet trying to understand this is out of my grasp?
You need to use SUBQUERIES. Something like:
SELECT name,
((SELECT score
FROM userData as u2
WHERE u2.name = u1.name
ORDER BY date desc
LIMIT 1
)
/
(
SELECT score
FROM userData as u3
WHERE u3.name = u1.name
ORDER BY date desc
LIMIT 1,1
)
* 100.0
) as inc_perc,
max(date) as last_date
FROM userData as u1
GROUP BY name
Simple solution assuming that the formula for %Inc column = total/sum *100
select name,total/sum * 100, date from (
select name,sum(score) as total,count(*) as num,date from table group by name
)as resultTable
select a.name as [user],(cast(cast(b.score as float)-a.score as float)/cast(a.score as float))*100 as '% Inc',b.[date] as lastdate
from userdata a inner join userdata b on a.name = b.name and a.date < b.date
I guess you are looking for the % increse in the score compared to past date
Another way (and note, that I have another result. Based on the name "percinc", percentage increase, I calculated it in my eyes correctly. If you want your result, just calculate it with t1.score / t2.score * 100):
Sample data:
CREATE TABLE t
(`name` varchar(3), `score` int, `date` varchar(10))
;
INSERT INTO t
(`name`, `score`, `date`)
VALUES
('joe', 5, '2014-01-01'),
('bob', 10, '2014-01-01'),
('joe', 15, '2014-01-08'),
('bob', 12, '2014-01-08')
;
Query:
select
t1.name,
t1.score first_score,
t1.date first_date,
t2.score last_score,
t2.date last_date,
t2.score / t1.score * 100 percinc
from
t t1
join t t2 on t1.name = t2.name
where
t1.date = (select min(date) from t where t.name = t1.name)
and t2.date = (select max(date) from t where t.name = t1.name);
Result:
| NAME | FIRST_SCORE | FIRST_DATE | LAST_SCORE | LAST_DATE | PERCINC |
|------|-------------|------------|------------|------------|---------|
| joe | 5 | 2014-01-01 | 15 | 2014-01-08 | 300 |
| bob | 10 | 2014-01-01 | 12 | 2014-01-08 | 120 |
live demo