How to do an arithmetic operation with aliased column in SQL - mysql

I have a databse table as like below:
id received_by sent_by amount product_id
1 1 2 10 1
2 1 3 12 1
3 2 1 5 1
4 3 1 8 2
Here, received_by and sent_by are two user ID those who are receiving and sending the product respectively. I want to calculate the total amount of each product of a single user by subtracting the sent amount from received amount.
My current query looks like below:
select
product_id,
(received - sent) as quantity,
case(when received_by = 1 then amount end) as received,
case(when sent_by = 1 then amount end) as sent
group by
product_id;
Here I get an error that Unknown column 'received' in 'field list'.
How can I calculate each users inventory/stock?

You can't use the calculated columns in the SELECT list.
Also you need the aggregate function SUM().
One way to do it is with a subquery:
select *, (received - sent) as quantity
from (
select product_id,
sum(case when received_by = 1 then amount else 0 end) as received,
sum(case when sent_by = 1 then amount else 0 end) as sent
from tablename
where 1 in (received_by, sent_by)
group by product_id
) t
Or:
select product_id,
sum(case when received_by = 1 then amount else -amount end) as quantity,
sum(case when received_by = 1 then amount else 0 end) as received,
sum(case when sent_by = 1 then amount else 0 end) as sent
from tablename
where 1 in (received_by, sent_by)
group by product_id
See the demo.

First of all you missed the from clause in your query before group by. Secondly you cannot use column aliases (received, sent) in same select statement.
create table mytable(id int, received_by int, sent_by int, amount int, product_id int);
insert into mytable values(1, 1, 2, 10, 1);
insert into mytable values(2, 1, 3, 12, 1);
insert into mytable values(3, 2, 1, 5, 1);
insert into mytable values(4, 3, 1, 8, 2);
Query:
select product_id, (coalesce(received,0)-coalesce(sent,0)) as Quantity, coalesce(received,0) received,coalesce(sent)sent
from
( select
product_id,
sum(case when received_by = 1 then amount end) as received,
sum(case when sent_by = 1 then amount end) as sent
from mytable
group by
product_id
)t;
Output:
|product_id | Quantity | received | sent|
|-----------|----------|----------|-----|
| 1 | 17 | 22 | 5|
| 2 | -8 | 0 | 8|
db<>fiddle here

Related

Count Distinct from SQL Aggregation

I have a table that looks like this:
store_id cust_id amount indicator
1 1000 2.05 A
1 1000 3.10 A
1 2000 3.10 A
2 1000 5.10 B
2 2000 6.00 B
2 1000 1.05 A
What I'm trying to do is find the percent of sales with indicators A, B for each store by only looking at unique customer IDs (i.e., the two sales to customer 1000 at store 1 would only count once). Something like this:
store_id pct_sales_A pct_sales_B pct_sales_AB
1 1.0 0.00 0.00
2 0.0 0.50 0.50
I know that I can use a subquery to find the counts of each transaction type, but I'm having trouble only counting the distinct customer IDs. Here's an (incorrect) approach for the pct_sales_A column:
SELECT
store_id,
COUNT(DISTINCT(CASE WHEN txns_A>0 AND txns_B=0 THEN cust_ID ELSE NULL))/COUNT(*) AS pct_sales_A --this is wrong
FROM (SELECT store_id, cust_id,
COUNT(CASE WHEN indicator='A' THEN amount ELSE 0 END) as txns_A,
COUNT(CASE WHEN indicator='B' THEN amount ELSE 0 END) as txns_B
FROM t1
GROUP BY store_id, cust_id
)
GROUP BY store_id;
You can use conditional aggregation with COUNT(DISTINCT):
SELECT store_id,
COUNT(DISTINCT CASE WHEN indicator = 'A' THEN cust_id END) * 1.0 / COUNT(DISTINCT cust_id) as ratio_a,
COUNT(DISTINCT CASE WHEN indicator = 'B' THEN cust_id END) * 1.0 / COUNT(DISTINCT cust_id) as ratio_a,
FROM t1
GROUP BY store_id;
Based on your comment, you need two levels of aggregation:
SELECT store_id,
AVG(has_a) as ratio_a,
AVG(has_b) as ratio_b,
AVG(has_a * has_b) as ratio_ab
FROM (SELECT store_id, cust_id,
MAX(CASE WHEN indicator = 'A' THEN 1.0 ELSE 0 END) as has_a,
MAX(CASE WHEN indicator = 'B' THEN 1.0 ELSE 0 END) as has_b
FROM t1
GROUP BY store_id, cust_id
) sc
GROUP BY store_id;
I think you want two levels of conditional aggregation:
select
store_id,
avg(has_a = 1 and has_b = 0) pct_sales_a,
avg(has_a = 0 and has_b = 1) pct_sales_b,
avg(has_a + has_b = 2) pct_sales_ab
from (
select
store_id,
cust_id,
max(indicator = 'A') has_a,
max(indicator = 'B') has_b
from t1
group by store_id, cust_id
) t
group by store_id
Demo on DB Fiddle:
store_id | pct_sales_a | pct_sales_b | pct_sales_ab
-------: | ----------: | ----------: | -----------:
1 | 1.0000 | 0.0000 | 0.0000
2 | 0.0000 | 0.5000 | 0.5000

SQL counting occurrences of conditional statements using values from 2 different roles

I have a table with fields including time (UTC) and accountID.
accountID | time | ...
1 |12:00 |....
1 |12:01 |...
1 |13:00 |...
2 |14:00 |...
I need to make an sql query to return the accountID with a new field counting 'category' where 'category' can be 'a' or 'b'. If there is a row entry from the same accountID that has a positive time difference of 1 minute or less, category 'a' needs to be incremented, otherwise 'b'. The results from the above table would be
accountID| cat a count| cat b count
1 | 1 | 2
2 | 0 | 1
What approaches can I take to compare values between different rows and output occurrences of comparison outcomes?
Thanks
To compute this categories you'll need to pre-compute the findings of close rows in a "table expression". For example:
select
accountid,
sum(case when cnt > 0 then 1 else 0 end) as cat_a_count,
sum(case when cnt = 0 then 1 else 0 end) as cat_b_count
from (
select
accountid, tim,
( select count(*)
from t b
where b.accountid = t.accountid
and b.tim <> t.tim
and b.tim between t.tim and addtime(t.tim, '00:01:00')
) as cnt
from t
) x
group by accountid
Result:
accountid cat_a_count cat_b_count
--------- ----------- -----------
1 1 2
2 0 1
For reference, the data script I used is:
create table t (
accountid int,
tim time
);
insert into t (accountid, tim) values
(1, '12:00'),
(1, '12:01'),
(1, '13:00'),
(2, '14:00');
Use lag() and conditional aggregation:
select accountid,
sum(prev_time >= time - interval 1 minute) as a_count,
sum(prev_time < time - interval 1 minute or prev_time is null) as b_count
from (select t.*,
lag(time) over (partition by accountid order by time) as prev_time
from t
) t
group by accountid;

SUM values or take MAX to SUM depending on value

I have a query to select some shippingcost and I want to sum them up in a special way.
Sample Data:
supplierID | articleID | sumUP | shippingCost
10 | 100 | 1 | 20
10 | 101 | 1 | 15
20 | 200 | 0 | 15
20 | 201 | 0 | 10
30 | 300 | 0 | 10
=============================================
Sum should be: 60
What I want to achive is to sum up all shippingCost values, but since sumUP on supplierID 20 and 30 is 0, i just want to have the maximum value of these suppliers.
so
supplier 10 should have 35 (sum of values)
supplier 20 should have 15 (maximum value)
supplier 30 should have 10 (maximum value)
in sum it should be 60.
I tried a lot of complex querys but always got stuck when I want to decide to sum or take max and sum all afterwards.
Is this even possible with a mysql statement? (of course subquerys in it).
Any suggestions how to solve this?
First group by supplierid to get the sum and the max of shippingcost for each supplier and then use conditional aggregation on the results:
select
sum((t.sumup = 0) * maxshippingcost + (t.sumup = 1) * sumshippingcost) total
from (
select supplierid,
max(sumup) sumup,
max(shippingcost) maxshippingcost,
sum(shippingcost) sumshippingcost
from tablename
group by supplierid
) t
See the demo.
Or with a CASE expression:
select
sum(
case t.sumup
when 0 then maxshippingcost
when 1 then sumshippingcost
end
) total
from (
select supplierid,
max(sumup) sumup,
max(shippingcost) maxshippingcost,
sum(shippingcost) sumshippingcost
from tablename
group by supplierid
) t
See the demo.
Use a case expression to either return the SUM() or the MAX():
select supplierID,
case when max(sumUP) = 1 then sum(shippingCost) else max(shippingCost) end
from tablename
group by supplierID
EDIT BY Dwza
As forpas mentioned, this statement just gives me the result that needs to be summed up. The total statement could look like:
select sum(my.result) from
(select supplierID,
case when max(sumUP) = 1 then sum(shippingCost) else max(shippingCost) end as result
from tablename
group by supplierID) as my
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(supplier_ID INT NOT NULL
,articleID INT NOT NULL PRIMARY KEY
,sum_UP INT NOT NULL
,shippingCost INT NOT NULL
);
INSERT INTO my_table VALUES
(10,100,1,20),
(10,101,1,15),
(20,200,0,15),
(20,201,0,10),
(30,300,0,10);
SELECT SUM(x) total
FROM
( SELECT supplier_id, MAX(shippingcost) x FROM my_table WHERE sum_up = 0 GROUP BY supplier_id
UNION
SELECT supplier_id, shippingcost FROM my_table WHERE sum_up = 1
) a;
+-------+
| total |
+-------+
| 60 |
+-------+
I use case and group by
select supplier_id,
case
when sum_up = 0
then max(shipping_cost)
when sum_up = 1
then sum(shipping_cost) end as total
from table_name
group by supplier_id, sum_up;
The result as follows:
supplier_id, sum_up
20 15
10 35
30 10
Now, I can sum it
select sum(total)
from (
select supplier_id,
case
when sum_up = 0
then max(shipping_cost)
when sum_up = 1
then sum(shipping_cost) end as total
from cd.sample
group by supplier_id, sum_up
) a;
SELECT sum(A.SumShipping) as TotalSum
FROM (SELECT supplierID, if(sumup = 1, sum(shippingcost), max(shippingcost))
as SumShipping FROM tablename group by supplierID) as A;

SQL Counting rows with columns that satisfy a condition

Suppose I have a table with columns ID and Content populated with data.
ID | Content
1 | a
1 | b
1 | c
2 | b
2 | a
3 | b
I want to find every ID that has at least one of each 'a', 'b', and 'c' so the returned table would be :
ID | Content
1 | a
1 | b
1 | c
Using conditional SUM validate if inside the group id exist 1 or more of each element.
Then select DISTINCT ID, Content to eliminate possible duplicates
SELECT DISTINCT ID, Content
From YourTable
WHERE ID IN (SELECT ID
FROM YourTable
GROUP BY ID
HAVING SUM(case when Content = 'a' then 1 else 0 end) >= 1
AND SUM(case when Content = 'b' then 1 else 0 end) >= 1
AND SUM(case when Content = 'c' then 1 else 0 end) >= 1
)
select * from (
where
Select ID ,SUM(case when Content = 'a' then 1 else 0 end) as sum_a ,UM(case when Content = 'b' then 1 else 0 end) as sum_b,
SUM(case when Content = 'c' then 1 else 0 end) as sum_c
FROM table
Group by ID)x
where x.sum_a >1 or sum_b>1 or sum_c>1
Here is another approach that checks for a certain number of distinct "content" items per ID. Here is a link to an SQL Fiddle for this solution.
select
id,
count(distinct(content))
from tableX
group by id
having count(distinct(content)) = 3;

How to get count of unique values in a column group by the primary key

I am new to sql and i have this problem in hand.
I have a table temp which has id and flag as its columns.
ID FLAG
-- ----
A 1
A 1
A 0
B 1
B 0
B 0
C 0
C 0
C 0
I need the 1's and 0's count with respect to each ID.
The desired output is
ID OnesCount ZerosCount
--- --------- ----------
A 2 1
B 1 2
C 0 3
I tried a lot i can get them individually by
select id,count(*) ZerosCount from temp where flag = 0 group by id
select id,count(*) OnesCount from temp where flag = 1 group by id
But do not understand how to join and get the desired output.
Can some one please help
In this specific case you can do like this:
select customer_id ID,
sum(pwr_flag) OnesCount,
sum(1-pwr_flag) ZerosCount
from temp_pwr
group by customer_id
In a more generic case you can use case when:
select customer_id ID,
sum(case pwr_flag when 1 then 1 else 0 end) OnesCount,
sum(case pwr_flag when 0 then 1 else 0 end) ZerosCount
sum(case pwr_flag when 17 then 1 else 0 end) SeventeensCount
from temp_pwr
group by customer_id
select customer_id,
count(case when pwr_flag = 0 then 1 end) ZerosCount,
count(case when pwr_flag = 1 then 1 end) OnessCount
from temp_pwr
group by customer_id