Insert a new column based on existing column values - mysql

I have a table table1 in mysql like this:
price item count
100 xyz 5
200 xyz 1
300 xyz 4
400 abc 1
500 abc 2
I want to insert a new column 'new_price' that will hold the 'price' for that 'item' with the highest 'count'. So the new table will be
price item count new_price
100 xyz 5 100
200 xyz 1 100
300 xyz 4 100
400 abc 1 500
500 abc 2 500
What is the most efficient way of doing this? Thanks very much for your help.

I think the easiest approach is to use variables:
select t.*,
(#price := if(#i = item, #price,
if(#i := item, price, price)
)
) as new_price
from table1 t cross join
(select #i := '', #price := -1) vars
order by item, count desc;
If you actually want to update values in the table, you can fit this into an update as well:
update table1 t join
(select t.*,
(#price := if(#i = item, #price,
if(#i := item, price, price)
)
) as new_price
from table1 t cross join
(select #i := '', #price := -1) vars
order by item, count desc
) tp
on tp.item = t.item and tp.price = t.price and tp.count = t.count
set t.new_price = tp.price;

Related

How to return rows that sum(field) <= value

The concept is to find the rows in which sum(fCurrAmt) may higher than the entered amount but should not lower than entered amount. I dont know how to explain indetail this creteria.
Lets say I have a table demo
Scenario : 1
id fCurrAmt price
------------------
1 1 10
2 1 20
3 2 25
4 3 30
If the entered amount is 3, I need to return first 3 rows
id fCurrAmt price
------------------
1 1 10
2 1 20
3 2 25
In the above scenario, sum(fCurrAmt) is 4 which is higher than entered amount.
Scenario : 2
id fCurrAmt price
------------------
1 1 10
2 1 20
If the entered amount is 3, I need to return there is no records.
In the above scenario, sum(fCurrAmt) is 2 which is lower than entered amount.
I have tried with below code in scenario 1
SELECT a.id,a.price,a.total,a.fCurrAmt from (
select b.id,b.price,b.fCurrAmt,(
select sum(fCurrAmt) from demo c where c.id <= b.id order by c.id
) as total from demo b
) a where a.total <= 3
It returns first 2 records only
Try This You need to use subquery and min with group by. Using subquery we can return the minimum id where the sum is satisfied with given numbers and then join the id to retrieve full rows upto the id
SELECT *
FROM test t
INNER JOIN(
SELECT MIN(id) valId
FROM (
SELECT t.id,
(SELECT SUM(t1.fCurrAmt)
FROM test t1
WHERE t1.id <= t.id) AS Rowsum
FROM test t) t2
WHERE Rowsum >= 3) t1 ON t1.valId >= t.id;
SQL Fiddle http://www.sqlfiddle.com/#!9/a1d07/13
Try this
DECLARE #sumOfFCurrAmt int
DECLARE #sumOfEnteredAmt int
set #sumOfFCurrAmt=(select Sum(fCurrAmt) from demoB)
set #sumOfEnteredAmt=(select sum(fCurrAmt) from demoC)
IF(#sumOfFCurrAmt>#sumOfEnteredAmt)
BEGIN
SELECT top(#sumOfEnteredAmt)* FROM demoB
END
A slightly lengthy way but it works.
First, I would store the sum of fCurrAmt up to the number entered in a temporary table. Hence, the first three statements are DROP, CREATE and INSERT. I would then take that value to check if the sum of those rows until the number entered are greater or lesser, if it is greater, then return all the rows until the threshold else return nothing. Here, sof12 is the same table as scenario 1 and sof14 is the same table as yours in scenario 2.
SCENARIO 1:
DROP TABLE IF EXISTS `tempsum`;
CREATE TABLE tempsum (`sum` integer(13));
INSERT INTO tempsum (SELECT SUM(fCurrAmt) FROM
(SELECT NULL AS id, NULL AS fCurrAmt, NULL AS price, NULL AS total
FROM dual
WHERE (#total := 0)
UNION
SELECT id, fCurrAmt, price, #total := #total + fCurrAmt AS total
FROM sof12
WHERE #total <= 3) as new2);
SELECT id, fCurrAmt, price FROM (
SELECT NULL AS id, NULL AS fCurrAmt, NULL AS price, NULL AS total
FROM dual
WHERE (#total := 0)
UNION
SELECT id, fCurrAmt, price, #total := #total + fCurrAmt AS total
FROM sof12
WHERE #total <= 3) As new3 HAVING (SELECT SUM(SUM) FROM tempsum) >= 3;
Output of this case:
id fCurrAmt price
1 1 10
2 1 20
3 2 25
SCENARIO 2:
DROP TABLE IF EXISTS `tempsum`;
CREATE TABLE tempsum (`sum` integer(13));
INSERT INTO tempsum (SELECT SUM(fCurrAmt) FROM
(SELECT NULL AS id, NULL AS fCurrAmt, NULL AS price, NULL AS total
FROM dual
WHERE (#total := 0)
UNION
SELECT id, fCurrAmt, price, #total := #total + fCurrAmt AS total
FROM sof14
WHERE #total <= 3) as new2);
SELECT id, fCurrAmt, price FROM (
SELECT NULL AS id, NULL AS fCurrAmt, NULL AS price, NULL AS total
FROM dual
WHERE (#total := 0)
UNION
SELECT id, fCurrAmt, price, #total := #total + fCurrAmt AS total
FROM sof14
WHERE #total <= 3) As new3 HAVING (SELECT SUM(SUM) FROM tempsum) >= 3;
Output of this case: No records returned.

Mysql group by (custom number for result)

I have a table
Id Name Id_collection Price
1 good1 2 10
2 good2 1 101
3 good3 3 102
4 good4 2 10
5 good5 2 10
I need to Group By id_collection, but i need to show 2 rows (to be able to change this value via variable or ... Ex: to change to 3 or 4 )
not
1
2
3
Ex
1
1
2
2
3
3
or
Ex
1
1
1
2
2
2
3
3
3
so the result must be
Id Name Id_collection Price
1 good1 2 10
4 good4 2 10
2 good2 1 101
3 good3 3 102
I was thinking about procedure or loop, but i didn't that before, Please help!!!
You want to group by adjacent values . . . in MySQL. You can use variables to assign the group. Alternatively, you can use this method to assign the group: count the number of rows that have id_collection different from each row with a smaller id.
You don't specify how to calculate the other columns, but here is a guess:
select min(id) as id, min(name) as name, id_collection, avg(price) as price
from (select t.*,
(select count(*)
from t t2
where t2.id_collection <> t.id_collection and
t2.id < t.id
) as grp
from t
) t
group by id_collection, grp;
EDIT:
I just realized that you probably don't want to aggregate the results; you probably just want the first row. For that, use variables:
select t.*
from (select t.*,
(#rn := if(#id = id_collection, #rn,
if(#id := id_collection, #rn + 1, #rn + 1)
)
) as rn
from t cross join
(select #id = -1, #rn := 0) params
order by id
) t
where rn = 1;
I edited a little the answer which #Gordon_Linoff posted, and it works now, you just have to change rn <= 3 this number and will get the various results.
#Gordon_Linoff - Thank you, this really helped me
select t.*
from (select t.*,
(#rn := if(#id = id_collection, #rn + 1,
if(#id := id_collection, 1, 0)
)
) as rn
from t cross join
(select #id := -1, #rn := 0) params
order by id_collection
) t
where rn <= 3;

Select recent n number of entries of all users from table

I have a below table and wants to select only last 2 entries of all users.
Source table:
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 10 2016-5-12
2 10 2016-5-12
1 11 2016-6-12
2 12 2016-8-12
3 12 2016-8-12
2 13 2016-8-12
1 14 2016-9-12
3 14 2016-9-12
3 11 2016-6-12
Expected output is like, (should list only recent 2 quizid entries for all users)
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 14 2016-9-12
1 11 2016-6-12
2 13 2016-8-12
2 12 2016-8-12
3 14 2016-9-12
3 12 2016-8-12
Any idea's to produce this output.
Using MySQL user defined variables you can accomplish this:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2
Working Demo
Note: If you want at most x number of entries for each user then change the condition in where clause like below:
WHERE t.row_number <= x
Explanation:
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC;
This query sorts all the data in ascending order of userId and descending order of quizendtime(AID).
Now take a walk on this (multi) sorted data.
Every time you see a new userId assign a row_number (1). If you see the same user again then just increase the row_number.
Finally filtering only those records which are having row_number <= 2 ensures the at most two latest entries for each user.
EDIT: As Gordon pointed out that the evaluation of expressions using user defined variables in mysql is not guaranteed to follow the same order always so based on that the above query is slightly modified:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF (
#sameUser = UserId,
#a := #a + 1,
IF(#sameUser := UserId, #a := 1, #a:= 1)
)AS row_number
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2;
WORKING DEMO V2
User-defined variables are the key to the solution. But, it is very important to have all the variable assignments in a single expression. MySQL does not guarantee the order of evaluation of expressions in a select -- and, in fact, sometimes processes them in different orders.
select t.*
from (select t.*,
(#rn := if(#u = UserId, #rn + 1,
if(#u := UserId, 1, 1)
)
) as rn
from t cross join
(select #u := -1, #rn := 0) params
order by UserId, quizendtime desc
) t
where rn <= 2;

Mysql difference between rows

i want to get price difference of car from 2 row through given following data.
i want to substract price column ex: (200-100),(300-200) and so on as data
My Table:
My desired output:
what i have tried
select t1.row_num1,t1.car_name
from
(
select (#row_num := #row_num +1) as row_num1 ,(select #row_num =0) r1, car_name,price
from car
)t1
I know that i don't have id column.hence i am generating row_number.
now i am getting problem to self join this table and get difference.
your help is appreciable.
Try This
set #next_row_price := null;
SELECT car_name , price, diff FROM(
SELECT car_name,price,(#next_row_price - price) * -1 AS diff,
IF(#next_row_price IS NULL, #next_row_price := price, 0) ,
IF(#next_row_price IS NOT NULL, #next_row_price := price, 0)
FROM car
) AS TEMP;
SQLFiddle
Although your output seems confusing nevertheless I am giving the following answer:
SOLUTION #1
SELECT
carsTable1.car_name,
carsTable1.price,
CASE WHEN ABS(carsTable1.price - (SELECT price FROM cars WHERE car_name='car 2')) = 0 THEN NULL ELSE
ABS(carsTable1.price - (SELECT price FROM cars WHERE car_name='car 2')) END diff
FROM
(SELECT
#rn := #rn + 1 row_number,
cars.car_name,
cars.price
FROM cars, (SELECT #rn := 0) var
) carsTable1;
Demo Here
Sample Input:
car_name price
car 1 100
car 2 200
car 3 300
Sample Output:
car_name price diff
car 1 100 100
car 2 200 NULL
car 3 300 100
Note: The price of car 2 is compared with the price of the rest of the cars. So the result shows null for car 2 since it's the reference car.
If I misunderstood your requirement then it must be : You want the price differences between the consecutive rows i.e. (No car,car1),(car1,car2), (car2,car3), (car3,car4)....
So in this case you can adopt the following query :
SOLUTION #2
SELECT
car_name,
cars.price,
CASE WHEN #currentPrice = 0 THEN NULL ELSE ABS(cars.price - #currentPrice) END AS diff,
#currentPrice := price
FROM cars ,(SELECT #currentPrice := 0) var
ORDER BY car_name
SQL FIDDLE BASED ON THIS QUERY
And if you want to omit the fourth column:
SELECT
t.car_name,
t.price,
t.diff
FROM
(
SELECT
car_name,
cars.price,
CASE WHEN #currentPrice = 0 THEN NULL ELSE (cars.price - #currentPrice) END AS diff,
#currentPrice := price
FROM cars ,(SELECT #currentPrice := 0) var
ORDER BY car_name ) t
SQL FIDDLE BASED ON THIS QUERY
Try this:-
CREATE TABLE #TempTable (rownum INT, price int, car_name VARCHAR(256));
INSERT INTO #TempTable (rownum, price, car_name)
SELECT
rownum = ROW_NUMBER() OVER (ORDER BY c.car_id),
c.price,
c.car_name
FROM car c;
SELECT
NEX.car_name + '-' + TT.car_name,
(nex.price - tt.price) AS Differences
FROM #TempTable TT
LEFT JOIN #TempTable prev ON prev.rownum = TT.rownum - 1
LEFT JOIN #TempTable nex ON nex.rownum = TT.rownum + 1;

How to get correct position on ties in mysql rankings

This is my code and works for ties but it does not skip position on ties
SELECT `item`, (`totalrate` / `nrrates`),
#rank_count := #rank_count + (totalrate/nrrates < #prev_value) rank,
#prev_value := totalrate/nrrates avg
FROM table, (SELECT #prev_value := NULL, #rank_count := 1) init
ORDER BY avg DESC
Here is the out I get
item (`totalrate` / `nrrates`) rank avg
Virginia 10.0000 1 10
Ana 9.7500 2 9.75
Angeie 9.72 3 9.72
Carel 9.666666666 4 9.66
sammy 9.666666666 4 9.66
Oda 9.500000000 5 9.5
I want
item (`totalrate` / `nrrates`) rank avg
Virginia 10.0000 1 10
Ana 9.7500 2 9.75
Angeie 9.72 3 9.72
Carel 9.666666666 4 9.66
sammy 9.666666666 4 9.66
Oda 9.500000000 6 9.5
To skip the 5 position
I would like to merge with this that does skip position on ties
(I took the below code from this post
MySQL Rank in the Case of Ties)
SELECT t1.name, (SELECT COUNT(*) FROM table_1 t2 WHERE t2.score > t1.score) +1
AS rnk
FROM table_1 t1
how would I modify my code to get it to skip position with the above code it looks simple but i haven't figured it out.
Thanks
On ties, you may want to skip and use current row num to next unmatched avg value row as next rank.
Following should help you
SELECT `item`, #curr_avg := ( `totalrate` / `nrrates` )
, case when #prev_avg = #curr_avg then #rank := #rank
else #rank := ( #cur_row + 1 )
end as rank
, #cur_row := ( #cur_row + 1 ) as cur_row
, #prev_value := #curr_avg avg
FROM table
, ( SELECT #prev_avg := 0, #curr_avg := 0
, #rank := 0, #cur_row := 0 ) init
ORDER BY avg DESC
Similar examples:
To display top 4 rows using rank
Mysql Query for Rank (RowNumber) and Groupings
Update a field with an incrementing value that resets based on
field
Here's another alternative. First, the averages are calculated. If they are already available in a table, it would be even easier (as can be seen in the fiddle demo). Anyways, the rank is based on the logic of counting how many items have a lesser average than the current item.
SELECT
A1.`item`,
A1.avg,
COUNT(A2.`item`) avg_rank
FROM
(
SELECT `item`, (`totalrate` / `nrrates`),
#prev_value := totalrate/nrrates avg
FROM table, (SELECT #prev_value := NULL, #rank_count := 1) init
) A1 --alias for the inline view
INNER JOIN
(
SELECT `item`, (`totalrate` / `nrrates`),
#prev_value := totalrate/nrrates avg
FROM table, (SELECT #prev_value := NULL, #rank_count := 1) init
) A2 --alias for the inline view
ON A2.avg < A1.avg
GROUP BY A1.id, A1.avg
ORDER BY A1.avg;
SQL Fiddle demo