select sum of values: grouped by sign - sql

I have a simple SQL table of the following format:
date | value
============
2010-01-01 | -54
2010-01-02 | 134
2010-01-03 | 73
With the following two SQL queries I can select the sum of values and distinguish between the sum of positive values and the sum of negative values.
SELECT YEAR(date), SUM(value) FROM table WHERE value > 0 GROUP BY YEAR(date)
SELECT YEAR(date), SUM(value) FROM table WHERE value < 0 GROUP BY YEAR(date)
Is there a way to do this in a single SQL query?
Thanks in advance!

Not tested (don't know if the case can be put within a SUM instruction), but the CASE instruction could be of help:
SELECT
YEAR(date),
SUM(CASE WHEN value > 0 THEN value ELSE 0 END) as positives_sum,
SUM(CASE WHEN value < 0 THEN value ELSE 0 END) as negatives_sum
FROM
table
GROUP BY
YEAR(date)

Many databases have a SIGN() function
SELECT YEAR(date),
SUM(value)
FROM table
GROUP BY YEAR(date),
SIGN(value)

Related

Add a 0 in the next row of a column after the last data point

I have written a query which gives the output as shown below:
Date Amount
01-01-2020
01-02-2020 10000
01-03-2020 20000
01-04-2020 30000
01-05-2020 40000
01-06-2020
01-07-2020
01-08-2020
In the above table, we can see that the amount is null for 01-01-2020, 01-06-2020, 01-07-2020, 01-08-2020. Now, I want to add a 0 to the amount column for just 1 row i.e for the date- 01-06-2020 which is after the last data point - 40000. And I'm not sure how to do it. Is there any straight forward query to achieve this? Thank you.
You can use lag() and a case expression:
select date,
case when amount is null and lag(amount) over(order by date) is not null
then 0
else amount
end as amount
from mytable
If you wanted an update statement:
with cte as (
select amount,
case when amount is null and lag(amount) over(order by date) is not null
then 0
end as new_amount
from mytable
)
update cte set amount = new_amount where new_amount = 0

Calculating Percentages in Postgres

I'm completely new to PostgreSQL. I have the following table called my_table:
a b c date
1 0 good 2019-05-02
0 1 good 2019-05-02
1 1 bad 2019-05-02
1 1 good 2019-05-02
1 0 bad 2019-05-01
0 1 good 2019-05-01
1 1 bad 2019-05-01
0 0 bad 2019-05-01
I want to calculate the percentage of 'good' from column c for each date. I know how to get the number of 'good':
SELECT COUNT(c), date FROM my_table WHERE c != 'bad' GROUP BY date;
That returns:
count date
3 2019-05-02
1 2019-05-01
My goal is to get this:
date perc_good
2019-05-02 25
2019-05-01 75
So I tried the following:
SELECT date,
(SELECT COUNT(c)
FROM my_table
WHERE c != 'bad'
GROUP BY date) / COUNT(c) * 100 as perc_good
FROM my_table
GROUP BY date;
And I get an error saying
more than one row returned by a subquery used as an expression.
I found this answer but not sure how to or if it applies to my case:
Calculating percentage in PostgreSql
How do I go about calculating the percentage for multiple rows?
avg() is convenient for this purpose:
select date,
avg( (c = 'good')::int ) * 100 as percent_good
from t
group by date
order by date;
How does this work? c = 'good' is a boolean expression. The ::int converts it to a number, with 1 for true and 0 for false. The average is then the average of a bunch of 1s and 0s -- and is the ratio of the true values.
For this case you need to use conditional AVG():
SELECT
date,
100 * avg(case when c = 'good' then 1 else 0 end) perc_good
FROM my_table
GROUP BY date;
See the demo.
You could use a conditional sum for get the good value and count for total
below an exaustive code sample
select date
, count(c) total
, sum(case when c='good' then 1 else 0 end) total_good
, sum(case when c='bad' then 1 else 0 end) total_bad
, (sum(case when c='good' then 1 else 0 end) / count(c))* 100 perc_good
, (sum(case when c='bad' then 1 else 0 end) / count(c))* 100 perc_bad
from my_table
group by date
and for your result
select date
, (sum(case when c='good' then 1 else 0 end) / count(c))* 100 perc_good
from my_table
group by date
or as suggested by a_horse_with_no_name using count(*) filter()
select date
, ((count(*) filter(where c='good'))/count(*))* 100 perc_good
from my_table
group by date

Query to find accounts with specified months

I Want to extract those account ids which are present in may and JUNE but not in JULY . I have data for 6 months
i have tried using CONDITION(AND/ NOT)
SELECT ACCOUNT_ID, DATA_MONTH
FROM DATA1
WHERE DATA_MONTH ='01-MAY-18' AND DATA_MONTH <> '01-JULY-18
GROUP BY DATA_MONTH
I am still getting account ids which where in JULY
You can put the conditions you need in a HAVING clause:
SELECT ACCOUNT_ID
FROM DATA1
GROUP BY ACCOUNT_ID
HAVING
SUM(CASE WHEN MONTH(DATA_MONTH) = 5 THEN 1 ELSE 0 END) > 0
AND
SUM(CASE WHEN MONTH(DATA_MONTH) = 6 THEN 1 ELSE 0 END) > 0
AND
SUM(CASE WHEN MONTH(DATA_MONTH) = 7 THEN 1 ELSE 0 END) = 0
I assume that in your rdbms there exists a function like MONTH() to extract the month from the date.
If you want to check the year also, you can do it in each condition, so you must change to:
CASE WHEN MONTH(DATA_MONTH) = 7 AND YEAR(DATA_MONTH) = 2018 THEN 1 ELSE 0
Using SQL Server's Month function gives you an understandable example of how to solve this.
SELECT ACCOUNT_ID
FROM DATA1
WHERE MONTH(DATA_MONTH) in (5,6) AND YEAR(DATA_MONTH) = 2018 AND MONTH(DATA_MONTH) NOT IN (7)
GROUP BY ACCOUNT_ID
Your date formats look like Oracle, so I will answer for that database.
The idea is to use conditional aggregation:
SELECT ACCOUNT_ID, DATA_MONTH
FROM DATA1
WHERE DATA_MONTH >= DATE '2018-05-01' AND
DATA_MONTH < DATE '2018-08-01'
GROUP BY ACCOUNT_ID
HAVING COUNT(DISTINCT DATA_MONTH) = 2 AND
MAX(DATA_MONTH) < DATE '2018-07-01';
This assumes that all your dates are on the first of the month.
SELECT ACCOUNT_ID, DATA_MONTH
FROM DATA1
WHERE MONTH(DATA_MONTH) =5 AND MONTH(DATA_MONTH) = 6
GROUP BY DATA_MONTH
SELECT ACCOUNT_ID, DATA_MONTH
FROM DATA1
WHERE MONTH(DATA_MONTH) BETWEEN 5 AND 6
GROUP BY Year(DATA_MONTH),month(DATA_MONTH)

How to count rows matching a filter in aggregate operations

Sorry for the not punctual title, this is the best I succeeded to obtain.
I have a table like this:
date | type | qty
2018-03-21 03:30:00 | A | 3
2018-03-22 03:30:00 | A | 3
2018-03-22 04:57:00 | A | 1
2018-03-22 05:18:00 | B | 3
I do some aggregations on this table, e.g. sum of qty over day or over month.
In the same query I need to count how many rows are of type B, while retrieving the total qty on that day.
So,
select sum(qty), date_trunc('day', date) ... group by date_trunc('day', date);
Now, what I need to do next is to count how many rows are of type B. So the expected result is
day | Bcount | totqty
2018-03-21 | 0 | 3
2018-03-22 | 1 | 7
I thought to use partitions but I'm not sure how to use them in this specific case.
Edit: thank you all, guys, for your answers. This was soooooooo easy 🙄
Since 9.4 release we can replace the CASE WHEN clauses in these aggregate functions by the new FILTER clause, use below query:
select date_trunc('day', date) AS Day,
Count(TYPE) filter (where Type = 'B') AS BCount,
Sum(qty) AS TotalQty
FROM Table1 group by date_trunc('day', date);
For Demo Follow the link:
http://sqlfiddle.com/#!17/a5203/14
Until Postgres 9.4 release, if you wanted to count a few sets of records when executing an aggregate function, you had to use a CASE WHEN.
Like This:
SELECT date_trunc('day', date) AS Day,
SUM(CASE WHEN TYPE = 'B' THEN 1 ELSE 0 END) AS BCount,
Sum(qty) AS TotalQty
FROM Table1 group by date_trunc('day', date);
Use a case expression to do conditional aggregation:
select ...
sum(case when type = 'B' then 1 else 0 end) as Bcount
...
select date_trunc('day', date) ,sum(qty),
SUM (CASE WHEN type = 'B' THEN 1 ELSE 0 END) AS Bcount
FROM Table1
group by date_trunc('day', date);
Demo
http://sqlfiddle.com/#!17/a5203/13

Split SQL column values and group by date and return single row

I have SQL Server query , using this , I am splitting event id sum columns to two columns based on some condition. Query executed successfully, but the result is not desired. It's half useful. Please help me to get expected result. I want one row for both split columns instead two rows and empty spaces.
SQL Query:
select convert(date, paymenttime)) , SUM(case when eventid = 33 then 1 ELSE 0 END) AS column1,
SUM(case when eventid = 36 then 1 ELSE 0 END) AS column2
from tbltransMain_backup where
paymentime <= '20160731' and PaymentTime >= '20160701'
group by convert(date,paymenttime),event_id
order by convert(date,paymenttime)
Result view:
Expected Result:
2016-07-01 27 1
2016-07-02 28 2
2016-07-03 30 15
The query you posted (perhaps unknowingly) into your question should already give you the desired results:
SELECT CONVERT(DATE, paymenttime),
SUM(CASE WHEN event_id = 33 THEN 1 ELSE 0 END) AS column1,
SUM(CASE WHEN event_id = 36 THEN 1 ELSE 0 END) AS column2
FROM tbltransMain_backup
WHERE paymentime <= '20160731' AND
paymentime >= '20160701'
GROUP BY CONVERT(DATE, paymenttime)
ORDER BY CONVERT(DATE, paymenttime)
The reason you were getting two rows for every date is that your query had the following grouping:
GROUP BY CONVERT(DATE, paymenttime),
event_id
In other words, each date would have two groups, one for event_id = 33 and one for event_id = 36.