What I want to do :
I have a table like this :
TABLE mytable
- ID (INT)
- START (DATETIME)
- END (DATETIME)
Let's say I have these rows :
| ID | START | END |
|--------------------------------------------------
| 1 | 2014-01-02 00:00:00 | 2014-12-02 00:00:00 | => month between : 12
| 2 | 2014-01-03 00:00:00 | 2015-02-03 00:00:00 | => month between : 14
Note : the "month between" include the start and end months
I for each YEAR_MONTH between START and END, I want to display a row like this :
ID | MONTH | YEAR
---------------------
1 | 1 | 2014
1 | 2 | 2014
1 | 3 | 2014
1 | 4 | 2014
1 | 5 | 2014
1 | 6 | 2014
1 | 7 | 2014
1 | 8 | 2014
1 | 9 | 2014
1 | 10 | 2014
1 | 11 | 2014
1 | 12 | 2014
2 | 1 | 2014
2 | 2 | 2014
2 | 3 | 2014
2 | 4 | 2014
2 | 5 | 2014
2 | 6 | 2014
2 | 7 | 2014
2 | 8 | 2014
2 | 9 | 2014
2 | 10 | 2014
2 | 11 | 2014
2 | 12 | 2014
2 | 1 | 2015
2 | 2 | 2015
So 12 records for ID 1 and 14 for ID 2.
I'm a bit stuck when the number of month is > 12
WHERE I AM :
I'm doing this :
SELECT mytable.id,
months.id as month,
YEAR(start) as year
FROM mytable
/* Join on a list from 1 to 12 */
LEFT JOIN (SELECT 1 as id UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12)
as months ON months.id BETWEEN MONTH(start) AND MONTH(end)
order by mytable.id, month, year
So ID 2 only has 2 rows for month 1 and 2 :
ID | MONTH | YEAR
---------------------
1 | 1 | 2014
1 | 2 | 2014
1 | 3 | 2014
1 | 4 | 2014
1 | 5 | 2014
1 | 6 | 2014
1 | 7 | 2014
1 | 8 | 2014
1 | 9 | 2014
1 | 10 | 2014
1 | 11 | 2014
1 | 12 | 2014
2 | 1 | 2014
2 | 2 | 2014
Do you have any ideas or advices for this problem ?
Is there a way to extract every YEAR_MONTH between two dates ?
Thank you.
HELPER :
Here is a script to create the table and insert the 2 rows mentionned :
CREATE TABLE mytable (
id INT PRIMARY KEY auto_increment,
start DATETIME NOT NULL,
end DATETIME NOT NULL
);
INSERT INTO mytable (start,end) VALUES
("2014-01-02 00:00:00","2014-12-02 00:00:00"),
("2014-01-03 00:00:00","2015-02-03 00:00:00");
If I understand you correctly, you need a table with dates (year - month) between each start and end date.
There's no simple select statement that will give you this, but you can create a procedure to do it. You need to create a temporary table, fill it with the values you need and then output the result.
Here's my proposed solution (considering a permanent table):
SQL Fiddle
MySQL 5.5.32 Schema Setup:
CREATE TABLE mytable (
id INT PRIMARY KEY auto_increment,
start DATETIME NOT NULL,
end DATETIME NOT NULL
)//
INSERT INTO mytable (start,end) VALUES
("2014-01-02 00:00:00","2014-12-02 00:00:00"),
("2014-01-03 00:00:00","2015-02-03 00:00:00")//
create procedure year_month_table()
begin
-- Declare the variables to fill the years_months table
declare id int;
declare start_date, end_date, d date;
-- Declare the "done" variable for the loop that fills the table,
-- the cursor to read the data, and the handler to check if the
-- loop should end.
declare done int default false;
declare cur_mytable cursor for
select * from mytable;
declare continue handler for not found
set done = true;
-- Create the table to hold your data
create table if not exists years_months (
row_id int unsigned not null auto_increment primary key,
id int not null,
month int,
year int,
unique index dedup(id, year, month),
index idx_id(id),
index idx_year(year),
index idx_month(month)
);
-- Open the cursor to read the ids and the start and end dates for each one
open cur_mytable;
-- Disable the indexes to speed up insertion
alter table years_months disable keys;
-- Start the loop
loop_data: loop
-- Read the values from your table and store them in the variables
fetch cur_mytable into id, start_date, end_date;
-- If you've reached the end of the table, then you must exit the loop
if done then
leave loop_data;
end if;
-- Initialize the date to fill the table
set d = start_date;
while d <= end_date do
-- Insert the values in your table
insert ignore into years_months (id, month, year) values (id, month(d), year(d));
-- Increment the d variable in 1 month
set d = date_add(d, interval +1 month);
end while;
end loop;
close cur_mytable;
-- Enable the indexes again
alter table years_months enable keys;
-- Show the result
select * from years_months;
end //
Query 1:
select * from mytable
Results:
| ID | START | END |
|----|--------------------------------|---------------------------------|
| 1 | January, 02 2014 00:00:00+0000 | December, 02 2014 00:00:00+0000 |
| 2 | January, 03 2014 00:00:00+0000 | February, 03 2015 00:00:00+0000 |
Query 2:
call year_month_table()
Results:
| ROW_ID | ID | MONTH | YEAR |
|--------|----|-------|------|
| 1 | 1 | 1 | 2014 |
| 2 | 1 | 2 | 2014 |
| 3 | 1 | 3 | 2014 |
| 4 | 1 | 4 | 2014 |
| 5 | 1 | 5 | 2014 |
| 6 | 1 | 6 | 2014 |
| 7 | 1 | 7 | 2014 |
| 8 | 1 | 8 | 2014 |
| 9 | 1 | 9 | 2014 |
| 10 | 1 | 10 | 2014 |
| 11 | 1 | 11 | 2014 |
| 12 | 1 | 12 | 2014 |
| 13 | 2 | 1 | 2014 |
| 14 | 2 | 2 | 2014 |
| 15 | 2 | 3 | 2014 |
| 16 | 2 | 4 | 2014 |
| 17 | 2 | 5 | 2014 |
| 18 | 2 | 6 | 2014 |
| 19 | 2 | 7 | 2014 |
| 20 | 2 | 8 | 2014 |
| 21 | 2 | 9 | 2014 |
| 22 | 2 | 10 | 2014 |
| 23 | 2 | 11 | 2014 |
| 24 | 2 | 12 | 2014 |
| 25 | 2 | 1 | 2015 |
| 26 | 2 | 2 | 2015 |
Notice that that last select statement in the procedure is the one that outputs the result. You can execute it every time you need.
Hope this helps
Important: As pointed by #amaster in his comment, this answer will fail if the period spans more than two years.
(Use the following code under your own risk ;) )
I've found another way to do this, but it's not a simple select statement and I think it's prone to errors, but I will put it here anyway:
select mytable.id, month, year
from mytable,
(select month, year
from
(select 1 as month
union select 2
union select 3
union select 4
union select 5
union select 6
union select 7
union select 8
union select 9
union select 10
union select 11
union select 12) as a,
(select year(start) as year from mytable
union select year(end) as year from mytable) as b) as a
where cast(concat_ws('-', a.year, a.month, day(mytable.start)) as date)
between date(mytable.start) and date(mytable.end)
order by mytable.id, year, month;
See this other SQL fiddle.
I know I am late to the party, but I was needing a good solution and sequencing was not working for my db version.
I started with https://stackoverflow.com/a/14813173/1707323 and made a few changes to get it working for my use like in this OP.
SELECT
DATE_FORMAT(m1, '%c') AS month_single,
DATE_FORMAT(m1, '%Y') AS this_year
FROM
(
SELECT
'2017-08-15' +INTERVAL m MONTH AS m1
FROM
(
SELECT
#rownum:=#rownum+1 AS m
from
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t2,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t3,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t4,
(SELECT #rownum:=-1) t0
) d1
) d2
WHERE
m1<='2020-03-23'
ORDER BY m1
This will get all of the months between these two dates. Please notice that the start date is in the second select clause and the end date is in the final where clause. This will include the starting month and ending month as well. It could be easily modified to exclude the starting and ending months with some extra +/- INTERVALS.
Related
Sample table tbl_name:
| ID | Name | Month | Quarter | Year |
| 1 | A | Jan | 1 | 2019 |
| 1 | A | Feb | 1 | 2019 |
| 2 | B | May | 2 | 2019 |
| 3 | C | May | 2 | 2018 |
Hi, this is the table I extract using SELECT query. I can find the distinct name per year using SELECT distinct name, year FROM tbl_name; But I'm trying to add a column during SELECT query to identify or count the unique occurrence per year of the name.
Expected:
| ID | Name | Month | Quarter | Year | Unique Count |
| 1 | A | Jan | 1 | 2019 | 1 |
| 1 | A | Feb | 1 | 2019 | 0 |
| 2 | B | May | 2 | 2019 | 1 |
| 3 | C | May | 2 | 2018 | 1 |
I tried splitting into two queries - one select everything; the other select just distinct and join them together but that will introduce duplicates. Is there a way to do this using SQL?
If you are running MySQL 8.0, you can use row_number() to flag the appearance of a name in a year:
select
t.*,
(row_number() over(
partition by name, year
order by str_to_date(concat(year, '-', month), '%Y-%b')
) = 1) unique_count
from mytable t
Note: do consider fixing the storage strategy of your date columns. Rather than splitting the information over several columns, you would better have a unique column in the relevant DATE datatype to store that information. That would save you the pain of recomposing the date when you need it.
Demo on DB Fiddle:
ID | Name | Month | Quarter | Year | unique_count
-: | :--- | :---- | ------: | ---: | -----------:
1 | A | Feb | 1 | 2019 | 1
1 | A | Jan | 1 | 2019 | 0
2 | B | May | 2 | 2019 | 1
3 | C | May | 2 | 2018 | 1
You can try this below logic-
DEMO HERE
WITH your_table(ID,Name,Month,Quarter,Year)
AS
(
SELECT 1,'A','Jan',1,2019 UNION ALL
SELECT 1,'A','Feb',1,2019 UNION ALL
SELECT 2,'B','May',2,2019 UNION ALL
SELECT 3,'C','May',2,2018
)
,CTE AS
(
SELECT *,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Quarter,Year) RN
FROM your_table
)
SELECT ID,Name,Month,Quarter,Year,
CASE WHEN RN = 1 THEN 1 ELSE 0 END Unique_Count
FROM CTE
Output is-
ID Name Month Quarter Year Unique_Count
1 A Jan 1 2019 1
1 A Feb 1 2019 0
2 B May 2 2019 1
3 C May 2 2018 1
I have two tables named table1 and table2 . I've tried to sum some column but result is showing wrong
I've tried the following mysql query:
SELECT t1.year
, SUM(t1.deposit) TOTALDEPOSIT
, SUM(t1.interest) TOTALINTEREST
, SUM(t1.otherinterest) TOTALOTHER
FROM table1 t1
LEFT
JOIN table2 t2
ON t1.year = t2.year
GROUP
BY t1.year
But result of SUM is not showing accurately
My tables are below
table1
| table1id| year| deposit| interest|
|---------|-----|--------|---------|
| 1|2019 | 20 | 1 |
| 2|2019 | 20 | 2 |
| 3|2019 | 20 | 1 |
| 3|2019 | 20 | 2 |
| 3|2020 | 20 | 3 |
| 3|2020 | 20 | 4 |
table2
| table2id| year | otherinterest|
|----------------|--------------|
| 1 | 2019 | 10 |
| 2 | 2019 | 10 |
The expected result is
| YEAR | TOTALDEPOSIT| TOTALINTEREST |TOTALOTHER |
|--------------------|----------------|-----------|
| 2019 | 120 | 6 | 20 |
| 2020 | 40 | 7 | |
But My query giving result
| YEAR | TOTALDEPOSIT| TOTALINTEREST |TOTALOTHER |
|--------------------|----------------|-----------|
| 2019 | 160 | 12 | 80 |
| 2020 | 40 | 7 | |
So could you please anyone help me to solve this query?
Your query doesn’t work correctly because the intermediate result is probably not the same as you expected.
Let’s try this query:
SELECT *
FROM table1 t1
LEFT JOIN table2 t2
ON t1.year = t2.year
Result will be:
+----------+------+---------+----------+----------+------+---------------+
| table1id | year | deposit | interest | table2id | year | otherinterest |
+----------+------+---------+----------+----------+------+---------------+
| 1 | 2019 | 20 | 1 | 1 | 2019 | 10 |
| 2 | 2019 | 20 | 2 | 1 | 2019 | 10 |
| 3 | 2019 | 20 | 1 | 1 | 2019 | 10 |
| 3 | 2019 | 20 | 2 | 1 | 2019 | 10 |
| 1 | 2019 | 20 | 1 | 2 | 2019 | 10 |
| 2 | 2019 | 20 | 2 | 2 | 2019 | 10 |
| 3 | 2019 | 20 | 1 | 2 | 2019 | 10 |
| 3 | 2019 | 20 | 2 | 2 | 2019 | 10 |
| 3 | 2020 | 20 | 3 | NULL | NULL | NULL |
| 3 | 2020 | 20 | 4 | NULL | NULL | NULL |
+----------+------+---------+----------+----------+------+---------------+
So we have 10 rows, not 6. You can see that for example sum of deposits for year 2019 is 160. Same number as in your "wrong" result.
This is because for each record in table1 where year is 2019 joining condition (t1.year = t2.year) is twice true.
In other words for this rows from table1 where year equals 2019 we have two rows in result table - one with table2id=1 and antoher with table2id=2.
A sub query is a bit less wordy than a join.
drop table if exists t,t1;
create table t
(table1id int, year int, deposit int, interest int);
insert into t values
( 1,2019 , 20 , 1),
( 2,2019 , 20 , 2),
( 3,2019 , 20 , 1),
( 3,2019 , 20 , 2),
( 3,2020 , 20 , 3),
( 3,2020 , 20 , 4);
create table t1
( table2id int, year int, otherinterest int);
insert into t1 values
( 1 , 2019 , 10 ),
( 2 , 2019 , 10 );
select t.year,sum(deposit),sum(interest),
(select sum(otherinterest) from t1 where t1.year = t.year) otherinterest
FROM t
group by t.year;
+------+--------------+---------------+---------------+
| year | sum(deposit) | sum(interest) | otherinterest |
+------+--------------+---------------+---------------+
| 2019 | 80 | 6 | 20 |
| 2020 | 40 | 7 | NULL |
+------+--------------+---------------+---------------+
2 rows in set (0.00 sec)
Just use a simple subquery and it will works.
SELECT A.year, SUM(A.deposit) TOTALDEPOSIT, SUM(A.interest) TOTALINTEREST,
(SELECT SUM(B.otherinterest) FROM table2 B WHERE B.year= A.year) TOTALOTHER
FROM table1 A
GROUP BY A.year
You should join the aggreated result from each table eg:
select t1.year, tt1.totaldeposit, tt1.totalinterest, tt2.otherinterest
from table1 t1
inner join (
select year, sum(deposit) totaldeposit, sum(interest) totalinterest
from table1
group by year
) tt1 On t1.year = tt1.year
left join (
select year, sum(otherinterest) otherinterest
from table1
group by year
) tt2 On t1.year = tt2.year
Simple GROUP BY with LEFT JOIN is what you need, but order of that operations should be different from what you have :)
select t1.year,
t1.deposit totaldeposit,
t1.interest totalinterest,
t2.otherinterest * t1.cnt totalother
from (
select year, sum(deposit) deposit, sum(interest) interest, count(*) cnt
from table1
group by year
) t1 left join (
select year, sum(otherinterest) otherinterest
from table2
group by year
) t2 on t1.year = t2.year
how would you write a recursive query that can bring back data grouped by months?
As an example, something like this...
Month Amount
Jan £1000
Feb £1500
March £2000
Currently I'm running 3 separate queries that calculates the sum of all transactions within a month and then groups them and then I union everything. This however doesn't factor in coming months or previous months. It's hardcoded per se.
What I'm doing is the following for every month.
WHERE processing_time >= 2019-02-01 00:00:00
AND processing_time <= 2019-02-28 23:59:59
I need something that can show me 12 months worth of data but that also changes dynamically.
So March 2018 to Feb 2018 if I checked today and April 2018 to March 2019 if I check next month.
You don't have to solve it with recursive queries. you can use a "calendar" table to fill in the missing values.
A MySQL/MariaDB number generator comes to mind for month name generations.
SELECT
MONTHNAME(CURRENT_DATE + INTERVAL number_generator.number MONTH)
FROM (
SELECT
#row := #row + 1 AS number
FROM (
SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) row1
CROSS JOIN (
SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) row2
CROSS JOIN (
SELECT #row := -1
) init_user_params
) AS number_generator
WHERE
number_generator.number BETWEEN 0 AND 12
ORDER BY
number_generator.number ASC
Result
| MONTHNAME(CURRENT_DATE + INTERVAL number_generator.number MONTH) |
| ---------------------------------------------------------------- |
| March |
| April |
| May |
| June |
| July |
| August |
| September |
| October |
| November |
| December |
| January |
| February |
| March |
see demo
Edit
This is very close but can I also get the start and end days of each
month?
The last day is not that hard because there is a function for it in MySQL.
For the first day you need to be more creative with LAST_DAY() in combination with intervals like +1 DAY and -1 MONTH to get the first day of the month.
SELECT
MONTHNAME(CURRENT_DATE + INTERVAL number_generator.number MONTH)
, ((LAST_DAY(CAST(CURRENT_DATE + INTERVAL number_generator.number MONTH AS DATE)))
+ INTERVAL 1 DAY) - INTERVAL 1 MONTH AS first_day_of_month
, (LAST_DAY(CAST(CURRENT_DATE + INTERVAL number_generator.number MONTH AS DATE))) AS last_day_of_month
Results
| MONTHNAME(CURRENT_DATE + INTERVAL number_generator.number MONTH) | first_day_of_month | last_day_of_month |
| ---------------------------------------------------------------- | ------------------ | ----------------- |
| March | 2019-03-01 | 2019-03-31 |
| April | 2019-04-01 | 2019-04-30 |
| May | 2019-05-01 | 2019-05-31 |
| June | 2019-06-01 | 2019-06-30 |
| July | 2019-07-01 | 2019-07-31 |
| August | 2019-08-01 | 2019-08-31 |
| September | 2019-09-01 | 2019-09-30 |
| October | 2019-10-01 | 2019-10-31 |
| November | 2019-11-01 | 2019-11-30 |
| December | 2019-12-01 | 2019-12-31 |
| January | 2020-01-01 | 2020-01-31 |
| February | 2020-02-01 | 2020-02-29 |
| March | 2020-03-01 | 2020-03-31 |
see demo
This question already has answers here:
MySQL how to fill missing dates in range?
(6 answers)
Closed 6 years ago.
I have a table like the below.
id month duration
001 1/1/16 3
002 3/1/16 4
003 12/1/15 2
I would like to add a new row to the table for every month after the month shown for the number specified minus 1, e.g. below:
id month duration
001 1/1/16 3
001 2/1/16 3
001 3/1/16 3
002 3/1/16 4
002 4/1/16 4
002 5/1/16 4
002 6/1/16 4
003 12/1/15 2
003 1/1/16 2
And so on, while duplicating the values in any column not shown.
I have done this in R, where I first populated 'short' data and then reshaped it to long, but after searching online, I still have no idea how to do this in mySQL. Thanks in advance for your help!
If you could automatically generate rows in MySQL that would be pretty easy; except that you can't. Still, you could use the technique described here to generate some rows and then use a JOIN to get the data you need:
-- Create a VIEW with 16 dummy rows
CREATE OR REPLACE VIEW generator_16
AS SELECT 0 n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL
SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL
SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL
SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL
SELECT 15;
Having this generator, your query becomes:
SELECT id, date_add(month, INTERVAL n MONTH), duration
FROM tbl
INNER JOIN generator_16 ON n < duration
ORDER BY id, month
I assumed your table structure is the following:
CREATE TABLE tbl(id varchar(10), month date, duration int);
INSERT INTO tbl VALUES('001', '2016-01-01', 3);
INSERT INTO tbl VALUES('002', '2016-03-01', 4);
INSERT INTO tbl VALUES('003', '2015-12-01', 2);
Issues of data display are generally best resolved in the presentation layer (e.g. a simple PHP loop), but just for fun...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,dt DATE NOT NULL
,duration INT NOT NULL
);
INSERT INTO my_table VALUES
(1,'2016-01-01',3),
(2,'2016-03-01',4),
(3,'2015-12-01',2);
SELECT * FROM my_table;
+----+------------+----------+
| id | dt | duration |
+----+------------+----------+
| 1 | 2016-01-01 | 3 |
| 2 | 2016-03-01 | 4 |
| 3 | 2015-12-01 | 2 |
+----+------------+----------+
SELECT * FROM ints;
+---+
| i |
+---+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+---+
SELECT x.id,x.dt + INTERVAL y.i MONTH,x.duration FROM my_table x JOIN ints y ON y.i < x.duration;
+----+---------------------------+----------+
| id | x.dt + INTERVAL y.i MONTH | duration |
+----+---------------------------+----------+
| 1 | 2016-01-01 | 3 |
| 1 | 2016-02-01 | 3 |
| 1 | 2016-03-01 | 3 |
| 2 | 2016-03-01 | 4 |
| 2 | 2016-04-01 | 4 |
| 2 | 2016-05-01 | 4 |
| 2 | 2016-06-01 | 4 |
| 3 | 2015-12-01 | 2 |
| 3 | 2016-01-01 | 2 |
+----+---------------------------+----------+
I have table as
P_Id | userid | year | month | day
-----+--------+------+-------+------
3 | 3 | 2011 | 2 | 2
5 | 1 | 2011 | 2 | 3
16 | 8 | 2011 | 3 | 4
5 | 3 | 2011 | 4 | 4
17 | 1 | 2011 | 4 | 6
8 | 4 | 2011 | 7 | 7
9 | 3 | 2011 | 8 | 8
10 | 8 | 2011 | 9 | 9
I want to select distinct column i.e userid but also the respective value of year month and year which were encountered first.
For given above table following should be output
P_Id | userid | year | month | day
-----+--------+------+-------+------
3 | 3 | 2011 | 2 | 2
5 | 1 | 2011 | 2 | 3
16 | 8 | 2011 | 3 | 4
8 | 4 | 2011 | 7 | 7
or
If i am ordering the table by year,month and day
userid which is encountered first must only be selected and rest must be not be selected
Put year, month and day to native date column and do this:
select p_id, userid, min(the_date) from table group by p_id, userid
It will provide the fastest result.
If you cant modify your table and should use year+month+day then you can convert this values to date and still use min function.
SELECT ta.*
FROM
( SELECT DISTINCT userid
FROM tableX
) AS di
JOIN
tableX AS ta
ON ta.P_id =
( SELECT ti.P_id
FROM tableX AS ti
WHERE ti.userid = di.userid
ORDER BY ti.year, ti.month, ti.day
LIMIT 1
)
Your query is as follows;
select * from (select min(p_id)p_id,userid, min(year)year,min(month)month,min(day)day from tsil group by userid) t order by p_id;
and here is the test;
create table tsil(p_id int, userid int, year int, month int, day int);
insert into tsil values (3,3,2011,2,2)
,(5,1,2011,2,3)
,(16,8,2011,3,4)
,(5,3,2011,4,4)
,(17,1,2011,4,6)
,(8,4,2011,7,7)
,(9,3,2011,8,8)
,(10,8,2011,9,9);
commit;
select * from (select max(p_id)p_id,userid, min(year)year,min(month)month,min(day)day from tsil group by userid) t order by p_id;
drop table tsil;
and here is the result; what you expected.
+------+--------+------+-------+------+
| p_id | userid | year | month | day |
+------+--------+------+-------+------+
| 3 | 3 | 2011 | 2 | 2 |
| 5 | 1 | 2011 | 2 | 3 |
| 8 | 4 | 2011 | 7 | 7 |
| 16 | 8 | 2011 | 3 | 4 |
+------+--------+------+-------+------+