Getting the difference of two sums from the same table - sql

I need to get the difference of a query result from the same table.
Ex: myTable
id code status amount
1 A active 250
2 A active 200
3 B active 300
4 B active 100
5 A active 100
6 C active 120
7 C active 200
I want to get the total or sum of amount for each code (add if active, subtract if inactive status) so that i will have a result like this:
code total amount
A 150
B 400
C 80
i have tried a lot already, one of them is this:
SELECT code, amount1-amount2
FROM (
SELECT code, SUM(amount) AS amount1
FROM mytable
WHERE status='active'
GROUP BY code
UNION ALL
SELECT code, SUM(amount) AS amount2
FROM mytable
WHERE status='inactive'
GROUP BY code
) as T1
GROUP BY code

The simplest solution would be to use a case expression inside the sum function:
SELECT
code,
sum(case when status='active' then amount else amount * -1 end) as total_amount
FROM mytable
GROUP BY code
If there can be other statuses besides active/inactive you have to be more explicit:
sum(
case
when status = 'active' then amount
when status = 'inactive' then amount * -1
end
) AS total

WITH A AS
(SELECT
code,
CASE WHEN status='active' THEN amount ELSE -amount END AS signed_amount
FROM myTable)
SELECT code, sum(signed_amount) FROM A GROUP BY code;

Related

SQL Select value from other table based on column value as treshold

I have a SQLite query which returns a user name and how much a user spent (done by SELECT SUM() from the different table).
Name
Spent
Adam
700
Mike
400
Steve
100
I have another table which contains discount amount with corresponding treshold:
Treshold
Discount
200
5
400
10
600
15
I need to find what discount each user has (if it does at all). So results would look like this:
Name
Spent
Discount
Total
Adam
700
15
595
Mike
400
10
360
Steve
100
0
100
You need a LEFT join of your query to the 2nd table and aggregation:
SELECT t1.name, t1.Spent,
COALESCE(MAX(t2.Discount), 0) Discount,
t1.Spent * (1 - 0.01 * COALESCE(MAX(t2.Discount), 0)) Total
FROM (SELECT name, SUM(Spent) Spent FROM table1 GROUP BY name) t1
LEFT JOIN table2 t2 ON t2.Treshold <= t1.Spent
GROUP BY t1.name;
See the demo.
I am in a hurry. Sorry.
with a as (
select name, sum(spent) spe
from test1
group by name)
select a.name
, a.spe
, max(tres)
, max(disc)
, spe -spe * (0 || '.' || disc) total
from test2, a
where tres <= a.spe
DEMO

Get count With Distinct in SQL Server

Select
Count(Distinct iif(t.HasReplyTask = 1, t.CustomerID, Null)) As Reply,
Count(Distinct iif(t.HasOverdueTask = 1, t.CustomerID, Null)) As Overdue,
Count(Distinct t.CustomerID) As Total
From
Table1 t
If a customer is in Reply, we need to remove that customer in Overdue count, That means if Customer 123 is in both, The Overdue count should be one less. How can I do this?
I am adding some data here,
Customer 123 has "HasReplyTask", so, we have to filter that customer from Count in OverDue(even though that customer has one Overdue task without HasReplyTask). 234 is one and Distinct of 456 is one.
So, the overdue count should be 2, Above query returns 3
If I've got it right, this can be done using a subquery to get the numbers for each customer, and then get the summary information as follows:
Select Sum(HasReplyTask) As Reply,
Sum(HasOverdueTask) As Overdue,
Count(CustomerID) As Total
From (
Select CustomerID,
IIF(Max(Cast(HasReplyTask As TinyInt))<>0, 0, Max(Cast(HasOverdueTask As TinyInt))) As HasOverdueTask,
Max(Cast(HasReplyTask As TinyInt)) As HasReplyTask
From Table1
Group by CustomerID) As T
I don't know about column data types, so I used cast function to use max function.
db<>fiddle
Reply
Overdue
Total
1
2
3
What would probably be more efficient for you is to pre-aggregate your table by customer ID and have counts per customer. Then your outer query can test for whatever you are really looking for. Something like
select
sum( case when PQ.ReplyCount > 0 then 1 else 0 end ) UniqReply,
sum( case when PQ.OverdueCount > 0 then 1 else 0 end ) UniqOverdue,
sum( case when PQ.OverdueCount - PQ.ReplyCount > 0 then 1 else 0 end ) PendingReplies,
count(*) as UniqCustomers
from
( select
yt.customerid,
count(*) CustRecs,
sum( case when yt.HasReplyTask = 1 then 1 else 0 end ) ReplyCount,
sum( case when yt.HasOverdueTask = 1 then 1 else 0 end ) OverdueCount
from
yourTable yt
group by
yt.customerid ) PQ
Now to differentiate the count you are REALLY looking for, you might need to do a test against the prequery (PQ) of ReplyCount vs OverdueCount such as... For a single customer ID (thus the pre query), if the OverdueCount is GREATER than the ReplyCount, then it is still considered overdue? So for customer 123, they had 3 overdue, but only 2 replies. You want that counted once? But for customers 234 and 456, the only had overdue entries and NO replies. So, the total where Overdue - Reply > 0 = 3 distinct people.
Is that your ultimate goal?

SQL - Filter data where number of lines is variable

I have invoice data (sample below) where each invoice has one or more lines and each line has one or more sequential assigned reviewers.
I want to filter the data to return only invoices where all lines have been approved by all reviewers.
So in the sample below I want 123 to return, but not 234 because invoice line 2 is still pending for reviewer 2. I am fairly new to SQL and at a loss on how to do this.
InvoiceNo.
InvoiceLine
ApprovalSeq
Status
123
1
1
Approved
123
1
2
Approved
123
2
1
Approved
234
1
1
Approved
234
1
2
Approved
234
2
1
Approved
234
2
2
Pending
One method for this is to use conditional aggregation:
select invoiceno
from t
group by invoiceno
having count(*) = sum(case when status = 'Approved' then 1 else 0 end);
Actually, because 'Approved' may be the minimum status alphabetically, you can use:
select invoiceno
from t
group by invoiceno
having max(status) = 'Approved'
you can use case statement to have conditional count.
select invoiceno
from invoice
group by invoiceno
having count(*) = count(case when status = 'Approved' then 1 else null end)
You could do that using sub-query as one of the options:
SELECT DISTINCT InvoiceNo FROM TABLE x LEFT JOIN
(SELECT DISTINCT InvoiceNo FROM TABLE x1 WHERE Status != 'Approved') notAppr
ON x.InvoiceNo = notAppr.InvoiceNo
WHERE notAppr.InvoiceNo IS NULL
It has been shown how to get the invoice numbers where no approval is pending by aggreating your invoice line approval table. You probably want to use this to select from the invoice table:
select *
from invoice
where invoiceno in
(select invoiceno from invoice_line_approval group by invoiceno having ...)
If so, you don't have to aggregate the whole invoice line approval table, but merely check whether a non-approved row exists for an invoice with NOT EXISTS:
select *
from invoice i
where not exists
(
select null
from invoice_line_approval ila
where ila.invoiceno = i.invoiceno
and ila.status <> 'Approved'
);
(If the status can be null, then you need and (ila.status <> 'Approved' or ila.status is null), though.)

Case in Sql group by query

I am working on a project in which I want to use Case to calculate price of product under specific Reference Number in SQL server. Below is my Sql query
SELECT
product AS Products,
refNum AS Refrence,
COUNT(id) AS Count
FROM ProductPriceList
GROUP BY
refNum, product
By Executing Above query I get:
Product Reference Count
Product1 Ref08 24
Product2 Ref08 7
Product3 Ref07 32
Product2 Ref12 1
Product3 Ref12 18
Product1 Ref07 76
Product1 Null 56
Can anyone guide me how to use Case statement in Sql query with group by statement to show price Below is the case:
if count < 10 then price 1
if count > 10 and < 100 then price 2
if count > 100 then price 3
I don't want to add a new table in my database. I hope you can understand my query.
Thanks in advance.
I think a basic CASE expression can handle your requirement:
SELECT
product AS Products,
refNum AS Refrence,
CASE WHEN COUNT(*) < 10 THEN 1
WHEN COUNT(*) >= 10 AND COUNT(*) < 100 THEN 2
ELSE 3 END AS price
FROM ProductPriceList
GROUP BY
product, refNum;
Not much to explain here, except that the 2 price case uses a bound which includes the count of 10 (since the 1 price case excludes it).
Here's alternative (doesn't differ much from exisiting one though):
You can use your query in subquery and use case outside:
select product,
--to get NULL values back
case Reference when 'RefNull' then NULL else Reference end [Reference],
case when [Count] < 10 then 1
when [Count] between 10 and 100 then 2
else 3 end [price]
from (
SELECT product AS Products,
--to allow also null values to be grouped
coalesce(refNum, 'RefNull') AS Refrence,
COUNT(id) AS Count
FROM ProductPriceList
GROUP BY coalesce(refNum, 'RefNull'), product
) [a]
Dataset:
Create Table ProductPriceList
(
Product varchar(10)
,RefNum CHAR(5)
,Records Int
);
Insert into ProductPriceList
Values
('Product1','Ref08',24)
,('Product2','Ref08',7)
,('Product3','Ref07',32)
,('Product2','Ref12',1)
,('Product3','Ref12',18)
,('Product1','Ref07',76)
,('Product1', NULL, 56);
With RCTE AS
(
Select Product
,RefNum
,Records
,1 RowNo
From ProductPriceList PPL
Union All
Select Product
,RefNum
,Records
,RowNo + 1
From RCTE R
Where RowNo + 1 < Records
)
Insert Into ProductPriceList (Product, RefNum, Records)
Select Product, RefNum, Records
From RCTE
where Records > 1
Query to fetch desired result:
Select Product
,RefNum
,Case When Count(*) < 10 Then 1
When Count(*) Between 10 and 99 then 2
Else 3 End Price
From ProductPriceList
Group By Product, RefNum
SQL Fiddle

SQL aggregate rows with same id , specific value in secondary column

I'm looking to filter out rows in the database (PostgreSQL) if one of the values in the status column occurs. The idea is to sum the amount column if the unique reference only has a status equals to 1. The query should not SELECT the reference at all if it has also a status of 2 or any other status for that matter. status refers to the state of the transaction.
Current data table:
reference | amount | status
1 100 1
2 120 1
2 -120 2
3 200 1
3 -200 2
4 450 1
Result:
amount | status
550 1
I've simplified the data example but I think it gives a good idea of what I'm looking for.
I'm unsuccessful in selecting only references that only have status 1.
I've tried sub-queries, using the HAVING clause and other methods without success.
Thanks
Here's a way using not exists to sum all rows where the status is 1 and other rows with the same reference and a non 1 status do not exist.
select sum(amount) from mytable t1
where status = 1
and not exists (
select 1 from mytable t2
where t2.reference = t1.reference
and t2.status <> 1
)
SELECT SUM(amount)
FROM table
WHERE reference NOT IN (
SELECT reference
FROM table
WHERE status<>1
)
The subquery SELECTs all references that must be excluded, then the main query sums everything except them
select sum (amount) as amount
from (
select sum(amount) as amount
from t
group by reference
having not bool_or(status <> 1)
) s;
amount
--------
550
You could use windowed functions to count occurences of status different than 1 per each group:
SELECT SUM(amount) AS amount
FROM (SELECT *,COUNT(*) FILTER(WHERE status<>1) OVER(PARTITION BY reference) cnt
FROM tc) AS sub
WHERE cnt = 0;
Rextester Demo