How to get first and last row for every column - mysql

input
DATE TRANSACTION TYPE
may01 22 ATM
jun18 34 ATM
Aug14 38 NB
jul18 46 NB
Sep11 29 NB
Dec21 70 NB
jan02 46 MobileB
Jun19 20 MobileB
Sep13 81 MobileB
HOW TO GET FIRST AND LAST ROW FOR EACH COLUMN LIKE
TYPE Start_DATE End_DATE
ATM may01 jun18
NB Aug14 Dec21
MobileB jan02 Sep13
IN this output have to get first date and last date group by TYPE.PLEASE HELP ME

Before you massively overhauled your question, I would have suggested:
SELECT 'COL1' AS `COL_NAMES`,(SELECT `c1` as `START_ROW` FROM `tbl` WHERE !ISNULL(`c1`) ORDER BY c1 ASC LIMIT 1) AS `ROW_START`,(SELECT `c1` as `END_ROW` FROM `tbl` WHERE !ISNULL(`c1`) ORDER BY c1 DESC LIMIT 1) AS `ROW_END`
UNION ALL
SELECT 'COL2' AS `COL_NAMES`,(SELECT `c2` as `START_ROW` FROM `tbl` WHERE !ISNULL(`c2`) ORDER BY c2 ASC LIMIT 1) AS `ROW_START`,(SELECT `c2` as `END_ROW` FROM `tbl` WHERE !ISNULL(`c2`) ORDER BY c2 DESC LIMIT 1) AS `ROW_END`
UNION ALL
SELECT 'COL3' AS `COL_NAMES`,(SELECT `c3` as `START_ROW` FROM `tbl` WHERE !ISNULL(`c3`) ORDER BY c3 ASC LIMIT 1) AS `ROW_START`,(SELECT `c3` as `END_ROW` FROM `tbl` WHERE !ISNULL(`c3`) ORDER BY c3 DESC LIMIT 1) AS `ROW_END`
UNION ALL
SELECT 'COL4' AS `COL_NAMES`,(SELECT `c4` as `START_ROW` FROM `tbl` WHERE !ISNULL(`c4`) ORDER BY c4 ASC LIMIT 1) AS `ROW_START`,(SELECT `c4` as `END_ROW` FROM `tbl` WHERE !ISNULL(`c4`) ORDER BY c4 DESC LIMIT 1) AS `ROW_END`
Assuming a table like:
CREATE TABLE IF NOT EXISTS `tbl` (
`c1` int(11) DEFAULT NULL,
`c2` int(11) DEFAULT NULL,
`c3` int(11) DEFAULT NULL,
`c4` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `tbl` (`c1`, `c2`, `c3`, `c4`) VALUES
(1, 30, 89, 34),
(2, 49, 76, 44),
(NULL, 52, 90, NULL),
(NULL, NULL, 16, NULL);
I can't give you an exact answer, now, with your new data, now, but my answer here should bring you pretty close, you'd just need to fix a few things like the column names, the table name. My !ISNULL(...) logic is fine if you're testing for the first and last non-NULL values, which includes the empty string. If you're testing for the empty string instead, use LENGTH(...)=0 instead, or a combination if both are possible: (!ISNULL(...) AND LENGTH(...)>0)

You can use GROUP_CONCAT and SUBSTRING_INDEX to get the first and last rows like below
select type,
SUBSTRING_INDEX(GROUP_CONCAT(CAST(date AS CHAR) ORDER BY date), ',', 1 ) as Start_date,
SUBSTRING_INDEX(GROUP_CONCAT(CAST(date AS CHAR) ORDER BY date DESC), ',', 1 ) as End_date
from test
group by type;
Check SQL Fiddle DEMO here.

the below code works fine , first i have converted date in to date format
select *,str_to_date(date,"%m/%d/%Y " ) as dat from tab3 ;
SELECT TYPE,MIN(DAT) AS SD,MAX(DAT) AS ED FROM A GROUP BY TYPE;

In SQL there is predefined functions for fetching the First and last row records based on column names
SELECT FIRST(column_name) FROM table_name;
SELECT LAST(column_name) FROM table_name;
In MySQL
SELECT column_name FROM table_name
ORDER BY column_name ASC
LIMIT 1;
SELECT column_name FROM table_name
ORDER BY column_name DESC
LIMIT 1;

select
*
from
(select * from application, (SELECT #rownums:=0) r order by (#rownum := #rownum + 1) asc limit 1) a1
union
select
*
from
(select * from application, (SELECT #rownum:=0) r1 order by (#rownum := #rownum + 1) desc limit 1) a2;

Related

SQL select top 3 or more if there are more items sharing the 3rd highest value?

Any SQL experts out there who can explain how I select say the top 3 items. When the 3rd item is equal to the 4th and 5th I'd like those included too but only in that situation. e.g. with the following list
40,
30,
15,
15,
15,
10
it would return 40,30,15,15,15 not 40,30,15.
If you want to output only the score field, you can do it like this:
SET #ranks = 3;
PREPARE stmt_top3 FROM
'SELECT score FROM (
SELECT score, (#row := #row + 1), IF (#row = ?, #min_score := score, NULL)
FROM user_score
WHERE score IN (
SELECT top_score.score FROM (
SELECT DISTINCT score,
(#row := 0), (#min_score := 0)
FROM user_score
ORDER BY score DESC
LIMIT ?
) AS top_score
)
ORDER BY score DESC
) AS score_rows
WHERE score >= #min_score
ORDER BY score DESC';
EXECUTE stmt_top3 USING #ranks, #ranks;
DEMO # SQL Fiddle
I used a prepared statement, so the number of ranks to show is flexible. If you don't want that, just hardcode a 3 instead of the 2 ?.
Otherwise, if you also need the rank and the user_id, i.e., here's a more extensive approach:
Highscore-Like Ranking (Shared Ranks)
Selects all rows that share the first rank, no matter how many there are.
As long as the number of these rows is not greater than or equal to the maximum of ranks allowed (3 in your example) the next rank is the number of rows + 1.
And everything begins from the start and so on ...
SET #ranks = 3;
PREPARE stmt_top3 FROM
'SELECT user_score.user_id, score_rank.rank, score_rank.score
FROM user_score
INNER JOIN (
SELECT (#last_rank := #last_rank + #last_equal_score) AS rank,
score, (#last_equal_score := count(score)) AS equal_score
FROM user_score
WHERE score IN (
SELECT top_score.score FROM (
SELECT DISTINCT score,
(#last_rank := 1), (#last_equal_score := 0)
FROM user_score
ORDER BY score DESC
LIMIT ?
) AS top_score
)
GROUP BY score
ORDER BY score DESC
) AS score_rank
ON user_score.score = score_rank.score
WHERE score_rank.rank <= ?
ORDER BY score_rank.rank ASC';
EXECUTE stmt_top3 USING #ranks, #ranks;
DEMO # SQL Fiddle
Due to the fact that MySQL does not support LIMIT in subqueries for certain subquery operators such as IN, you have to wrap your subquery that contains a LIMIT clause in another simple subquery to avoid the following error:
ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT
& IN/ALL/ANY/SOME subquery'
Restrictions on Subqueries
I used the following setup for testing:
CREATE TABLE user (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
) ENGINE=InnoDB;
INSERT INTO user (id) VALUES
(NULL),
(NULL),
(NULL),
(NULL),
(NULL),
(NULL);
CREATE TABLE user_score (
user_id INT UNSIGNED NOT NULL UNIQUE,
score INT NOT NULL DEFAULT 0,
INDEX (score),
FOREIGN KEY (user_id) REFERENCES user (id)
) ENGINE=InnoDB;
INSERT INTO user_score (user_id, score) VALUES
(1, 40),
(2, 30),
(3, 15),
(4, 15),
(5, 15),
(6, 10);
SELECT *
FROM myTable
WHERE Field1 IN(
SELECT *
FROM (
SELECT DISTINCT Field1
FROM myTable
ORDER BY Field1 DESC
LIMIT 3)
) t
The only issue you might have from your example is if you specifically DON'T want to include duplicates of 40 & 30?

How many different ways are there to get the second row in a SQL search?

Let's say I was looking for the second most highest record.
Sample Table:
CREATE TABLE `my_table` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`value` int(10),
PRIMARY KEY (`id`)
);
INSERT INTO `my_table` (`id`, `name`, `value`) VALUES (NULL, 'foo', '200'), (NULL, 'bar', '100'), (NULL, 'baz', '0'), (NULL, 'quux', '300');
The second highest value is foo. How many ways can you get this result?
The obvious example is:
SELECT name FROM my_table ORDER BY value DESC LIMIT 1 OFFSET 1;
Can you think of other examples?
I was trying this one, but LIMIT & IN/ALL/ANY/SOME subquery is not supported.
SELECT name FROM my_table WHERE value IN (
SELECT MIN(value) FROM my_table ORDER BY value DESC LIMIT 1
) LIMIT 1;
Eduardo's solution in standard SQL
select *
from (
select id,
name,
value,
row_number() over (order by value) as rn
from my_table t
) t
where rn = 1 -- can pick any row using this
This works on any modern DBMS except MySQL. This solution is usually faster than solutions using sub-selects. It also can easily return the 2nd, 3rd, ... row (again this is achievable with Eduardo's solution as well).
It can also be adjusted to count by groups (adding a partition by) so the "greatest-n-per-group" problem can be solved with the same pattern.
Here is a SQLFiddle to play around with: http://sqlfiddle.com/#!12/286d0/1
This only works for exactly the second highest:
SELECT * FROM my_table two
WHERE EXISTS (
SELECT * FROM my_table one
WHERE one.value > two.value
AND NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
)
LIMIT 1
;
This one emulates a window function rank() for platforms that don't have them. It can also be adapted for ranks <> 2 by altering one constant:
SELECT one.*
-- , 1+COALESCE(agg.rnk,0) AS rnk
FROM my_table one
LEFT JOIN (
SELECT one.id , COUNT(*) AS rnk
FROM my_table one
JOIN my_table cnt ON cnt.value > one.value
GROUP BY one.id
) agg ON agg.id = one.id
WHERE agg.rnk=1 -- the aggregate starts counting at zero
;
Both solutions need functional self-joins (I don't know if mysql allows them, IIRC it only disallows them if the table is the target for updates or deletes)
The below one does not need window functions, but uses a recursive query to enumerate the rankings:
WITH RECURSIVE agg AS (
SELECT one.id
, one.value
, 1 AS rnk
FROM my_table one
WHERE NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
UNION ALL
SELECT two.id
, two.value
, agg.rnk+1 AS rnk
FROM my_table two
JOIN agg ON two.value < agg.value
WHERE NOT EXISTS (
SELECT * FROM my_table nx
WHERE nx.value > two.value
AND nx.value < agg.value
)
)
SELECT * FROM agg
WHERE rnk = 2
;
(the recursive query will not work in mysql, obviously)
You can use inline initialization like this:
select * from (
select id,
name,
value,
#curRank := #curRank + 1 AS rank
from my_table t, (SELECT #curRank := 0) r
order by value desc
) tb
where tb.rank = 2
SELECT name
FROM my_table
WHERE value < (SELECT max(value) FROM my_table)
ORDER BY value DESC
LIMIT 1
SELECT name
FROM my_table
WHERE value = (
SELECT min(r.value)
FROM (
SELECT name, value
FROM my_table
ORDER BY value DESC
LIMIT 2
) r
)
LIMIT 1

MySQL Group By get title column with smallest date value

I have a query like:
SELECT *
FROM table
GROUP BY sid
ORDER BY datestart desc
LIMIT 10
which returns the last 10 sid groups.
For each of these groups, I need the title column of the row with the lowest datestart value
I tried using
SELECT *, min(datestart)
but that didn't return the row with the smallest datestart value, just the lowest datestart. I need the title from the lowest datestart.
(Relevant) Table Structure:
CREATE TABLE `table` (
`title` varchar(1000) NOT NULL,
`datestart` timestamp NOT NULL default CURRENT_TIMESTAMP,
`sid` bigint(12) unsigned NOT NULL,
KEY `datestart` (`datestart`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Any ideas?
Updated answer
select t1.* from `table` as t1
inner join (
select sid,min(datestart) as elder
from `table`
group by sid
order by elder desc limit 10) as t2
on t1.sid = t2.sid and t1.datestart = t2.elder
Use a composite index on (sid,datestart)
Try this query. You will get expected results. If it don't work change Table_2.datestart > Table_1.datestart by Table_2.datestart < Table_1.datestart
SELECT title, datestart
FROM `table` AS Table_1
LEFT JOIN `table` AS Table_2 ON (Table_2.sid = Table_1.sid AND Table_2.datestart > Table_1.datestart)
Table_2.sid IS NULL;
Edited query
SELECT Table_1.title, Table_1.datestart
FROM `table` AS Table_1
LEFT JOIN `table` AS Table_2 ON (Table_2.sid = Table_1.sid AND Table_2.datestart > Table_1.datestart)
Table_2.sid IS NULL;

MySQL query, MAX() + GROUP BY

Daft SQL question. I have a table like so ('pid' is auto-increment primary col)
CREATE TABLE theTable (
`pid` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`cost` INT UNSIGNED NOT NULL,
`rid` INT NOT NULL,
) Engine=InnoDB;
Actual table data:
INSERT INTO theTable (`pid`, `timestamp`, `cost`, `rid`)
VALUES
(1, '2011-04-14 01:05:07', 1122, 1),
(2, '2011-04-14 00:05:07', 2233, 1),
(3, '2011-04-14 01:05:41', 4455, 2),
(4, '2011-04-14 01:01:11', 5566, 2),
(5, '2011-04-14 01:06:06', 345, 1),
(6, '2011-04-13 22:06:06', 543, 2),
(7, '2011-04-14 01:14:14', 5435, 3),
(8, '2011-04-14 01:10:13', 6767, 3)
;
I want to get the PID of the latest row for each rid (1 result per unique RID). For the sample data, I'd like:
pid | MAX(timestamp) | rid
-----------------------------------
5 | 2011-04-14 01:06:06 | 1
3 | 2011-04-14 01:05:41 | 2
7 | 2011-04-14 01:14:14 | 3
I've tried running the following query:
SELECT MAX(timestamp),rid,pid FROM theTable GROUP BY rid
and I get:
max(timestamp) ; rid; pid
----------------------------
2011-04-14 01:06:06; 1 ; 1
2011-04-14 01:05:41; 2 ; 3
2011-04-14 01:14:14; 3 ; 7
The PID returned is always the first occurence of PID for an RID (row / pid 1 is frst time rid 1 is used, row / pid 3 the first time RID 2 is used, row / pid 7 is first time rid 3 is used). Though returning the max timestamp for each rid, the pids are not the pids for the timestamps from the original table. What query would give me the results I'm looking for?
(Tested in PostgreSQL 9.something)
Identify the rid and timestamp.
select rid, max(timestamp) as ts
from test
group by rid;
1 2011-04-14 18:46:00
2 2011-04-14 14:59:00
Join to it.
select test.pid, test.cost, test.timestamp, test.rid
from test
inner join
(select rid, max(timestamp) as ts
from test
group by rid) maxt
on (test.rid = maxt.rid and test.timestamp = maxt.ts)
select *
from (
select `pid`, `timestamp`, `cost`, `rid`
from theTable
order by `timestamp` desc
) as mynewtable
group by mynewtable.`rid`
order by mynewtable.`timestamp`
Hope I helped !
SELECT t.pid, t.cost, to.timestamp, t.rid
FROM test as t
JOIN (
SELECT rid, max(tempstamp) AS maxtimestamp
FROM test GROUP BY rid
) AS tmax
ON t.pid = tmax.pid and t.timestamp = tmax.maxtimestamp
I created an index on rid and timestamp.
SELECT test.pid, test.cost, test.timestamp, test.rid
FROM theTable AS test
LEFT JOIN theTable maxt
ON maxt.rid = test.rid
AND maxt.timestamp > test.timestamp
WHERE maxt.rid IS NULL
Showing rows 0 - 2 (3 total, Query took 0.0104 sec)
This method will select all the desired values from theTable (test), left joining itself (maxt) on all timestamps higher than the one on test with the same rid. When the timestamp is already the highest one on test there are no matches on maxt - which is what we are looking for - values on maxt become NULL. Now we use the WHERE clause maxt.rid IS NULL or any other column on maxt.
You could also have subqueries like that:
SELECT ( SELECT MIN(t2.pid)
FROM test t2
WHERE t2.rid = t.rid
AND t2.timestamp = maxtimestamp
) AS pid
, MAX(t.timestamp) AS maxtimestamp
, t.rid
FROM test t
GROUP BY t.rid
But this way, you'll need one more subquery if you want cost included in the shown columns, etc.
So, the group by and join is better solution.
If you want to avoid a JOIN, you can use:
SELECT pid, rid FROM theTable t1 WHERE t1.pid IN ( SELECT MAX(t2.pid) FROM theTable t2 GROUP BY t2.rid);
Try:
select pid,cost, timestamp, rid from theTable order by timestamp DESC limit 2;

Time interval calculation in time series using SQL

I have a MySQL table like this
CREATE TABLE IF NOT EXISTS `vals` (
`DT` datetime NOT NULL,
`value` INT(11) NOT NULL,
PRIMARY KEY (`DT`)
);
the DT is unique date with time
data sample:
INSERT INTO `vals` (`DT`,`value`) VALUES
('2011-02-05 06:05:00', 300),
('2011-02-05 11:05:00', 250),
('2011-02-05 14:35:00', 145),
('2011-02-05 16:45:00', 100),
('2011-02-05 18:50:00', 125),
('2011-02-05 19:25:00', 100),
('2011-02-05 21:10:00', 125),
('2011-02-06 00:30:00', 150);
I need to get something like this:
start|end|value
NULL,'2011-02-05 06:05:00',300
'2011-02-05 06:05:00','2011-02-05 11:05:00',250
'2011-02-05 11:05:00','2011-02-05 14:35:00',145
'2011-02-05 14:35:00','2011-02-05 16:45:00',100
'2011-02-05 16:45:00','2011-02-05 18:50:00',125
'2011-02-05 18:50:00','2011-02-05 19:25:00',100
'2011-02-05 19:25:00','2011-02-05 21:10:00',125
'2011-02-05 21:10:00','2011-02-06 00:30:00',150
'2011-02-06 00:30:00',NULL,NULL
I tried the following query:
SELECT T1.DT AS `start`,T2.DT AS `stop`, T2.value AS value FROM (
SELECT DT FROM vals
) T1
LEFT JOIN (
SELECT DT,value FROM vals
) T2
ON T2.DT > T1.DT ORDER BY T1.DT ASC
but it returns to many rows (29 instead of 9) in result and I cold not find any way to limit this using SQL. Is it Possible in MySQL?
Use a subquery
SELECT
(
select max(T1.DT)
from vals T1
where T1.DT < T2.DT
) AS `start`,
T2.DT AS `stop`,
T2.value AS value
FROM vals T2
ORDER BY T2.DT ASC
You can also use a MySQL specific solution employing variables
SELECT CAST( #dt AS DATETIME ) AS `start` , #dt := DT AS `stop` , `value`
FROM (SELECT #dt := NULL) dt, vals
ORDER BY dt ASC
But you need to do it precisely
the ORDER by must be present otherwise the variables don't roll properly
the variable needs to be NULLified within the query using a subquery to set it, otherwise if you run it twice in a row, the 2nd time it will not start with NULL
You can use a server-side variable to simulate it:
select #myvar as start, end, value, #myvar := end as next_rows_start
from vals
Variables are interpreted from left-right in sequence, so the two references to #myvar (start and next_rows_start) will output with two different values.
Just remember to reset #myvar to null before and/or after the query, otherwise the second and subsequent runs will have a wrong first row:
select #myvar := null
This would be easier if the table had a running ID column which corresponds to the times in DT (same order). If you don't want to change the table you can use a temp:
drop table if exists temp;
CREATE TABLE temp (
`id` INT(11) AUTO_INCREMENT,
`DT` datetime NOT NULL,
`value` INT(11) NOT NULL,
PRIMARY KEY (`id`)
);
insert into temp (DT,value) select * from vals order by DT asc;
select t1.DT as `start`, t2.DT as `end`, t2.value
from temp t2
left join temp t1 ON t2.id = t1.id + 1;