SQL SUM and value conversion - sql

I'm looking to transform data in my SUM query to acknowledge that some numeric values are negative in nature, although not represented as such.
I look for customer balance where the example dataset includes also credit transactions that are not written as negative in the database (although all records that have value C for credit in inv_type column should be treated as negative in the SQL SUM function). As an example:
INVOICES
inv_no inv_type cust_no value
1 D 25 10
2 D 35 30
3 C 25 5
4 D 25 50
5 C 35 2
My simple SUM function would not give me the correct answer:
select cust_no, sum(value) from INVOICES
group by cust_no
This query would obviously sum the balance of customer no 25 for 65 and no 35 for 32, although the anticipated answer would be 10-5+50 = 55 and 30 - 2 = 28
Should I perhaps utilize CAST function somehow? Unfortunately I'm not up to date on the underlying db engine, however good chance of it being of IBM origin. Most of the basic SQL code has worked out so far though.

You can use the case expression inside of a sum(). The simplest syntax would be:
select cust_no,
sum(case when inv_type = 'C' then - value else value end) as total
from invoices
group by cust_no;
Note that value could be a reserved word in your database, so you might need to escape the column name.

You should be able to write a projection (select) first to obtain a signed value column based on inv_type or whatever, and then do a sum over that.
Like this:
select cust_no, sum(value) from (
select cust_no
, case when inv_type='D' then [value] else -[value] end [value]
from INVOICES
) SUMS
group by cust_no

You can put an expression in the sum that calculates a negative value if the invoice is a credit:
select
cust_no,
sum
(
case inv_type
when 'C' then -[value]
else [value]
end
) as [Total]
from INVOICES

Related

How to pivot in postgresql

I have table like following,and I would like to transform them.
year month week type count
2021 1 1 A 5
2021 1 1 B 6
2021 1 1 C 7
2021 1 2 A 0
2021 1 2 B 8
2021 1 2 C 9
I'd like to pivot like following.
year month week A B C
2021 1 1 5 6 7
2021 1 2 0 8 9
I tried like following statement, but it returned a lot of null columns.
And I wonder I must add columns one by one when new type will be added.
select
year,
month,
week,
case when type in ('A') then count end as A,
case when type in ('B') then count end as B,
case when type in ('C') then count end as C,
from
table
If someone has opinion, please let me know.
Thanks
demo: db<>fiddle
You can either use the FILTER clause:
SELECT
year, month, week,
MAX("count") FILTER (WHERE type = 'A') as A, -- 2
MAX("count") FILTER (WHERE type = 'B') as B,
MAX("count") FILTER (WHERE type = 'C') as C
FROM mytable
GROUP BY year, month, week -- 1
ORDER BY year, month, week
or you can use the CASE clause:
SELECT
year, month, week,
MAX (CASE WHEN type = 'A' THEN "count" END) AS A,
MAX (CASE WHEN type = 'B' THEN "count" END) AS B,
MAX (CASE WHEN type = 'C' THEN "count" END) AS C
FROM mytable
GROUP BY year, month, week
ORDER BY year, month, week
In both cases you need to perform a GROUP BY action.
This makes an aggregation function necessary, like MAX() or SUM(). Finally you need to apply a kind of filter (CASE or FILTER) to only aggregate the related data.
Additionally: Please note that the words count, year, month, week are keywords of SQL. To avoid any complications you should think about other column names.
This question has been asked many times, & there are decent (even dynamic) solutions. While CROSSTAB() is available in recent versions of Postgres, not everyone has sufficient user privileges to install the prerequisite extension.
One such solution involves a temp type (temp table) created by an anonymous function & JSON expansion of the resultant type.
See also: DB FIDDLE (UK): https://dbfiddle.uk/Sn7iO4zL
How to pivot or crosstab in postgresql without writing a function?

How to compare between 2 COUNT() SQL Server

I have a table like this:
Name Profit
==============
A 50
A -10
A 60
I want to count how many data partition by Name and then I compare it with how many data that only profit. So, from the data above I will get result like this:
Name Total Profit Percentage
===============================
A 3 2 66.7
Please help me to solve this problem. Thanks in advance.
I think a simple GROUP BY query should work:
SELECT
Name,
COUNT(*) AS Total,
SUM(CASE WHEN Profit > 0 THEN 1 ELSE 0 END) AS Profit,
100.0 * SUM(CASE WHEN Profit > 0 THEN 1 ELSE 0 END) / COUNT(*) AS Percentage
FROM yourTable
GROUP BY
Name;
The only part of the query which might not be self-explanatory is the summation of the CASE expression. This sum tallies, for each group of records having the same name, the number of times that Profit has a non zero value. This technique is called conditional aggregation, and we also reuse this sum when calculating the percentage.
A somewhat enhanced version of Tim's answer (i.e. eliminate the calculation repetition):
SELECT Name, Total, Profit, 100 * Profit / Total AS Percentage
FROM (SELECT Name,
COUNT(*) AS Total,
SUM(CASE WHEN Profit > 0 THEN 1 ELSE 0 END) AS Profit
FROM yourTable
GROUP BY Name) q;
The engine may optimize the repletion, but it is mainly for readability and maintainability (in case the calculation needs to change). In this case, there is not much gain because it is only one repletion, but in cases where thee are several repetitions, this solution becomes more useful.
In case the maintainability point is not clear, let's say the definition of profitability has changed in the company and now we consider > 10 to be profitable. In Tim's query, you'll have to change every calculation from > 0 to > 10. In the query above, you'll only have to change it in one place.
Try this
declare #t table
(
Name varchar(10),
Profit int
)
insert into #t
values('A',50),('A',60),('A',-10)
SELECT
Name,
Profit = SUM(CASE WHEN Profit>0 THEN 1 ELSE 0 END),
Total = COUNT(1),
Average = (SUM(CASE WHEN Profit>0 THEN 1.0 ELSE 0.0 END)/COUNT(1))*100
FROM #t
GROUP BY Name

Calculate percentages of columns in Oracle SQL

I have three columns, all consisting of 1's and 0's. For each of these columns, how can I calculate the percentage of people (one person is one row/ id) who have a 1 in the first column and a 1 in the second or third column in oracle SQL?
For instance:
id marketing_campaign personal_campaign sales
1 1 0 0
2 1 1 0
1 0 1 1
4 0 0 1
So in this case, of all the people who were subjected to a marketing_campaign, 50 percent were subjected to a personal campaign as well, but zero percent is present in sales (no one bought anything).
Ultimately, I want to find out the order in which people get to the sales moment. Do they first go from marketing campaign to a personal campaign and then to sales, or do they buy anyway regardless of these channels.
This is a fictional example, so I realize that in this example there are many other ways to do this, but I hope anyone can help!
The outcome that I'm looking for is something like this:
percentage marketing_campaign/ personal campaign = 50 %
percentage marketing_campaign/sales = 0%
etc (for all the three column combinations)
Use count, sum and case expressions, together with basic arithmetic operators +,/,*
COUNT(*) gives a total count of people in the table
SUM(column) gives a sum of 1 in given column
case expressions make possible to implement more complex conditions
The common pattern is X / COUNT(*) * 100 which is used to calculate a percent of given value ( val / total * 100% )
An example:
SELECT
-- percentage of people that have 1 in marketing_campaign column
SUM( marketing_campaign ) / COUNT(*) * 100 As marketing_campaign_percent,
-- percentage of people that have 1 in sales column
SUM( sales ) / COUNT(*) * 100 As sales_percent,
-- complex condition:
-- percentage of people (one person is one row/ id) who have a 1
-- in the first column and a 1 in the second or third column
COUNT(
CASE WHEN marketing_campaign = 1
AND ( personal_campaign = 1 OR sales = 1 )
THEN 1 END
) / COUNT(*) * 100 As complex_condition_percent
FROM table;
You can get your percentages like this :
SELECT COUNT(*),
ROUND(100*(SUM(personal_campaign) / sum(count(*)) over ()),2) perc_personal_campaign,
ROUND(100*(SUM(sales) / sum(count(*)) over ()),2) perc_sales
FROM (
SELECT ID,
CASE
WHEN SUM(personal_campaign) > 0 THEN 1
ELSE 0
end AS personal_campaign,
CASE
WHEN SUM(sales) > 0 THEN 1
ELSE 0
end AS sales
FROM the_table
WHERE ID IN
(SELECT ID FROM the_table WHERE marketing_campaign = 1)
GROUP BY ID
)
I have a bit overcomplicated things because your data is still unclear to me. The subquery ensures that all duplicates are cleaned up and that you only have for each person a 1 or 0 in marketing_campaign and sales
About your second question :
Ultimately, I want to find out the order in which people get to the
sales moment. Do they first go from marketing campaign to a personal
campaign and then to sales, or do they buy anyway regardless of these
channels.
This is impossible to do in this state because you don't have in your table, either :
a unique row identifier that would keep the order in which the rows were inserted
a timestamp column that would tell when the rows were inserted.
Without this, the order of rows returned from your table will be unpredictable, or if you prefer, pure random.

How to divide two values from the different row

I have used this formula.
Quote change = (current month data / previous month data) * 100
Then my data stored on SQL SERVER table look like below :
id DATE DATA
1 2015/01/01 10
2 2015/02/01 20
3 2015/03/01 30
4 2015/04/01 40
5 2015/05/01 50
6 2015/06/01 60
7 2015/07/01 70
8 2015/08/01 80
9 2015/09/01 90
How can i implement this formula on SQL Function ?
For Example
current month is 2015/02/1
Quote change = (Current Month Data / Previous Month Data ) * 100
Quote change =( 15/10)*100
Then if current date is 2015/01/01. Because no data before 2015/01/01, I need to show 0 or #
Sql server 2012 have a window function called LAG that is very useful in situations like this.
Lag returns the value of a specific column in the previous row (specified by the order by part of the over clause).
Try this:
;With cte as
(
SELECT Id, Date, Data, LAG(Data) OVER(ORDER BY Date) As LastMonthData
FROM YourTable
)
SELECT Id,
Date,
Data,
CASE WHEN ISNULL(LastMonthData, 0) = 0 THEN 0 ELSE (Data/LastMonthData) * 100 END As Quote
FROM cte
I've used a CTE just so I wouldn't have to repeat the LAG twice.
The CASE expression is to prevent an exception in case the LastMonthData is 0 or null.
You can use inner join like mentioned below -
select a.*,isnull(cast(a.data/b.data as decimal(4,2))*100,0)
from TableA as a
inner join TableA as b
on b.date = dateadd(mm,-1,a.date)
Let me know if this helps

mysql table inside join

I have table tblsale.
In this table i have field called BillType, which contains "s" and "r" (s = sale , r = returns )
The table has rougly 25 records. Of that, 7 records are "r", and rest of the records are "s".
How do I write the query so that my result set includes the following columns:
What is want exactly is below
Amount BillType Amount BillType Date
100 s 50 r 29-11-2010
120 s 20 r 28-11-2010
130 s 30 r 27-11-2010
140 s 50 r 26-11-2010
What you appear to want are the results of two queries, sales and returns, side by side. It can be done with a kludge like this:
select amount, sale, returnamount, returned, returndate
from
(
select amount, 1 as sale, 0 as returnamount, 0 as returned, '' as returndate
from sales where billtype='s'
union
select 0 as amount, 0 as sale, amount as returnamount, 1 as returned, date as returndate
from sales where billtype ='r'
)
You may have to cast date into a string representation. The unioned sets need the same column structure, so you create dummy columns. (You didn't ask for a sale date for sales.)
Or you can do this with CASE WHEN statement.
I'm not sure what you are asking, but maybe:
SELECT BillNo, VAT, BillType, AfterDiscount FROM tblsale WHERE BillType = 's';