MySQL select calculate 2 fields based on other field - mysql

Let's say i have a table like this
| user | symbol | status | value |
----------------------------------
| 101 | A | 1 | 20 |
| 102 | A | 1 | 20 |
| 103 | A | 1 | 20 |
| 101 | A | 0 | 20 |
| 102 | B | 1 | 20 |
| 103 | A | 1 | 20 |
| 101 | A | 0 | 20 |
| 102 | A | 1 | 20 |
| 103 | A | 0 | 20 |
| 101 | B | 1 | 20 |
| 102 | A | 0 | 20 |
and i want the result like this (all value change to minus that have status = 0)
| user | symbol | differences |
----------------------------------
| 101 | A | -20 |
| 101 | B | 20 |
| 102 | A | 20 |
| 102 | B | 20 |
| 103 | A | 20 |
Please help, any help would be appreciated!

Use a CASE expression for changing the value to minus if status = 0.
Query
select `user`, `symbol`,
sum(case when `status` = 0 then `value` * -1 else `value` end) as differences
from `your_table_name`
group by `user`, `symbol`
order by `user`, `symbol`;
SQL Fiddle demo

A generic solution using CASE..WHEN below:
SELECT user,
symbol,
CASE WHEN status > 0 THEN value
ELSE -value END as differences
FROM yourTable;
Though in this particular case, you can also use math:
SELECT user,
symbol,
(value * (2*status-1)) as differences
FROM yourTable;
If you also want to aggregate the results, then you can change the queries above to use a GROUP BY with SUM as the aggregation:
SELECT user,
symbol,
SUM(CASE WHEN status > 0 THEN value
ELSE -value END) as differences
FROM yourTable
GROUP BY user, symbol;
And the equivalent aggregated query, using the arithmetic:
SELECT user,
symbol,
SUM(value * (2*status-1)) as differences
FROM yourTable
GROUP BY user, symbol;

Related

How to sum values of two tables and group by date

I am building a trading system where users need to know their running account balance by date for a specific user (uid) including how much they made from trading (results table) and how much they deposited or withdrew from their accounts (adjustments table).
Here is the sqlfiddle and tables: http://sqlfiddle.com/#!9/6bc9e4/1
Adjustments table:
+-------+-----+-----+--------+------------+
| adjid | aid | uid | amount | date |
+-------+-----+-----+--------+------------+
| 1 | 1 | 1 | 20 | 2019-08-18 |
| 2 | 1 | 1 | 50 | 2019-08-21 |
| 3 | 1 | 1 | 40 | 2019-08-21 |
| 4 | 1 | 1 | 10 | 2019-08-19 |
+-------+-----+-----+--------+------------+
Results table:
+-----+-----+-----+--------+-------+------------+
| tid | uid | aid | amount | taxes | date |
+-----+-----+-----+--------+-------+------------+
| 1 | 1 | 1 | 100 | 3 | 2019-08-19 |
| 2 | 1 | 1 | -50 | 1 | 2019-08-20 |
| 3 | 1 | 1 | 100 | 2 | 2019-08-21 |
| 4 | 1 | 1 | 100 | 2 | 2019-08-21 |
+-----+-----+-----+--------+-------+------------+
How do I get the below results for uid (1)
+--------------+------------+------------------+----------------+------------+
| ResultsTotal | TaxesTotal | AdjustmentsTotal | RunningBalance | Date |
+--------------+------------+------------------+----------------+------------+
| - | - | 20 | 20 | 2019-08-18 |
| 100 | 3 | 10 | 133 | 2019-08-19 |
| -50 | 1 | - | 84 | 2019-08-20 |
| 200 | 4 | 90 | 378 | 2019-08-21 |
+--------------+------------+------------------+----------------+------------+
Where RunningBalance is the current account balance for the particular user (uid).
Based on #Gabriel's answer, I came up with something like, but it gives me empty balance and duplicate records
SELECT SUM(ResultsTotal), SUM(TaxesTotal), SUM(AdjustmentsTotal), #runningtotal:= #runningtotal+SUM(ResultsTotal)+SUM(TaxesTotal)+SUM(AdjustmentsTotal) as Balance, date
FROM (
SELECT 0 AS ResultsTotal, 0 AS TaxesTotal, adjustments.amount AS AdjustmentsTotal, adjustments.date
FROM adjustments LEFT JOIN results ON (results.uid=adjustments.uid) WHERE adjustments.uid='1'
UNION ALL
SELECT results.amount AS ResultsTotal, taxes AS TaxesTotal, 0 as AdjustmentsTotal, results.date
FROM results LEFT JOIN adjustments ON (results.uid=adjustments.uid) WHERE results.uid='1'
) unionTable
GROUP BY DATE ORDER BY date
For what you are asking you would want to union then group the results from both tables, this should give the results you want. However, I recommend calculating the running balance outside of MySQL since this adds some complexity to our query.
Weird things could start to happen, for example, if someone already defined the #runningBalance variable as part of the queries scope.
SELECT aggregateTable.*, #runningBalance := ifNULL(#runningBalance, 0) + TOTAL
FROM (
SELECT SUM(ResultsTotal), SUM(TaxesTotal), SUM(AdjustmentsTotal)
, SUM(ResultsTotal) + SUM(TaxesTotal) + SUM(AdjustmentsTotal) as TOTAL
, date
FROM (
SELECT 0 AS ResultsTotal, 0 AS TaxesTotal, amount AS AdjustmentsTotal, date
FROM adjustments
UNION ALL
SELECT amount AS ResultsTotal, taxes AS TaxesTotal, 0 as AdjustmentsTotal, date
FROM results
) unionTable
GROUP BY date
) aggregateTable

Group value mysql [duplicate]

This question already has answers here:
Aggregate ad clicks and views
(2 answers)
Closed 3 years ago.
Say I have a table log with format [prodName, status]
An example set be:
| ProdName | Status
|-----------------------
| AXD1 | 100
| BL1 | 100
| AXD2 | 300
| BL2 | 300
| AXD1 | 300
| AXD2 | 100
| BL1 | 100
| BL1 | 100
I will the result group by status where 100 is success and 300 not. So the result like this.
| ProdName | Success | Not
|-----------------------
| AXD1 | 1 | 1
| BL1 | 3 | 0
| AXD2 | 1 | 1
| BL2 | 0 | 1
You could use case expressions to find if a row was a success or not, and then count them:
SELECT ProdName,
COUNT(CASE Status WHEN 100 THEN 1 END) AS "Success",
COUNT(CASE Status WHEN 300 THEN 1 END) AS "Not Success"
FROM mytable
GROUP BY ProdName
You can use conditional aggregation:
select
ProdName,
sum(Status = 100) Success,
sum(Status = 300) `Not`
from Log
group by ProdName
See the demo.
Results:
| ProdName | Success | Not |
| -------- | ------- | --- |
| AXD1 | 1 | 1 |
| AXD2 | 1 | 1 |
| BL1 | 3 | 0 |
| BL2 | 0 | 1 |

efficient query to group by one field and put all other fields in the row SQL

Imagine the result of a query is something like the following:
+----+---------+--------+
| id | count | type |
+----+---------+--------+
| 1 | 20 | a |
| 1 | 30 | b |
| 1 | 10 | c |
| 2 | 05 | a |
| 2 | 20 | b |
| 2 | 40 | c |
+----+---------+--------+
and the expected result:
+----+---------+--------+------+
| id | a | b | c |
+----+---------+--------+------+
| 1 | 20 | 30 | 10 |
| 2 | 05 | 20 | 40 |
+----+---------+--------+------+
I know some solutions which are complex using Cursor, Variables, Join and etc. I would like to find the most efficient one, otherwise I will handle it from the application layer.
One method uses conditional aggregation:
select id,
sum(case when type = 'a' then count else 0 end) as a,
sum(case when type = 'b' then count else 0 end) as b,
sum(case when type = 'c' then count else 0 end) as c
from t
group by id;

Insert X times based on column

I have a table that has the following properties.
| UPC | Cost | Items |
--------------------------
| abc | 2.50 | 30 |
| 123 | 2.11 | 40 |
Let's say I need to copy the information into another table, but I need to do each one as its own line item... for example, I need to end with...
| UPC | Cost | Sold | ID |
------------------------------
| abc | 2.50 | NULL | 1 |
| abc | 2.50 | NULL | 2 |
...
| abc | 2.50 | NULL | 29 |
| abc | 2.50 | NULL | 30 |
| 123 | 2.11 | NULL | 31 |
| 123 | 2.11 | NULL | 32 |
...
| 123 | 2.11 | NULL | 69 |
| 123 | 2.11 | NULL | 70 |
Is there a way to insert based off # of items in the original table?
I was thinking I could do something like this...
WHILE (SELECT Total FROM dbo.tempInventory) > 0
BEGIN
INSERT INTO dbo.Inventory (UPC, Cost, Sold)
SELECT (UPC, Cost, NULL)
FROM dbo.tempInventory
UPDATE dbo.tempInventory
SET Total = Total-1
END
And this would work for 1 UPC at a time. The issue is I'm working with over 3500 UPC's, and each have between 1 and 60 items to input.
I found a way to do it directly in SQL, but to be honest I'm not 100% sure HOW it works. Would anyone be able to explain?
WITH tally AS (
SELECT 1 n
UNION ALL
SELECT n + 1 FROM tally WHERE n < 100
)
SELECT UPC, n.n Position
FROM dbo.tempInventory t JOIN tally n
ON n.n <= t.Items
ORDER BY Description, Position

Update innerquery result

I have a query and a result as follows.
In the database NULL and 0 represent the same meaning.
Now I want a counter based on Null+0 or 1
Eg:in the following example I want the result like this:
IsVirtual Category counter
NULL+0 3 343+8 = (351 is Total)
Query
select * from
(
Select IsVirtual, Category, count(*) as counter
from [Hardware]
group by IsVirtual, Category
) innercat
Output
+-----------+----------+---------+
| IsVirtual | Category | counter |
+-----------+----------+---------+
| NULL | 3 | 343 |
| 0 | 3 | 8 |
| 1 | 2 | 1 |
| 0 | 1 | 1 |
| NULL | 6 | 119 |
| 0 | 4 | 1 |
| NULL | 1 | 70 |
| 0 | 5 | 9 |
| NULL | 4 | 54 |
| 0 | 2 | 2 |
| NULL | 5 | 41 |
| NULL | 2 | 112 |
| 1 | 1 | 5 |
+-----------+----------+---------+
I think you want this :
SELECT COALESCE(IsVirtual, 0) as [IsVirtual],
Category,
Count(*) as [Counter]
FROM yourtable
GROUP BY COALESCE(IsVirtual, 0),Category
This will give you expected result without using subquery.
try with this
select * from (
Select CASE ISNULL(IsVirtual,0)
WHEN 0 Then 'NULL + 0'
ELSE IsVirtual
END AS IsVirtual, Category, count(*) as counter from [Hardware] group by ISNULL(IsVirtual,0), Category
)innercat
You can also do the same thing by using MAX function
This might help you.
SELECT
max(IsVirtual) as IsVirtual,
Category,
Count(*) as Counter
FROM
yourtable
GROUP BY
Category