Offset with Quantity in SQL - mysql

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;

Related

get the range of sequence values in table column

I have a list of value in my column. And want to query the range.
Eg. If values are 1,2,3,4,5,9,11,12,13,14,17,18,19
I want to display
1-5,9,11-14,17-19
Assuming that each value is stored on a separate row, you can use some gaps-and-island technique here:
select case when min(val) <> max(val)
then concat(min(val), '-', max(val))
else min(val)
end val_range
from (select val, row_number() over(order by val) rn from mytable) t
group by val - rn
order by min(val)
The idea is to build groups of consecutive values by taking the difference between the value and an incrementing rank, which is computed using row_number() (available in MySQL 8.0):
Demo on DB Fiddle:
| val_range |
| :-------- |
| 1-5 |
| 9 |
| 11-14 |
| 17-19 |
In earlier versions, you can emulate row_number() with a correlated subquery, or a user variable. The second option goes like:
select case when min(val) <> max(val)
then concat(min(val), '-', max(val))
else min(val)
end val_range
from (select #rn := 0) x
cross join (
select val, #rn := #rn + 1 rn
from (select val from mytable order by val) t
) t
group by val - rn
order by min(val)
As a complement to other answers:
select dn.val as dnval, min(up.val) as upval
from mytable up
join mytable dn
on dn.val <= up.val
where not exists (select 1 from mytable a where a.val = up.val + 1)
and not exists (select 1 from mytable b where b.val = dn.val - 1)
group by dn.val
order by dn.val;
1 5
9 9
11 14
17 19
Needless to say, but using an OLAP function like #GNB does, is orders of magnitude more efficient.
A short article on how to mimic OLAP functions in MySQL < 8 can be found at:
mysql-row_number
Fiddle
EDIT:
If another dimension is introduced (in this case p), something like:
select dn.p, dn.val as dnval, min(up.val) as upval
from mytable up
join mytable dn
on dn.val <= up.val
and dn.p = up.p
where not exists (select 1 from mytable a where a.val = up.val + 1 and a.p = up.p)
and not exists (select 1 from mytable b where b.val = dn.val - 1 and b.p = dn.p)
group by dn.p, dn.val
order by dn.p, dn.val;
can be used, see Fiddle2

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;

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 would I return the result of SQL math operations?

So I was taking a test recently with some higher level SQL problems. I only have what I would consider "intermediate" experience in SQL and I've been working on this for a day or so now. I just can't figure it out.
Here's the problem:
You have a table with 4 columns as such:
EmployeeID int unique
EmployeeType int
EmployeeSalary int
Created date
Goal: I need to retrieve the difference between the latest two EmployeeSalary for any EmployeeType with more than 1 entry. It has to be done in one statement (nested queries are fine).
Example Data Set: http://sqlfiddle.com/#!9/0dfc7
EmployeeID | EmployeeType | EmployeeSalary | Created
-----------|--------------|----------------|--------------------
1 | 53 | 50 | 2015-11-15 00:00:00
2 | 66 | 20 | 2014-11-11 04:20:23
3 | 66 | 30 | 2015-11-03 08:26:21
4 | 66 | 10 | 2013-11-02 11:32:47
5 | 78 | 70 | 2009-11-08 04:47:47
6 | 78 | 45 | 2006-11-01 04:42:55
So for this data set, the proper return would be:
EmployeeType | EmployeeSalary
-------------|---------------
66 | 10
78 | 25
The 10 comes from subtracting the latest two EmployeeSalary values (30 - 20) for the EmployeeType of 66. The 25 comes from subtracting the latest two EmployeeSalary values (70-45) for EmployeeType of 78. We skip EmployeeID 53 completely because it only has one value.
This one has been destroying my brain. Any clues?
Thanks!
How to make really simple query complex?
One funny way(not best performance) to do it is:
SELECT final.EmployeeType, SUM(salary) AS difference
FROM (
SELECT b.EmployeeType, b.EmployeeSalary AS salary
FROM tab b
JOIN (SELECT EmployeeType, GROUP_CONCAT(EmployeeSalary ORDER BY Created DESC) AS c
FROM tab
GROUP BY EmployeeType
HAVING COUNT(*) > 1) AS sub
ON b.EmployeeType = sub.EmployeeType
AND FIND_IN_SET(b.EmployeeSalary, sub.c) = 1
UNION ALL
SELECT b.EmployeeType, -b.EmployeeSalary AS salary
FROM tab b
JOIN (SELECT EmployeeType, GROUP_CONCAT(EmployeeSalary ORDER BY Created DESC) AS c
FROM tab
GROUP BY EmployeeType
HAVING COUNT(*) > 1) AS sub
ON b.EmployeeType = sub.EmployeeType
AND FIND_IN_SET(b.EmployeeSalary, sub.c) = 2
) AS final
GROUP BY final.EmployeeType;
SqlFiddleDemo
EDIT:
The keypoint is MySQL doesn't support windowed function so you need to use equivalent code:
For example solution in SQL Server:
SELECT EmployeeType, SUM(CASE rn WHEN 1 THEN EmployeeSalary
ELSE -EmployeeSalary END) AS difference
FROM (SELECT *,
ROW_NUMBER() OVER(PARTITION BY EmployeeType ORDER BY Created DESC) AS rn
FROM #tab
) AS sub
WHERE rn IN (1,2)
GROUP BY EmployeeType
HAVING COUNT(EmployeeType) > 1
LiveDemo
And MySQL equivalent:
SELECT EmployeeType, SUM(CASE rn WHEN 1 THEN EmployeeSalary
ELSE -EmployeeSalary END) AS difference
FROM (
SELECT t1.EmployeeType, t1.EmployeeSalary,
count(t2.Created) + 1 as rn
FROM #tab t1
LEFT JOIN #tab t2
ON t1.EmployeeType = t2.EmployeeType
AND t1.Created < t2.Created
GROUP BY t1.EmployeeType, t1.EmployeeSalary
) AS sub
WHERE rn IN (1,2)
GROUP BY EmployeeType
HAVING COUNT(EmployeeType) > 1;
LiveDemo2
The dataset of the fiddle is different from the example above, which is confusing (not to mention a little perverse). Anyway, there's lots of ways to skin this particular cat. Here's one (not the fastest, however):
SELECT a.employeetype, ABS(a.employeesalary-b.employeesalary) diff
FROM
( SELECT x.*
, COUNT(*) rank
FROM employees x
JOIN employees y
ON y.employeetype = x.employeetype
AND y.created >= x.created
GROUP
BY x.employeetype
, x.created
) a
JOIN
( SELECT x.*
, COUNT(*) rank
FROM employees x
JOIN employees y
ON y.employeetype = x.employeetype
AND y.created >= x.created
GROUP
BY x.employeetype
, x.created
) b
ON b.employeetype = a.employeetype
AND b.rank = a.rank+1
WHERE a.rank = 1;
a very similar but faster solution looks like this (although you sometimes need to assign different variables between tables a and b - for reasons I still don't fully understand)...
SELECT a.employeetype
, ABS(a.employeesalary-b.employeesalary) diff
FROM
( SELECT x.*
, CASE WHEN #prev = x.employeetype THEN #i:=#i+1 ELSE #i:=1 END i
, #prev := x.employeetype prev
FROM employees x
, (SELECT #prev := 0, #i:=1) vars
ORDER
BY x.employeetype
, x.created DESC
) a
JOIN
( SELECT x.*
, CASE WHEN #prev = x.employeetype THEN #i:=#i+1 ELSE #i:=1 END i
, #prev := x.employeetype prev
FROM employees x
, (SELECT #prev := 0, #i:=1) vars
ORDER
BY x.employeetype
, x.created DESC
) b
ON b.employeetype = a.employeetype
AND b.i = a.i + 1
WHERE a.i = 1;

Trying to use ID in MySQL SubSubQuery

So I'll show you what I'm trying to do and explain my problem, there may be an answer different to the approach I'm trying to take.
The query I'm trying to perform is as follows:
SELECT *
FROM report_keywords rk
WHERE rk.report_id = 231
AND (
SELECT SUM(t.conv) FROM (
SELECT conv FROM report_keywords t2 WHERE t2.campaign_id = rk.campaign_id ORDER BY conv DESC LIMIT 10
) t
) >= 30
GROUP BY rk.campaign_id
The error I get is
Unknown column 'rk.campaign_id' in 'where clause'
Obviously this is saying that the table alias rk is not making it to the subsubquery. What I'm trying to do is get all of the campaigns where the sum of the top 10 conversions is greater than or equal to 30.
The relevant table structure is:
id INT,
report_id INT,
campaign_id INT,
conv INT
Any help would be greatly appreciated.
Update
Thanks to Kickstart I was able to do what I wanted. Here's my final query:
SELECT campaign_id, SUM(conv) as sum_conv
FROM (
SELECT campaign_id, conv, #Sequence := if(campaign_id = #campaign_id, #Sequence + 1, 1) AS aSequence, #campaign_id := campaign_id
FROM report_keywords
CROSS JOIN (SELECT #Sequence := 0, #campaign_id := 0) Sub1
WHERE report_id = 231
ORDER BY campaign_id, conv DESC
) t
WHERE aSequence <= 10
GROUP BY campaign_id
HAVING sum_conv >= 30
Possibly use a user variable to add a sequence number to get the latest 10 records for each one, then use SUM to get the count of those.
Something like this:-
SELECT rk.*
FROM report_keywords rk
INNER JOIN
(
SELECT campaign_id, SUM(conv) AS SumConv
FROM
(
SELECT campaign_id, conv, #Sequence := if(campaign_id = #campaign_id, #Sequence + 1, 1) AS aSequence, #campaign_id := campaign_id
FROM report_keywords
CROSS JOIN (SELECT #Sequence := 0, #campaign_id := "") Sub1
ORDER BY campaign_id, conv
) Sub2
WHERE aSequence <= 10
GROUP BY campaign_id
) Sub3
ON rk.campaign_id = Sub3.campaign_id AND Sub3.SumConv >= 30
WHERE rk.report_id = 231