SQL calculation based on different conditions - mysql

I have one table and trying to subtract the total where a condition is True from the full total.
Ticket
Amount
Code
11
5.00
12
3.00
X
13
10.00
14
2.00
X
My query was
SELECT SUM(AMOUNT)
FROM Table
MINUS
SELECT SUM(Amount)
FROM Table
WHERE Code = 'X'
So the answer should be 20 - 5= 15

Below two possible queries:
-- Use IF operator
SELECT SUM(amount) - SUM(IF(code = 'X', amount, 0)) FROM tbl;
-- Use implicit MySQL conversion boolean to int (true => 1)
SELECT SUM(amount) - SUM(amount * (code = 'X')) FROM tbl;
SQL editor online

Related

Query for finding latest record in sql

Given a currency table I need to find the latest record of conversion rate which is less than given particular date
Input table structure given below:
id
baseCur
Curr
rate
date
1
INR
USD
81
2022-11-09
2
INR
USD
82
2022-11-08
3
INR
USD
80
2022-11-06
4
INR
CAD
56
2022-11-05
5
INR
RUB
.74
2022-11-04
6
INR
CAD
57
2022-11-12
Problem statement:
I need to find all latest currencies rate that is less than 2022-11-09.On any given date there will be only conversation rate for any particular currency
so expected output
id
baseCur
Curr
rate
date
2
INR
USD
82
2022-11-08
4
INR
CAD
56
2022-11-05
5
INR
RUB
.74
2022-11-04
Explanantion of output :
Id 1,6 rejected : cause they are greater than 2022-11-09 date
Id 3 rejected cause we have one more record for INR to CAD in row 2 and its date is more new to Id 3
You can use a window function such as DENSE_RANK() if DB version is 8.0+ in order to determine the latest records by using the query below
WITH t AS
(
SELECT t.*, DENSE_RANK() OVER (PARTITION BY baseCur, Curr ORDER BY date DESC) AS dr
FROM t
WHERE date < '2022-11-09'
)
SELECT id, baseCur, Curr, rate, date
FROM t
WHERE dr = 1
But, notice that this query returns the ties(equal date values) as well if there is any.
Demo
Beside the option to use a window function for that, you could also use a subquery. In the subquery, you will catch every currency with the latest date:
SELECT
curr, MAX(yourdate) maxDate
FROM yourtable
WHERE yourdate < '2022-11-09'
GROUP BY curr;
This query will produce this outcome:
Curr
maxDate
2
2022-11-08
4
2022-11-05
5
2022-11-04
This result can be used by applying a JOIN clause or IN clause from a main query.
This will add the other columns.
SELECT y.id, y.baseCur, y.curr, y.rate, y.yourdate
FROM yourtable y
JOIN (SELECT
curr, MAX(yourdate) maxDate
FROM yourtable
WHERE yourdate < '2022-11-09'
GROUP BY curr) maxDates
ON y.curr = maxDates.curr
AND y.yourdate = maxDates.maxDate
ORDER BY id;
Thus, the complete intended result will be created:
id
baseCur
Curr
rate
date
2
INR
USD
82
2022-11-08
4
INR
CAD
56
2022-11-05
5
INR
RUB
.74
2022-11-04
To point that out: I think using a window function should be prefered if possible.
They just have the "disadvantage" older DB's don't provide them and they often differ depending on the DB type.
So, if a query is required that works always on each DB type and DB version, this way of using a subquery becomes helpful.
You can fetch the desired output using subquery, as shown below, which fetches latest record from each currency.
-- 1. Based on id column
SELECT * FROM sometable as t WHERE t.id =
(SELECT MAX(id) FROM sometable WHERE Curr = t.Curr and date < '2022-11-09');
-- 2. Based on date column
SELECT * FROM sometable as t WHERE t.date =
(SELECT MAX(date) FROM sometable WHERE Curr = t.Curr and date < '2022-11-09');

Is it possible to run calculations while aggregating in Google BigQuery

In StandardSQL, is it possible to run operations on each row during the grouping process? I'm not sure if I'm even asking the right question. Here's an example.
Let's say I have 3 rows like this:
| move_id | item_id | quantity | value |
|---------|---------|----------|-------|
| 1 | 1 | 10 | 100 |
| 1 | 2 | 20 | 150 |
| 1 | 3 | 30 | 200 |
I now want to group the table by move_id, summing values based on the proportion of each row's quantity to the minimum quantity.
For example the minimum quantity is 10, and row 2 has a quantity of 20, which means it's value should be cut in half before summing. Row 3 has a quantity of 30, which means it's value should be cut to a third before summing.
So my final value column should be 100 + (150 / 2) + (200 / 3) = 241.67.
My result should be:
| move_id | quantity | value |
|---------|----------|--------|
| 1 | 10 | 241.67 |
The query should be something like:
SELECT ANY_VALUE(move_id) AS move_id, MIN(quantity) AS quantity, SUM([THIS IS MY QUESTION, WHAT GOES HERE?]) as value FROM table GROUP BY move_id;
Is this possible?
Below is for BigQuery Standard SQL and does all in one shot
#standardSQL
SELECT move_id,
MIN(quantity) AS quantity,
SUM(value/quantity) * MIN(quantity) AS value
FROM `project.dataset.table`
GROUP BY move_id
If to apply to sample data from your question - result is
Row move_id quantity value
1 1 10 241.66666666666669
As you can see here - instead of splitting calculation/aggregation inside query, you can rather transform your formula such like below
100 + (150 / 2) + (200 / 3)
(100 * 10 / 10 + (150 * 10 / 20) + (200 * 10 / 30)
((100 / 10 + (150 / 20) + (200 / 30)) * 10
SUM(value / quantity) * MIN(quantity)
so, you ended up with just simple aggregation "in one shot"
The somewhat difficult part of your query is that you want to aggregate, but the sum you have in mind itself requires the result of an aggregation - the minimum quantity per each move_id group. One option here would be to first generate the min quantity in a CTE, then aggregate that CTE using your logic.
WITH cte AS (
SELECT *, MIN(quantity) OVER (PARTITION BY move_id) min_quantity
FROM yourTable
)
SELECT
move_id,
MIN(quantity) AS quantity,
SUM(value * min_quantity / quantity) AS value
FROM cte
GROUP BY
move_id;
Demo
Note: The above demo uses SQL Server, but the SQL used is ANSI compliant, and should also run on BigQuery without any issues.
Also, if your version of BigQuery does not support cte, then you may just inline the code contained inside the CTE as as a subquery.
In absence of CTEs, you can use Derived Table (subquery) to fetch minimum quantity for every move_id separately. And, then utilize them in the main query, to compute the sum:
SELECT t.move_id,
dt.min_quantity,
Sum(t.value / ( t.quantity / dt.min_quantity )) AS value
FROM your_table AS t
JOIN (SELECT move_id,
Min(quantity) AS min_quantity
FROM your_table
GROUP BY move_id) AS dt
ON dt.move_id = t.move_id
GROUP BY t.move_id
SQL Fiddle Demo

Running total with condition and always looking at the previous value

I want to do a sequential sum in a table by taking into consideration a few extra conditions.
We need to make sure that when the sum is taken sequentially so if a id has +40 then the next sum would be 130, if the next one is +1, the sum is still 130, now if the next one is -1 then the sum has to be 129.
100 needs to be added to the sum for the first time and from there on just the count should be added depending on condition.
We need to even cap the min value of sum so it can't be less than 70
I have tried the query below but it does not seem to look at the prior value.
Example that I tried:
create table tableA (id int not null, count int not null);
insert into tableA(id, count) values(1,11), (2,21),(3, -3); -- case 1
insert into tableA(id, count) values(1,35), (2,-3); -- case 2
insert into tableA(id, count) values(1,-45),(2,67); -- case3
Query tried:
select t.id, t.count,
case when (100+(select ifnull(sum(count),0) from tableA x where x.id <= t.id)) >= 130 then 130
when (100+(select ifnull(sum(count),0) from tableA x where x.id <= t.id)) <= 70 then 70
else (100+(select ifnull(sum(count),0) from tableA x where x.id <= t.id))
end as xxxx
from tableA t;
I expect my output to look like:
Case1 Result:
id count Sum
1 11 111
2 21 130
3 -4 126
Case2 Result:
id count Sum
1 35 130
2 -3 127
Case3 Result:
id count Sum
1 -45 70
2 67 137
THIS ANSWERS THE ORIGINAL VERSION OF THE QUESTION.
I think this does what you want:
select a.*, (#sum := least(#sum + count, 130)) as "sum"
from (select a.*
from tablea a
order by a.id
) a cross join
(select #sum := 0) params;
I don't understand where the 100 is coming from. It is not part of your explanation.
Here is a db<>fiddle that illustrates how this works using 30 as the limit (which seems to be your intention).

How to find a best of 2 in 3 column and average of those 2

In my database I have a table analog with columns like id, name, IA1, IA2, IA3 and average.
In the average column I need to set the average of the highest two values in IA1, IA2 and IA3.
For example:
anil has 20, 15 and 28 in IA1, IA2, IA3 respectively so average will be (20+28)/2 i.e. 24
ashwin has 21, 30 and 28 in IA1, IA2, IA3 respectively so average will be (30+28)/2 i.e. 29
I tried this query but I got only the greatest of those values:
update analog set average = greatest (IA1,IA2,IA3);
Please help
Since you have only three values, you can subtract the LEAST of the 3 from the sum of all of them and then divide that by 2 to get the average of the two largest values:
UPDATE analog
SET average = (IA1 + IA2 + IA3 - LEAST(IA1, IA2, IA3)) / 2
Demo on dbfiddle
Update
#GordonLinoff raises a good point that if it's possible that any of the IA* values could be NULL, you can use COALESCE to convert them to a valid integer (0 is probably the most appropriate assuming that the values would normally be positive) so that you don't end up with NULL values in the average column:
UPDATE analog
SET average = (COALESCE(IA1,0) + COALESCE(IA2,0) + COALESCE(IA3,0) -
LEAST(COALESCE(IA1,0), COALESCE(IA2,0), COALESCE(IA3,0))) / 2
Demo on dbfiddle
if you have only 3 values then you could try using a union for the 3 columns and
update analog a
inner join (
select id, (sum(IA) - min(IA))/2 val
from (
select id, IA1 IA
from analog union all
select id, IA2
from analog union all
select id, IA3
from analog
) t1
group by id
) t2 ON a.id = t2.id
set a.average = t2.val

SQL Sum Row number until condition is met

so in my database i have two rows: Barcode, Profit ;
They are in an descending order according to profit, eg:
Barcode , Profit:
101 , 10000
106 , 9999
107 , 8888
108 , 222
i need to sql query which will do the following:
I need to sum all the profit then look at its %80 value and then start summing barcode values until %80 is met, eg:
Barcode , Profit:
101 , 10000
106 , 9999
107 , 8888
108 , 222
10000+9999+8888+222= 29109
29109 is the total sum and its %80 is = 23 287,2
since 10000+9999+8888 does contain the %80the result should return:
Barcode
101
106
107
You can do this using variables:
select t.*
from (select t.*, (#sump := #sump + profit) as running_profit
from t cross join
(select #sump := 0) params
order by profit desc
) t
where running_profit < 0.8 * #sump;
The inner query calculates the cumulative profit. As a side effect, it also calculates the total sum of the profit.
The outer where select all rows up to the first row that exceeds the 80% threshold.