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

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;

Related

How to do an arithmetic operation with aliased column in SQL

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

Sum hours value, count and display based on hours using SQL

I have 2 tables which are Teacher and Activities.
CREATE TABLE teacher (
TeacherId INT, BranchId VARCHAR(5));
INSERT INTO teacher VALUES
("1121","A"),
("1132","A"),
("1141","A"),
("2120","B"),
("2122","B");
CREATE TABLE activities (
ID INT, TeacherID INT, Hours INT);
INSERT INTO activities VALUES
(1,1121,2),
(2,1121,1),
(3,1132,1),
(4,1141,NULL),
(5,2120,NULL),
(6,2122,NULL);
NULL indicates no activities and will be convert to 0 on output table. I want to produce a query to count total of hours and count how many activities base on teacher hours such as the following table:
+-----------+------------+------------+
| Hours | A | B |
+-----------+------------+------------+
| 0 | 1 | 2 |
| 1 | 1 | 0 |
| 2 | 0 | 0 |
| 3 | 1 | 0 |
+-----------+------------+------------+
Edited: Sorry I don't know how to elaborate accurately, but here is the fiddle i received from other member https://www.db-fiddle.com/f/mmtuZquKyUqdhPvTFN9qaF/1
Edit: Last, modification need, to sum the hours and count the hours base on branch id and teacher id as the output.
Expected output here (red text): https://drive.google.com/file/d/1wyZ_aX5hz_7I1Ncf5sXLpstYk6FT8PMg/view?usp=sharing
We can handle this via the use of a calendar table of hours joined to an aggregation subquery:
SELECT
t1.Hours,
SUM(CASE WHEN t2.BranchId = 'A' THEN t2.cnt ELSE 0 END) AS A,
SUM(CASE WHEN t2.BranchId = 'B' THEN t2.cnt ELSE 0 END) AS B
FROM (SELECT 0 AS Hours UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t1
LEFT JOIN
(
SELECT t.BranchId, COALESCE(a.Hours, 0) AS Hours, COUNT(*) AS cnt
FROM Teacher t
LEFT JOIN Activities a ON a.TeacherId = t.TeacherId
GROUP BY t.BranchId, COALESCE(a.Hours, 0)
) t2
ON t1.Hours = t2.Hours
GROUP BY
t1.Hours
ORDER BY
t1.Hours
Demo
This is basically a JOIN and aggregation . . . but you need to start with all the hours you want:
SELECT h.Hours,
COALESCE(SUM(t.BranchId = 'A'), 0) AS A,
COALESCE(SUM(t.BranchId = 'B'), 0) AS B
FROM (SELECT 0 AS Hours UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
) h LEFT JOIN
activities a
ON h.hours = COALESCE(a.hours, 0) LEFT JOIN
teacher t
ON t.TeacherId = a.TeacherId
GROUP BY h.Hours
ORDER BY h.Hours;
Here is a db<>fiddle.

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;

Select duplicates while concatenating every one except the first

I am trying to write a query that will select all of the numbers in my table, but those numbers with duplicates i want to append something on the end that shows it as a duplicate. However I am not sure how to do this.
Here is an example of the table
TableA
ID Number
1 1
2 2
3 2
4 3
5 4
SELECT statement output would be like this.
Number
1
2
2-dup
3
4
Any insight on this would be appreciated.
if you mysql version didn't support window function. you can try to write a subquery to make row_number then use CASE WHEN to judgement rn > 1 then mark dup.
create table T (ID int, Number int);
INSERT INTO T VALUES (1,1);
INSERT INTO T VALUES (2,2);
INSERT INTO T VALUES (3,2);
INSERT INTO T VALUES (4,3);
INSERT INTO T VALUES (5,4);
Query 1:
select t1.id,
(CASE WHEN rn > 1 then CONCAT(Number,'-dup') ELSE Number END) Number
from (
SELECT *,(SELECT COUNT(*)
FROM T tt
where tt.Number = t1.Number and tt.id <= t1.id
) rn
FROM T t1
)t1
Results:
| id | Number |
|----|--------|
| 1 | 1 |
| 2 | 2 |
| 3 | 2-dup |
| 4 | 3 |
| 5 | 4 |
If you can use window function you can use row_number with window function to make rownumber by Number.
select t1.id,
(CASE WHEN rn > 1 then CONCAT(Number,'-dup') ELSE Number END) Number
from (
SELECT *,row_number() over(partition by Number order by id) rn
FROM T t1
)t1
sqlfiddle
I made a list of all the IDs that weren't dups (left join select) and then compared them to the entire list(case when):
select
case when a.id <> b.min_id then cast(a.Number as varchar(6)) + '-dup' else cast(a.Number as varchar(6)) end as Number
from table_a
left join (select MIN(b.id) min_id, Number from table_a b group by b.number)b on b.number = a.number
I did this in MS SQL 2016, hope it works for you.
This creates the table used:
insert into table_a (ID, Number)
select 1,1
union all
select 2,2
union all
select 3,2
union all
select 4,3
union all
select 5,4

Aggregate rows if one row meets a specific condition

I'm trying to select the sum of the values in the isOK column for each Name separated, BUT only if isOK = 1 on Day = 2.
The query for the following example table tablename
Name | Day | isOK
char | int | int
-----------------
Flo | 1 | 1
Seb | 1 | 1
Tim | 1 | 0
Flo | 2 | 1
Seb | 2 | 0
Tim | 2 | 1
should give Flo: 2 and Tim: 1, but not Seb: 1, since his isOK on Day = 2 is 0.
I've tried using SUM(isOK) with IF constructs, but it's just not working. My alternative solution, to select all Name where isOK = 1 first and select the SUM(isOK) for each of the names is slow and seems in need of improvement.
I guess it's not that difficult, but I've been trying for hours now and I just can't combine my two queries into one.
One way to do this is to use a conditional expression together with a having clause like this:
select name, sum(isOk) ok_sum
from your_table
group by name
having sum(case when day = 2 and isOK = 1 then 1 else 0 end) > 0;
With your sample data the result would be:
name ok_sum
Flo 2
Tim 1
As MySQL evaluates boolean expressions as 1 or 0 it should be possible to reduce the condition to this:
having sum(day = 2 and isOK = 1) > 0;
Another way to do it would be to use a correlated subquery that makes sure there exists a row with Day = 2 and isOk = 1 for the Name:
select t1.name, sum(t1.isOk) ok_sum
from your_table t1
where exists (
select 1
from your_table t2
where t2.day = 2 and t2.isOK = 1 and t1.name = t2.name
)
group by t1.name
See this fiddle
TRY this :
SELECT
name, SUM(isok) AS isOk
FROM
table
GROUP BY `name`
HAVING SUM(`day` = 2 AND isok = 1) > 0;
SELECT x.name, SUM(y.isOK) total
FROM my_table x
JOIN my_table y
ON y.name = x.name
WHERE x.day = 2
AND x.isok=1
GROUP
BY x.name;