Multiple AND conditions in case statement - sql

I've got an Oracle table CUST with Fields YEAR and STATUS. I'm trying to write a statement that will select the records where ((CUST.YEAR = '2017' and CUST.STATUS = 'ACTIVE') and where (CUST.YEAR = '2018' and CUST.STATUS = 'ACTIVE')). When both of these statements are true, I want to return '1' else '0'
select *
from cust
where cust.year = '2017' and cust.status = 'Active'
returns the correct number of rows (394).
select *
from cust
where cust.year = '2018' and cust.status = 'Active'
returns the correct number of rows (451).
Here's where the wheels fall off (due to my inexperience with SQL scripting). I've tried to combine the two select statements and I either gets tens of thousands of rows or errors because of incorrect syntax. This is before even attempting to use a case statement to return a comparative result (if both of these conditions, then '1' else '0').
I realize this is probably pretty elementary stuff but the syntax is beyond me as of now. Would someone be kind enough to help me construct this statement?
The few times I've posted to this forum I've learned things that help to make me more self-sufficient, so I offer my thanks in advance.

You can take advantage of IN here:
select *
from cust
where cust.year IN ('2017', '2018') and
cust.status = 'Active'

The way I understood it, or might be what you're looking for, i.e.
select *
from cust
where (cust.year = '2017' and cust.status = 'Active')
or (cust.year = '2018' and cust.status = 'Active');
which - as William says - leads to
where cust.status = 'Active'
and cust.year in ('2017', '2018')

If I have understood your requirement correctly you want to determine whether your table has records for both pairs of conditions, that is , whether you have active records for both 2017 and 2018. The solutions provided so far will assert whether either condition is true but not both.
So here is a solution which satisfies your actual requirement. We have a WITH clause which selects one active record for each year (which is all your need). The inline view then counts how many records it found. If the count is two then you have active records for both years.
with tst as (
select cust.cust_id, cust.year
from cust
where cust.year = '2017'
and cust.status = 'Active'
group by cust.cust_id, cust.year
union all
select cust.cust_id, cust.year
from cust
where cust.year = '2018'
and cust.status = 'Active'
group by cust.cust_id, cust.year
)
select cust_id
, case when cnt = 2 then 1 else 0 end as your_condition
from ( select cust_id, count(*) as cnt
from tst
group by cust_id )
/

Related

SUM with left outer join gets inflated result

The following query gives me the MRR (monthly recurring revenue) for my customer:
with dims as (
select distinct subscription_id, country_name, product_name from revenue
where site_id = '18XLsHIVSJg' and subscription_id is not null
)
select to_date('2022-07-01') as occurred_date,
count(distinct srm.subscription_id) as subscriptions,
count(distinct srm.receiver_contact) as subscribers,
sum(srm.baseline_mrr) as mrr_srm
from subscription_revenue_mart srm
join dims d on d.subscription_id = srm.subscription_id
where srm.site_id = '18XLsHIVSJg'
-- MRR as of the day before ie June 30th
and to_date(srm.creation_date) < '2022-07-01'
-- Counting the subscriptions active after July 1st
and ((srm.subscription_status = 'SUBL.A') or
-- Counting the subscriptions canceled/deactivated after July 1st
(srm.subscription_status = 'SUBL.C' and (srm.deactivation_date >= '2022-07-01') or (srm.canceled_date >= '2022-07-01')) ) group by 1;
I get a total of $5922.15 but I need to add data from another table to capture upgrades/downgrades a customer makes on a product subscription. Using the same approach as above, I can query my "change" table thusly:
select subscription_id, sum(mrr_change_amount) mrr_change_amount,max(subscription_event_date) subscription_event_date from subscription_revenue_mart_change srmc
where site_id = '18XLsHIVSJg'
and to_date(srmc.creation_date) < '2022-07-01'
and ((srmc.subscription_status = 'SUBL.A')
or (srmc.subscription_status = 'SUBL.C' and (srmc.deactivation_date >= '2022-07-01') or (srmc.canceled_date >= '2022-07-01')))
group by 1;
I get a total of $3635.47
When I combine both queries into one, I get an inflated result:
with dims as (
select distinct subscription_id, country_name, product_name from revenue
where site_id = '18XLsHIVSJg' and subscription_id is not null
),
change as (
select subscription_id, sum(mrr_change_amount) mrr_change_amount,
-- there can be multiple changes per subscription
max(subscription_event_date) subscription_event_date from subscription_revenue_mart_change srmc
where site_id = '18XLsHIVSJg'
and to_date(srmc.creation_date) < '2022-07-01'
and ((srmc.subscription_status = 'SUBL.A')
or (srmc.subscription_status = 'SUBL.C' and (srmc.deactivation_date >= '2022-07-01') or (srmc.canceled_date >= '2022-07-01')))
group by 1
)
select to_date('2022-07-01') as occurred_date,
count(distinct srm.subscription_id) as subscriptions,
count(distinct srm.receiver_contact) as subscribers,
-- See comment RE: LEFT OUTER join
sum(coalesce(c.mrr_change_amount,srm.baseline_mrr)) as mrr
from subscription_revenue_mart srm
join dims d
on d.subscription_id = srm.subscription_id
-- LEFT OUTER join required for customers that never made a change
left outer join change c
on srm.subscription_id = c.subscription_id
where srm.site_id = '18XLsHIVSJg'
and to_date(srm.creation_date) < '2022-07-01'
and ((srm.subscription_status = 'SUBL.A')
or (srm.subscription_status = 'SUBL.C' and (srm.deactivation_date >= '2022-07-01') or (srm.canceled_date >= '2022-07-01'))) group by 1;
It should be $9557.62 ie (5922.15 + $3635.47) but the query outputs $16116.91, which is wrong.
I think the explode-implode syndrome may cause this.
I had designed my "change" CTE to prevent this by aggregating all the relevant fields but it's not working.
Can someone provide pointers on the best way to work around this issue?
It would help if you gave us sample data too, but I see a problem here:
sum(coalesce(c.mrr_change_amount,srm.baseline_mrr)) as mrr
Why COALESCE? That will give you one of the 2 numbers, but I guess what you want is:
sum(ifnull(c.mrr_change_amount, 0) + srm.baseline_mrr) as mrr
That's the best I can offer with what you've given us.

SQL query for extracting accounts whose last load date is not equal to today

I seem to have some issue in the query and need your help.
I have 2 tables:
1st table contains Bank account details - account number, status etc - bankacc
2nd table stores name of the statement and the load date on which the statement is imported - bankstm
I am trying to write a query that will populate only those bank accounts whose statement was not imported as of today date.
Date format in database - 2020-01-17 00:00:00.000
Code that i have tried:
SELECT b.bank_acc as Bank_Account, max(b.date_ld) as Load_Date from bankstm b
where b.date_ld < CAST(GETDATE() AS DATE) and
b.bank_acc in (select a.acc_no from bankacc a where a.in_use = 'Y' and a.analyse03 = '1517')
group by b.bank_acc
This code populates all the records from previous date whereas most of them statements loaded today.
I also attempted the code with '=' or '<>' or '>' based on the queries raised previously in stack overflow. But nothing seems to be giving me the correct result.
So finally i am raising it for experts to help me out.
You need to apply the date filter on the max.
I cast the max(b.date_ld) to date in case its datetime format
SELECT b.bank_acc as Bank_Account, max(b.date_ld) as Load_Date from bankstm b
where
b.bank_acc in (select a.acc_no from bankacc a where a.in_use = 'Y' and a.analyse03 = '1517')
group by b.bank_acc
having cast(max(b.date_ld) as date) < CAST(GETDATE() AS DATE)
You can modify your statement to use a not exists if your only criteria is that the record doesn't have a corresponding entry for today's date as a load date.
If the criteria is different, may require modification.
SELECT [b].[bank_acc] AS [bank_account]
, MAX([b].[date_ld]) AS [load_date]
FROM bankstm AS b
WHERE NOT EXISTS
(
SELECT 1
FROM [bankstm] AS [bb]
WHERE [b].[bank_acc] = [bb].[bank_acc] AND
TRY_CONVERT(DATE, [bb].[date_ld]) = TRY_CONVERT(DATE, GETDATE())
)
AND EXISTS
(
SELECT 1
FROM [bankacct] a
WHERE b.bank_acc = a.bank_acc and a.in_use = 'Y' and a.analyse03 = '1517'
)
GROUP BY b.bank_acc
;
first of all you can improve your query with join and avoid using sub query.
SELECT b.bank_acc as Bank_Account, max(b.date_ld) as Load_Date
FROM bankstm AS b
LEFT JOIN bankacc AS ba ON b.bank_acc = ba.acc_no
WHERE ba.in_use = 'Y'
AND ba.analyse03 = '1517'
GROUP BY b.bank_acc
HAVING CAST(MAX(b.date_ld) AS DATE) < CAST(GETDATE() AS DATE)
I would use not exists:
select ba.*
from bankacc ba
where ba.in_use = 'Y' and
ba.analyse03 = '1517' and
not exists (select 1
from bankstm bs
where bs.bank_acc = ba.acc_no and
bs.date_ld = convert(date, getdate())
);
For performance, you want indexes on bankacc(in_use, analyse03, acc_no) and bankstm(bank_acc, date_ld).

Dividing counts on db2?

Hello I have a query where I am pulling the count of new hires who have downloaded our app and the count of new hires.
I am trying to divide the two to find the percentage of those who have downloaded the app
but whenever I run my query my result is 0
both data and driver_id are varchar
here is my query
SELECT ROUND(COUNT(DATA) / (
SELECT COUNT(D2.DRIVER_ID)
FROM DRIVER D2
WHERE ACTIVE_IN_DISP = 'True'
AND START_DATE >= '10/1/2015'
), 4)
FROM CUSTOM_DATA C
,DRIVER D
WHERE CUSTDEF_ID = '50'
AND SRC_TABLE_KEY = DRIVER_ID
AND ACTIVE_IN_DISP = 'True'
AND START_DATE >= '10/1/2015'
thanks in advance!
Problem is you are getting truncate to INT
convert your data to float before the division
SELECT ROUND( COUNT(DATA) *1.0 /
(SELECT COUNT(D2.DRIVER_ID)
....
Just use conditional aggregation. It is easier and less prone to error:
SELECT ROUND(AVG(CASE WHEN CUSTDEF_ID = '50' THEN 1.0 ELSE 0
END), 4)
FROM CUSTOM_DATA C JOIN
DRIVER D
ON SRC_TABLE_KEY = DRIVER_ID
WHERE ACTIVE_IN_DISP = 'True' AND
START_DATE >= '2015-10-01';
Not having to repeat the logic in the WHERE using a subquery makes the query much less prone to error. Plus, it is probably faster to get rid of the subquery as well.

Work Around for SQL Query 'NOT IN' that takes forever?

I am trying to run a query on an Oracle 10g DB to try and view 2 groups of transactions. I want to view basically anyone who has a transaction this year (2014) that also had a transaction in the previous 5 years. I then want to run a query for anyone who has a transaction this year (2014) that hasn't ordered from us in the last 5 years. I assumed I could do this with the 'IN' and 'NOT IN' features. The 'IN' query runs fine but the 'NOT IN' never completes. DB is fairly large which is probably why. Would love any suggestions from the experts!
*Notes, [TEXT] is a description of our Customer's Company name, sometimes the accounting department didn't tie this to our customer ID which left NULL values, so using TEXT as my primary grouping seemed to work although the name is obscure. CODE_D is a product line just to bring context to the name.
Below is my code:
SELECT CODE_D, sum(coalesce(credit_amount, 0) - coalesce(debet_amount,0)) as TOTAL
FROM
gen_led_voucher_row_tab
WHERE ACCOUNTING_YEAR like '2014'
and TEXT NOT IN
(select TEXT
from gen_led_voucher_row_tab
and voucher_date >= '01-JUN-09'
and voucher_date < '01-JUN-14'
and (credit_amount > '1' or debet_amount > '1')
)
GROUP BY CODE_D
ORDER BY TOTAL DESC
Try using a LEFT JOIN instead of NOT IN:
SELECT t1.CODE_D, sum(coalesce(t1.credit_amount, 0) - coalesce(t1.debet_amount,0)) as TOTAL
FROM gen_led_voucher_row_tab AS t1
LEFT JOIN gen_led_voucher_row_tab AS t2
ON t1.TEXT = t2.TEXT
AND t2.voucher_date >= '01-JUN-09'
AND t2.voucher_date < '01-JUN-14'
AND (credit_amount > '1' or debet_amount > '1')
WHERE t2.TEXT IS NULL
AND t1.ACCOUNTING_YEAR = '2014'
GROUP BY CODE_D
ORDER BY TOTAL DESC
ALso, make sure you have an index on the TEXT column.
You can increase your performance by changing the Not In clause to a Where Not Exists like as follows:
Where Not Exists
(
Select 1
From gen_led_voucher_row_tab b
Where voucher_date >= '01-JUN-09'
and voucher_date < '01-JUN-14'
and (credit_amount > '1' or debet_amount > '1')
And a.Text = b.Text
)
You'll need to alias the first table as well to a for this to work. Essentially, you're pulling back a ton of data to just discard it. Exists invokes a Semi Join which does not pull back any data at all, so you should see significant improvement.
Edit
Your query, as of the current update to the question should be this:
SELECT CODE_D,
sum(coalesce(credit_amount, 0) - coalesce(debet_amount,0)) as TOTAL
FROM gen_led_voucher_row_tab a
Where ACCOUNTING_YEAR like '2014'
And Not Exists
(
Select 1
From gen_led_voucher_row_tab b
Where voucher_date >= '01-JUN-09'
and voucher_date < '01-JUN-14'
and (credit_amount > '1' or debet_amount > '1')
And a.Text = b.Text
)
GROUP BY CODE_D
ORDER BY TOTAL DESC

Cross referencing results from query's

I need to get an active count of patients who have been discharged in a given time frame, but here is the trick. In this table patients can show up more than once, our system uses an episode based system .
Like in (picture 1).
select
p.patient_id,
p.episode_id,
p.case_status,
p.case_substatus,
p.episode_close_date
from patient p
I need no patients to show up in both of these query's.
select * from patient p
where p.case_status = 'a'
-
select * from patient p
where (p.episode_close_date between '2013-01-01 00:00:00.000' and '2013-06-01 00:00:00.000') and p.case_status = 'i'
I guess, what's the best way to do it, would be that that the highest p.episode_id = 'I'. Any ideas on how to do this?
Thanks in advance.
You can do this with aggregation and a having clause. The having clause counts the number of rows that match each condition -- and you want to set the values to 0 because you want both to return no rows:
select patient_id
from patient p
group by patient_id
having sum(case when p.case_status = 'a' then 1 else 0 end) = 0 and
sum(case when (p.episode_close_date between '2013-01-01 00:00:00.000' and '2013-06-01 00:00:00.000') and
p.case_status = 'i'
then 1 else 0
end) = 0;
Basically, the logic in your first two queries in moved into the separate clauses of the having, to count the rows that match each condition.
EDIT:
Here is how you can see the last episode for each patient:
select p.*
from (select p.*,
max(episode_id) over (partition by patient_id) as maxei
from patients p
) p
where episode_id = maxei;
You may also be able to use this with your logic, but I'm not sure about the interplay between the statuses and the dates in the query.