Mysql difference between rows - mysql

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;

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.

Offset with Quantity in SQL

Let us suppose we have following tables
product_id | quantity
1 | 250
2 | 150
3 | 120
4 | 300
5 | 301
How do we know that the item number of 401th in SQL? (the answer should be product_id : 3). The query should return the product_id
Let us assume also the row has been in order
You can use Correlated query to find cummulative sum and then filter range using between to find the required slot:
select product_id
from (
select a.*,
coalesce((
select sum(quantity)
from your_table b
where b.product_id < a.product_id
), 0) + 1 cquant1,
(
select sum(quantity)
from your_table b
where b.product_id <= a.product_id
) cquant2
from your_table a
) t
where 401 between cquant1 and cquant2;
Demo
You can also use user variable for this:
select *
from (
select product_id,
#sum1 := #sum1 + coalesce((
select quantity
from your_table x
where x.product_id < t.product_id
order by x.product_id desc limit 1
), 0) as cquantity1,
#sum2 := #sum2 + quantity as cquantity2
from your_table t,
(select #sum1 := 0, #sum2 := 0) t2
order by product_id
) t
where 401 between cquantity1 and cquantity2;
Demo
In case of ORACLE, this will not work with SQLServer
This is by using LAG and SUM OVER() functions,
SELECT PRODUCT_ID FROM
(
SELECT PRODUCT_ID
, LAG(CUM_QUAN, 1, 0) OVER (ORDER BY PRODUCT_ID) AS START_QUAN
, CUM_QUAN END_QUAN
FROM
(
SELECT PRODUCT_ID
, QUANTITY
, SUM(QUANTITY) OVER (ORDER BY PRODUCT_ID) AS CUM_QUAN
FROM YOUR_TABLE
)
) WHERE 401 BETWEEN START_QUAN AND END_QUAN
You can do this with variables by getting a cumulative sum. However, Gurv's answer is way too complicated.
I think this is the simplest way:
select t.*
from (select t.*, (#s := #s + quantity) as running_quantity
from t cross join
(select #s := 0) params
order by product_id
) t
where 401 < running_quantity and
401 >= running_quantity - quantity;

Check if a user was "active" in multiple rows - MySQL

How would I go about creating group_ids in the following example based on the area(s) the users are active in?
group_id rep_id area datebegin dateend
1 1000 A 1/1/15 1/1/16
1 1000 B 1/1/15 1/1/16
2 1000 C 1/2/16 12/31/99
In the table you can see that rep 1000 was active in both A and B between 1/15 and 1/16. How would I go about coding the group_id field to group by datebegin & dateend?
Thanks for any help.
You can use variables in order to enumerate groups of records having identical rep_id, datebegin, dateend values:
SELECT rep_id, datebegin, dateend,
#rn := IF(#rep_id <> rep_id,
IF(#rep_id := rep_id, 1, 1),
#rn + 1) AS rn
FROM (
SELECT rep_id, datebegin, dateend
FROM mytable
GROUP BY rep_id, datebegin, dateend) AS t
CROSS JOIN (SELECT #rep_id := 0, #rn := 0) AS v
ORDER BY rep_id, datebegin
Output:
rep_id, datebegin, dateend, rn
-----------------------------------
1000, 2015-01-01, 2016-01-01, 1
1000, 2016-02-01, 2099-12-03, 2
You can use the above query as a derived table and join back to the original table. rn field is the group_id field you are looking for.
You can use variables to assign groups. As you said, only if the date_begin and date_end exactly match for 2 rows, they would be in the same group. Else a new group starts.
select rep_id,area,date_begin,date_end,
,case when #repid <> rep_id then #rn:=1 --reset the group to 1 when rep_id changes
when #repid=rep_id and #begin=date_begin and #end=date_end then #rn:=#rn --if rep_id,date_begin and date_end match use the same #rn previously assigned
else #rn:=#rn+1 --else increment #rn by 1
end as group_id
,#begin:=date_begin
,#end:=date_end
,#repid:=rep_id
from t
cross join (select #rn:=0,#begin:='',#end:='',#repid:=-1) r
order by rep_id,date_begin,date_end
The above query includes variables in the output. To only get the group_id use
select rep_id,area,date_begin,date_end,group_id
from (
select rep_id,area,date_begin,date_end
,case when #repid <> rep_id then #rn:=1
when #repid=rep_id and #begin=date_begin and #end=date_end then #rn:=#rn
else #rn:=#rn+1
end as group_id
,#begin:=date_begin
,#end:=date_end
,#repid:=rep_id
from t
cross join (select #rn:=0,#begin:='',#end:='',#repid:=-1) r
order by rep_id,date_begin,date_end
) x

Show in just one row all records that have the same id.

Lets say I have a table of product price history, which is the price and product id, with the following records:
id price
1 23
2 14
2 23
2 20
3 30
3 40
what I want is to show the data grouped by id, showing the prices at which has been sold each product.
What i expectis something like this:
id priceA PriceB PriceC
1 23 NULL NULL
2 14 23 20
3 30 40 NULL
This is not the right way to do things
you should use a separate table and try some primary keys.
suppose you have a poductprice table with id and price
make a view like
CREATE VIEW history AS (
SELECT
id,
CASE WHEN id = "1" THEN price END AS priceA,
CASE WHEN id = "2" THEN price END AS priceB,
CASE WHEN id = "3" THEN price END AS priceC
FROM productprice
);
SELECT * FROM history;
This requirement really a bad fit for SQL, but it can be achieved with a lot of fiddling involving "dynamic sql" and fudges to achieve te equivalent of row_number(). i.e. It would be easier to achieve with CTE and row_number() perhaps if MySQL gets bith this could be revisited.
Anyway, what is required is getting the prices into numbered columns, so the first price of each product goes in the first column, the second price in the second column and so on. So we need in the first instance a way to number the rows which will later be transformed into columns. In MySQL this can be done by using variables, like this:
select
#row_num := IF(#prev_value = p.id, #row_num+1, 1) AS RowNumber
, id
, price
, #prev_value := p.id
from (select distinct id, price from pricehistory) p
CROSS JOIN ( SELECT #row_num :=1, #prev_value :='' ) vars
order by id, price
So that snippet is used twice in the following. In the upper part it forms a set of case expressions that will do the transformation. I the lower part we combine those case expressions with the remainder of the wanted sql and then execute it.
set #sql = (
SELECT GROUP_CONCAT(col_ref)
FROM (
select distinct
concat(' max(case when RowNumber=',RowNumber,' then Price else NULL end) as c',RowNumber) col_ref
from (
select
#row_num := IF(#prev_value = p.id, #row_num+1, 1) AS RowNumber
, id
, price
, #prev_value := p.id
from (select distinct id, price from pricehistory) p
CROSS JOIN ( SELECT #row_num :=1, #prev_value :='' ) vars
order by id, price
) d
order by `RowNumber`
) dc
);
set #sql = concat('select id,', #sql,
' from (
select
#row_num := IF(#prev_value = p.id, #row_num+1, 1) AS RowNumber
, id
, price
, #prev_value := p.id
from (select distinct id, price from pricehistory) p
CROSS JOIN ( SELECT #row_num :=1, #prev_value :='''' ) vars
order by id, price
) d
Group By `id`');
#select #sql
PREPARE stmt FROM #sql;
EXECUTE stmt;
\\
The result of this, based on the sample given is:
id c1 c2 c3
1 1 23 NULL NULL
2 2 14 20 23
3 3 30 40 NULL
This solution can be tested and re-run at: http://rextester.com/AYAA36866
Note the fully generated sql reads like this:
select id
, max(case when RowNumber=1 then Price else NULL end) as c1
, max(case when RowNumber=2 then Price else NULL end) as c2
, max(case when RowNumber=3 then Price else NULL end) as c3
from (
select
#row_num := IF(#prev_value = p.id, #row_num+1, 1) AS RowNumber
, id
, price
, #prev_value := p.id
from (select distinct id, price from pricehistory) p
CROSS JOIN ( SELECT #row_num :=1, #prev_value :='' ) vars
order by id, price
) d
Group By `id`
You might want something like this:
SELECT id, GROUP_CONCAT(string SEPARATOR ' ') FROM priceHistory GROUP BY id;

Insert a new column based on existing column values

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;