I have table:
T1
| Name | score |
| John | 1 |
|Anton | 2 |
|George| 8 |
|Peter | 1 |
| Tom | 2 |
I need to loop over table between two values.
I need to create a procedure or a function that for given value1 and value2 prints out (in alphabetical order) names that have in total(score) more than value1 and less than value2.
EXAMPLE
For value1= 5, and value2=12.
First i need to sort names alphabetically.
| Name | score |
|Anton | 2 |
|George| 8 |
| John | 1 |
|Piter | 1 |
| Tom | 2 |
Since value1 is 5, and value2 is 12, i need to loop until sum(score) will be more than 5, and less than 12. In this case result should be:
| Name |
|Anton |
|George|
| John |
because in total they got more than 5 and less than 12 score.
1)discard scores > 12 2) workout cumulative sum 3) work out max cumulative sum in the required range 4) select those there cumulative sum is < max cumulative sum
DROP table if exists t;
create table t
( Name varchar(10), score int);
insert into t values
('Anton' , 12),
('George', 12),
('John' , 1),
('Piter' , 1),
('Tom' , 2),
('bilal' , 5)
;
with
cte as
(select t.*,
sum(score) over (order by name) cumsum
from t
where score < 12) ,
cte1 as
(select max(cumsum) maxcumsum from cte where cumsum < 12 and cumsum > 5),
cte2 as
(select cte.*,cte1.maxcumsum
from cte
cross join cte1)
select * from cte2
where maxcumsum > 5 and cumsum <= maxcumsum
order by name;
+-------+-------+--------+-----------+
| Name | score | cumsum | maxcumsum |
+-------+-------+--------+-----------+
| bilal | 5 | 5 | 9 |
| John | 1 | 6 | 9 |
| Piter | 1 | 7 | 9 |
| Tom | 2 | 9 | 9 |
+-------+-------+--------+-----------+
4 rows in set (0.022 sec)
Related
This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 1 year ago.
Please assume this table:
// mytable
+--------+-------------+---------+
| num | business_id | user_id |
+--------+-------------+---------+
| 3 | 503 | 12 |
| 7 | 33 | 12 |
| 1 | 771 | 13 |
| 2 | 86 | 13 |
| 1 | 772 | 13 |
| 4 | 652 | 14 |
| 4 | 567 | 14 |
+--------+-------------+---------+
I need to group it based on user_id, So, here is my query:
select max(num), user_id from mytable
group by user_id
Here is the result:
// res
+--------+---------+
| num | user_id |
+--------+---------+
| 7 | 12 |
| 2 | 13 |
| 4 | 14 |
+--------+---------+
Now I need to also get the business_id of those rows. Here is the expected result:
// mytable
+--------+-------------+---------+
| num | business_id | user_id |
+--------+-------------+---------+
| 7 | 33 | 12 |
| 2 | 86 | 13 |
| 4 | 567 | 14 | -- This is selected randomly, because of the equality of values
+--------+-------------+---------+
Any idea how can I do that?
You don't group. You filter. One method uses window functions such as row_number():
select t.*
from (select t.*,
row_number() over (partition by user_id order by num desc) as seqnum
from mytable t
) t
where seqnum = 1;
Another method which can have slightly better performance with an index on (user_id, num) is a correlated subquery:
select t.*
from mytable t
where t.num = (select max(t2.num)
from mytable t2
where t2.user_id = t.user_id
);
You should think "group by" when you want to summarize rows. You should think "where" when you want to choose rows with particular characteristics.
I'm trying to make a query setting rank column by First and Second column. Like Rank over Partition which doesn't exist MySQL
For example,
From
+----+-------+--------+------+
| id | First | Second | Rank |
+----+-------+--------+------+
| 1 | a | 10 | |
| 2 | a | 9 | |
| 3 | b | 10 | |
| 4 | b | 7 | |
| 5 | a | 1 | |
| 6 | b | 1 | |
+----+-------+--------+------+
To
+----+-------+--------+------+
| id | First | Second | Rank |
+----+-------+--------+------+
| 1 | a | 10 | 3 |
| 2 | a | 9 | 2 |
| 3 | b | 10 | 3 |
| 4 | b | 7 | 2 |
| 5 | a | 1 | 1 |
| 6 | b | 1 | 1 |
+----+-------+--------+------+
The Rank doesn't continue. It starts from 1 again when it reaches the last value of 'a' of 'First' column.
And it's gotta be SET not SELECT.
I wouldn't mind using SELECT but my point is I'm not trying to retrieve data from Database but setting values.
Cheers in advance mates.
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,first CHAR(1) NOT NULL
,second INT NOT NULL
);
INSERT INTO my_table VALUES
(1,'a',10),
(2,'a',9),
(3,'b',10),
(4,'b',7),
(5,'a',1),
(6,'b',1);
SELECT id
, first
, second
, rank
FROM
( SELECT x.*
, CASE WHEN #prev = first THEN #i:=#i+1 ELSE #i:=1 END rank
, #prev:=first
FROM my_table x
, (SELECT #prev:=null,#i:=0) vars
ORDER
BY first
, second
, id
) a
ORDER
BY id;
+----+-------+--------+------+
| id | first | second | rank |
+----+-------+--------+------+
| 1 | a | 10 | 3 |
| 2 | a | 9 | 2 |
| 3 | b | 10 | 3 |
| 4 | b | 7 | 2 |
| 5 | a | 1 | 1 |
| 6 | b | 1 | 1 |
+----+-------+--------+------+
6 rows in set (0.00 sec)
Came up with a solution which I was looking for.
I'm not sure if these queries are completely safe but so far no harms.
SET #rank = 0, #First = ''
UPDATE 'Table' SET
rank = IF(#First = First, #rank:= #rank +1, #rank := 1 AND #First := First)
ORDER BY First ASC, Second;
One method is a correlated subquery. For rank() you can do:
select t.*,
(select count(*) + 1
from t t2
where t2.first = t.first and t2.second < t.second
) as rank
from t;
Ranks are tricky to handle with variables (dense_rank() and row_number() are simpler).
EDIT:
This is easy to turn into an update:
update t join
(select t.*,
(select count(*) + 1
from t t2
where t2.first = t.first and t2.second < t.second
) as new_rank
from t
) tt
on t.id = tt.id
set t.rank = tt.new_rank;
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
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 a table named conductor. I want to select latest records that date less than my_value.
+----+-----------+------+
| id | program | date |
+----+-----------+------+
| 1 | program 1 | 1 |
| 2 | program 1 | 3 |
| 3 | program 2 | 3 |
| 4 | program 1 | 5 |
| 5 | program 1 | 7 |
+----+-----------+------+
If we consider my_value is 4 then output will be:
+----+-----------+------+
| id | program | date |
+----+-----------+------+
| 2 | program 1 | 3 |
| 3 | program 2 | 3 |
+----+-----------+------+
How can I select records by SQL?
SELECT * FROM Conductor
WHERE `date` = (SELECT max(`date`) FROM Conductor
WHERE `date` < myvalue )
You can try a query like:
SELECT * FROM conductor
WHERE date = (SELECT date FROM conductor
WHERE date < my_value ORDER BY DESC limit 1);
This should give you what you are expecting!
SELECT * FROM Conductor
WHERE date IN (SELECT max(date) FROM Conductor
WHERE date < myvalue )
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,program VARCHAR(12) NOT NULL
,date INT NOT NULL
);
INSERT INTO my_table VALUES
(1 ,'program 1',1),
(2 ,'program 1',3),
(3 ,'program 2',3),
(4 ,'program 1',5),
(5 ,'program 1',7);
SELECT * FROM my_table;
+----+-----------+------+
| id | program | date |
+----+-----------+------+
| 1 | program 1 | 1 |
| 2 | program 1 | 3 |
| 3 | program 2 | 3 |
| 4 | program 1 | 5 |
| 5 | program 1 | 7 |
+----+-----------+------+
SELECT x.*
FROM my_table x
JOIN
( SELECT program
, MAX(date) max_date
FROM my_table
WHERE date < 4
GROUP
BY program
) y
ON y.program = x.program
AND y.max_date = x.date;
+----+-----------+------+
| id | program | date |
+----+-----------+------+
| 2 | program 1 | 3 |
| 3 | program 2 | 3 |
+----+-----------+------+