SQL sum with a condition - sql

I have a query that need to have a sum up value, but in one of my table's column, there's an increment and decrements value.
For decremented value, the value of it need to be as negative even it is stored as positive value in the table.
While for incremented value, it remains as positive.
My query look like this:
SELECT
SUM(
CASE WHEN ACTIVITY_TYPE = '0' THEN -(ACTIVITY_VALUE)
ELSE ACTIVITY_VALUE
END
) AS NEW_ACTIVITY_VALUE
FROM V_EMPLOYEE_PAYACT
WHERE ACTIVITY_TYPE IN ('0','1')
AND EMPLOYEE_NO = '00002789'
AND ((YEAR = 2014 AND MONTH <= 4)
OR
(YEAR > 2013 AND YEAR <2014)
OR
(YEAR = 2013 AND MONTH >= 5))
GROUP BY YEAR, MONTH
It return this result:
NEW_ACTIVITY_VALUE
391.00
-600.00
I need the value to be as -209 (the total of those two number)

You are currently grouping the sum per unique combination of month and year. If you want the total sum, just drop the group by clause:
SELECT
SUM(
CASE WHEN ACTIVITY_TYPE = '0' THEN -(ACTIVITY_VALUE)
ELSE ACTIVITY_VALUE
END
) AS NEW_ACTIVITY_VALUE
FROM V_EMPLOYEE_PAYACT
WHERE ACTIVITY_TYPE IN ('0','1')
AND EMPLOYEE_NO = '00002789'
AND ((YEAR = 2014 AND MONTH <= 4)
OR
(YEAR > 2013 AND YEAR <2014)
OR
(YEAR = 2013 AND MONTH >= 5))

Try this one:
SELECT
SUM(
CASE
WHEN ACTIVITY_TYPE = '0' THEN
- (ACTIVITY_VALUE)
ELSE
ACTIVITY_VALUE
END
) AS NEW_ACTIVITY_VALUE
FROM
V_EMPLOYEE_PAYACT
WHERE
ACTIVITY_TYPE IN ('0', '1')
AND EMPLOYEE_NO = '00002789'
AND (
(YEAR = 2014 AND MONTH <= 4)
OR (YEAR > 2013 AND YEAR < 2014)
OR (YEAR = 2013 AND MONTH >= 5)
)

Related

How to nest multiple case when expressions and add a condition

I am trying to divide customers (contact_key) column who shopped in 2021 (A.TXN_MTH) into new and 'returning' with returning meaning that they had not shopped in the last 12 months (YYYYMM in X.Fiscal_mth_idnt column).
I am using CASE WHEN A.TXN_MTH = MIN(X.FISCAL_MTH_IDNT) THEN 'NEW' which is correct. The next case when should be when the max month before X.TXN_MTH is 12 or more months previous. I have added the 12 months part in the Where statement. Should I be nesting 3 CASE WHEN'S instead of WHERE?
SELECT
T.CONTACT_KEY
, A.TXN_MTH
, CASE WHEN A.TXN_MTH = MIN(X.FISCAL_MTH_IDNT) THEN 'NEW'
WHEN (MAX(CASE WHEN X.FISCAL_MTH_IDNT < A.TXN_MTH THEN X.FISCAL_MTH_IDNT ELSE NULL END)) THEN 'RETURNING'
END AS CUST_TYPE
FROM B_TRANSACTION T
INNER JOIN B_TIME X
ON T.TRANSACTION_DT_KEY = X.DATE_KEY
INNER JOIN A
ON A.CONTACT_KEY = T.CONTACT_KEY AND A.BU_KEY = T.BU_KEY
WHERE (MAX(CASE WHEN X.FISCAL_MTH_IDNT < A.TXN_MTH THEN X.FISCAL_MTH_IDNT ELSE NULL END)) < A.TXN_MTH - (date_format(add_months(concat_ws('-',substr(yearmonth,1,4),substr(yearmonth,5,2),'01'),-12),'yyyyMM')
GROUP BY
T.CONTACT_KEY
, TXN_MTH;
You have not described your tables, so assuming fiscal_mth_idnt is a DATE column then you can use the LAG analytic function to find the previous row's value:
SELECT contact_key,
txn_mth,
CASE
WHEN prev_fiscal_mth_idnt IS NULL
THEN 'NEW'
WHEN ADD_MONTHS(prev_fiscal_mth_idnt, 12) < fiscal_mth_idnt
THEN 'RETURNING'
ELSE 'CURRENT'
END AS cust_type
FROM (
SELECT T.CONTACT_KEY,
A.TXN_MTH,
yearmonth,
X.FISCAL_MTH_IDNT,
LAG(X.FISCAL_MTH_IDNT) OVER (
PARTITION BY T.CONTACT_KEY
ORDER BY X.FISCAL_MTH_IDNT
) AS prev_fiscal_mth_idnt
FROM B_TRANSACTION T
INNER JOIN B_TIME X
ON T.TRANSACTION_DT_KEY = X.DATE_KEY
INNER JOIN A
ON A.CONTACT_KEY = T.CONTACT_KEY AND A.BU_KEY = T.BU_KEY
)
WHERE yearmonth LIKE '2021%';

How to use two where conditions in SQL?

Following is the query I have written and I need to where conditions.
Admin_Level_3_palika is not null
Year = '2021'
However, the following query is still giving me null values for Admin_Level_3_palika
SELECT
Admin_Level_3_palika,
COUNT(CASE WHEN Week_number = '21' THEN 1 END) AS count_Week_21,
COUNT(CASE WHEN Week_number = '22' THEN 1 END) AS count_Week_22,
(COUNT(CASE WHEN Week_number = '22' THEN 1 END) -
COUNT(CASE WHEN Week_number = '21' THEN 1 END)) AS Difference
FROM `interim-data.casedata.Interim Latest`
where Admin_Level_3_palika is not null or YEAR = '2021'
GROUP BY
Admin_Level_3_palika
ORDER BY
count_Week_22 desc limit 20
Please help me with how to work with this. Following is an example of my dataset, Epid_ID being unique for each row.
Admin_Level_3_palika Week_number YEAR Epid_ID
Lamkichuha MC 21 2020 COV-NEP-PR5-RUP-20-00022
Lamkichuha MC 21 2021 COV-NEP-PR5-RUP-20-00023
If these are your conditions:
1. Admin_Level_3_palika is not null
2. Year = '2021'
Then you need and:
where Admin_Level_3_palika is not null and Year = '2021'
If year is an integer (as I would expect it to be), drop the single quotes. Don't mix data types in comparisons.
For performance, you might also want to limit the week number:
where Admin_Level_3_palika is not null and
Year = '2021' and
week_number in ('21', '22')
And finally, BigQuery offers countif() which I recommend:
SELECT Admin_Level_3_palika,
COUNTIF(Week_number = '21') AS count_Week_21,
COUNTIF(Week_number = '22') AS count_Week_22,
(COUNTIF(Week_number = '22') - COUNTIF(Week_number = '21')) AS Difference
FROM `interim-data.casedata.Interim Latest`
WHERE Admin_Level_3_palika is not null AND
YEAR = '2021' AND
week_number IN ('21', '22')
GROUP BY Admin_Level_3_palika
ORDER BY count_Week_22 desc
LIMIT 20
change the or to and
the line:
where Admin_Level_3_palika is not null or YEAR = '2021'
should be:
where Admin_Level_3_palika is not null AND YEAR = '2021'
if year is not of type string you can do
where Admin_Level_3_palika is not null AND YEAR = 2021

How to improve slow sql query with aggregate functions

I want to show top ten customers,sales,margin where customers is registred during this accounting year. The query takes about 65seconds to run and it is not accepted :-(
As you may see i am not good at sql and will be very happy for help to improve the query.
SELECT Top 10
AcTr.R3, Actor.Nm,
SUM(CASE WHEN AcTr.AcNo<='3999' THEN AcAm*-1 ELSE 0 END) AS Sales ,
SUM(AcAm*-1) AS TB
FROM AcTr, Actor
WHERE (Actor.CustNo = AcTr.R3) AND
(Actor.CustNo <> '0') AND
(Actor.CreDt >= '20180901') AND
(Actor.CreDt <= '20190430') AND
AcTr.AcYr = '2018' AND
AcTr.AcPr <= '8' AND
AcTr.AcNo>='3000' AND
AcTr.AcNo <= '4999'
GROUP BY AcTr.R3, Actor.Nm
ORDER BY Sales DESC
Welcome to the community. You have a good start, but future, it is more helpful if you can provide (as commented), the CREATE table declarations so users know the actual data types. Not always required, but helps.
As for your query layout, it is more common to show the JOIN syntax instead of WHERE showing relations between tables, but that comes in time and practice.
Indexes help and should be based on a combination of both WHERE/JOIN criteria AND Grouping fields. Also, if fields are numeric, then do not 'quote' them, just leave as numbers. For example, your AcYr, AcPr, AcNo. I would think that an account number really would be a string value vs number for accounting purposes.
I would suggest the following indexes on your tables
Table Index
Actr ( AcYr, AcPr, AcNo, R3 )
Actor ( CustNo, CreDt )
The Actr table I have the filtering criteria first and the R3 last to help optimize the GROUP BY. The Actor table by the customer number, then the CreDt (Create date??), and is it really a string, or is it a date field? If so, the date criteria would be something like '2018-09-01' and '2019-04-30'
select TOP 10
Actor.Nm,
PreSum.Sales,
PreSm.TB
from
( select
R3,
SUM(CASE WHEN AcTr.AcNo <= '3999'
THEN AcAm * -1 ELSE 0 END) AS Sales,
SUM( AcAm * -1) AS TB
from
Actr
where
AcTr.AcYr = 2018
AND AcTr.AcPr <= 8
AND AcTr.AcNo >= '3000'
AND AcTr.AcNo <= '4999'
GROUP BY
AcTr.R3 ) PreSum
JOIN Actor
on PreSum.R3 = Actor.CustNo
AND Actor.CustNo <> 0
AND Actor.CreDt >= '20180901'
AND Actor.CreDt <= '20190430'
order by
Sales DESC
Per latest inquiry / comment, wanting by year comparison and getting rid of the top 10 performers per a given time period.
select
Actor.Nm,
PreSum.Sales2018,
PreSum.Sales2019,
PreSum.TB2018,
PreSum.TB2019
from
( select
AcTr.R3,
SUM(CASE WHEN AcTr.AcYr = 2018
AND AcTr.AcNo <= '3999'
THEN AcAm * -1 ELSE 0 END) AS Sales2018,
SUM(CASE WHEN AcTr.AcYr = 2019 AND AcTr.AcNo <= '3999'
THEN AcAm * -1 ELSE 0 END) AS Sales2019,
SUM( CASE WHEN AcTr.AcYr = 2018
THEN AcAm * -1 else 0 end ) AS TB2018
SUM( CASE WHEN AcTr.AcYr = 2019
THEN AcAm * -1 else 0 end ) AS TB2019
from
Actr
where
AcTr.AcYr IN ( 2018, 2019 )
AND AcTr.AcPr <= 8
AND AcTr.AcNo >= '3000'
AND AcTr.AcNo <= '4999'
GROUP BY
AcTr.R3 ) PreSum
JOIN Actor
on PreSum.R3 = Actor.CustNo
AND Actor.CustNo <> 0
AND Actor.CreDt >= '20180901'
AND Actor.CreDt <= '20190430'
order by
Sales DESC

calculate YTD & Prev Year YTD

I want to calculate YTD (1st Jan 2016 to last date of a month) & Prev year YTD (1st Jan 2015 to last date of a month) for each Client.
Below is the SQL Query that i have attempted, but here i get two rows for each Client instead of 1 as I'm using 'CASE WHEN'.
My question is how can i get the result in just one row per Client instead of one row for YTD & another row for YTD-1 for each client?
SELECT [ClientName]
, (CASE WHEN YEAR([Purchase_Date]) = YEAR(GETDATE())-1 THEN (count(Activity)) end) AS 'YTD-1'
, (CASE WHEN YEAR([Purchase_Date]) = YEAR(GETDATE()) THEN (count(Activity)) end) AS 'YTD'
FROM Purchases
WHERE MONTH([Purchase_Date]) <= MONTH(GETDATE())
GROUP BY [ClientName], YEAR([Purchase_Date])
ORDER BY 1
Kindly Help!
Thanks,
Ramesh
Remove YEAR([Purchase_Date]) from the GROUP BY part.
Also, instead of:
(CASE WHEN YEAR([Purchase_Date]) = YEAR(GETDATE())-1 THEN (count(Activity)) end) AS 'YTD-1'
use:
count(CASE WHEN YEAR([Purchase_Date]) = YEAR(GETDATE())-1 THEN Activity else NULL end) AS 'YTD-1'
And the same for 'YTD' column.

Rewrite SQL query to return rows for each dataset

I have the following table for student's fee payments
[fee_id] INT
[user_id] INT
[payment] DECIMAL (18, 2)
[date] DATETIME
[fee_remaining] DECIMAL (18, 2)
[year] INT
[payment_method] NVARCHAR (50)
[fee_required] DECIMAL (18, 2)
This is my current query to display the number of students who have either paid, yet to pay or have partially paid their fees for the year
SELECT DISTINCT
(SELECT COUNT(*) AS Expr1
FROM fee_payments
WHERE (fee_remaining = 0)
AND (YEAR = #year)) AS Fully_Paid,
(SELECT COUNT(*) AS Expr1
FROM fee_payments
WHERE (fee_remaining = fee_required)
AND (YEAR = #year)) AS Unpaid,
(SELECT COUNT(*) AS Expr1
FROM fee_payments
WHERE (fee_remaining > 0)
AND (YEAR = #year)
AND (fee_remaining <> fee_required)) AS Partially_Paid
FROM fee_payments AS fee_payments_1
This is my output
Fully_Paid | Unpaid | Partially_Paid
-------------------------------------
8 | 1 | 5
Is it at all possible to have my output displayed as follows?
Status | Total
----------------------------
Fully Paid | 8
Unpaid | 1
Partially Paid | 5
Any help would be greatly appreciated
Use a case expression to assign the required status to each row and group by the calculated column.
select status, count(*) as total
from (
SELECT
case when fee_remaining = 0 then 'fully_paid'
when fee_remaining <> fee_required then 'partially_paid'
when fee_remaining = fee_required then 'unpaid'
end as status
FROM fee_payments
WHERE YEAR = #year) t
group by status
Also note this assumes fee_remaining and fee_required are non null values. If they can be null, use coalesce to handle them when comparing.
So without completely restructuring your query into something more efficient like kvp's answer, you could UNION each of the results instead of using them each as a sub-query:
SELECT 'Fully Paid' AS Status, COUNT(*) AS Total
FROM fee_payments
WHERE (fee_remaining = 0) AND (YEAR = #year)
UNION
SELECT 'Unpaid', COUNT(*)
FROM fee_payments
WHERE (fee_remaining = fee_required) AND (YEAR = #year)
UNION
SELECT 'Partially Paid', COUNT(*)
FROM fee_payments
WHERE (fee_remaining > 0) AND (YEAR = #year) AND (fee_remaining <> fee_required)
Your code appears to have more than one row per year. It seems like the last row would be the most informative, so I'm thinking something like this:
select sum(case when fee_remaining = 0 then 1 else 0 end) as FullyPaid,
sum(case when fee_remaining < fee_required then 1 else 0 end) as PartiallyPaid,
sum(case when fee_remaining = fee_required then 1 else 0 end) as Unpaid
from (select fp.*,
row_number() over (partition by user_id order by date desc) as seqnum
from fee_payments fp
where YEAR = #year
) fp
where seqnum = 1;
SELECT 'Fully Paid' as Status, COUNT(*) AS Total
FROM fee_payments
GROUP BY year
WHERE fee_remaining = 0
AND YEAR = #year
UNION ALL
SELECT 'Unpaid' as Status, COUNT(*) AS Total
FROM fee_payments
GROUP BY year
WHERE fee_remaining = fee_required
AND YEAR = #year
UNION ALL
SELECT 'Partially Paid' as Status, COUNT(*) AS Total
FROM fee_payments
GROUP BY year
WHERE fee_remaining > 0
AND YEAR = #year
AND fee_remaining <> fee_required