SQL Server error simplify code due to multiple joins - sql

I need an alternative way to simplify this code.
This code computes the age of the balances of the client depending on the repayment schedule of loan. The filters are 1-7 days, 8-30, 31-60 .... and so on until it reach 181 and above
with membersWithLoans as -- gets members with loan
(
select
a.memberid, a.loanid, a.loanamt, a.intamt
from
loanmst a
where
loandt <= '12/19/2016'
and status = 'O'
)
,selectPaymentToDate as -- gets payments of the members to date
(
select
b.loanid, sum(a.princollamt) as princollamt1,
sum(a.intcollamt) as intcollamt1
from
collectiondtl a
inner join
membersWithLoans b on a.loanid = b.loanid
where
a.accdate <= '12/19/2016'
group by
b.loanid)
,selectBalanceToDate as -- gets the balance of member to date
(
select
b.loanid,
sum(a.princeamt) as prinBalanceToDate,
sum(a.instamt) as intBalanceToDate,
sum(a.insamt) as insuBalanceToDate
from
loandtl a
inner join
membersWithLoans b on a.loanid = b.loanid
where
a.duedt <= '12/19/2016'
group by
b.loanid)
, combineBalanceWithpayment as -- combine payment and balance
(
select a.loanid,a.loanamt, a.intamt,
(case
when b.prinBalanceToDate is null then 0
else b.prinBalanceToDate end) as prinBalanceToDate2,
(case
when b.intBalanceToDate is null then 0
else b.intBalanceToDate end) as intBalanceToDate2,
(case
when b.insuBalanceToDate is null then 0
else b.insuBalanceToDate end) as insuBalanceToDate2,
(case
when c.princollamt1 is null then 0
else c.princollamt1 end) as PrincipalCollectiontoDate,
(case
when c.intcollamt1 is null then 0
else c.intcollamt1 end) as IntCollectiontoDate,
cast(((case
when b.prinBalanceToDate is null then 0
else b.prinBalanceToDate
end)
-
(case
when c.princollamt1 is null then 0
else c.princollamt1 end))as decimal(10,2)) as Arrears
from
membersWithLoans a
left join selectBalanceToDate b
on a.loanid=b.loanid
left join selectPaymentToDate c
on a.loanid=c.loanid
)
,filterNegativeArrears as
(
select *
from
combineBalanceWithpayment
where Arrears > 0
)
the code above gets the member information
,select1To7days as -- this code gets amount to be paid in a specific schedule
(
select b.loanid,
sum((case
when a.princeamt is null then 0
else a.princeamt end))as prin7Daysbalance
from loandtl a
inner join membersWithLoans b
on a.loanid=b.loanid
where
a.duedt > DATEADD(day,-7,'12/19/2016')
and
a.duedt<='12/19/2016'
group by b.loanid
)
,select8to30days as -- this code gets amount to be paid in a specific schedule
(
select b.loanid,
sum((case
when a.princeamt is null then 0
else a.princeamt end))as prin8To30Daysbalance
from loandtl a
inner join membersWithLoans b
on a.loanid=b.loanid
where
a.duedt<=DATEADD(day,-7,'12/19/2016')
and a.duedt > DATEADD(day,-30,'12/19/2016')
group by b.loanid
)
-- and so on ..... the filters for schedule is compose of 31 to 60days, 61 to 90 days,
--121 to 180 days, 181 and above. there is no pattern since it the requirement on days may change
, computePar1To7days as -- computes the 1 to 7 days
(
select a.loanid, cast((a.arrears - a.Par1To7days) as decimal(10,2)) as deductedArrears, a.Par1To7days
from
(
select a.loanid,a.arrears,
cast((case
when a.arrears >= b.prin7Daysbalance then b.prin7Daysbalance -- if the arrears is greater than the 7days balance to be collected then it will be show
else a.arrears end)as decimal(10,2))as Par1To7days -- else the remaining is the arrears
from
filterNegativeArrears a
left join select1To7days b
on a.loanid=b.loanid
) a
where cast((a.arrears - a.Par1To7days) as decimal(10,2)) > 0
)
,computePar8To30days as -- computes the 8 to 30 days
(
select a.loanid, cast((a.arrears - a.Par8To30days)as decimal(10,2)) as deductedArrears, a.Par8To30days
from
(
select a.loanid, a.deductedArrears as arrears,
cast((case
when (a.deductedArrears) > 0
then
(case
when (a.deductedArrears)>= b.prin8To30Daysbalance
then b.prin8To30Daysbalance
else (a.deductedArrears)
end)
else 0 end)as decimal(10,2))as Par8To30days
from computePar1To7days a
left join select8To30days b
on a.loanid=b.loanid
) a
where cast((a.arrears - a.Par8To30days) as decimal(10,2)) > 0
)
-- so on until all par is computed. 31 to 60 days, 61 to 90 days,
--121 to 180 days, 181 and above. there is no pattern since it the requirement on days may change
the code above gets the sum of data from specific schedules like
1 to 7 days, 8-30 days, 31 to 60 days, 61 to 90 days, 121 to 180 days, 181 and above
select a.*,
b.Par1To7days,
c.Par8To30days,
d.Par31To60days,
e.Par61To90days,
f.Par91To120days,
g.Par121To180days --,
--h.Par181AndAbovedays
from
filterNegativeArrears a
left join computePar1To7days b
on a.loanid=b.loanid
left join computePar8To30days c
on a.loanid=c.loanid
left join computePar31To60days d
on a.loanid=d.loanid
left join computePar61To90days e
on a.loanid=e.loanid
left join computePar91To120days f
on a.loanid=f.loanid
left join computePar121To180days g
on a.loanid=g.loanid
--left join computePar181AndAbovedays h
-- on a.loanid=h.loanid
the code above joins the computed age
The code is working fine and calculating fine
but when I add more join in the selection I get an error
left join computePar181AndAbovedays h
on a.loanid=h.loanid
The problem is I started encountering the error:
An expression services limit has been reached. Please look for
potentially complex expressions in your query, and try to simplify
them.
I still need more table to join with my query.
Can you suggest ways to simplify this query is highly appreciated

You are obviously running into a limit there and budging that limit will not be possible with the query you have. Follow the advice given in the error message: simplify.
How? Well you have a multitude of CTE's defined. Another way of writing your query is by materializing the CTE's in temporary tables before the actual query and then using the temporary tables instead.
For example, this CTE:
membersWithLoans as -- gets members with loan
(
select a.memberid, a.loanid,a.loanamt,a.intamt
from loanmst a
where loandt<='12/19/2016'
and status = 'O'
)
Can be be materialized to a temporary table:
select a.memberid, a.loanid,a.loanamt,a.intamt
into #membersWithLoans
from loanmst a
where loandt<='12/19/2016'
and status = 'O'
This will create a temporary table #membersWithLoans that can be used in further temporary table creations or in your final query.
To illustrate further, suppose you have materialized all your CTE's to temporary tables, you would have no more WITH clause. You would then finally use the temporary tables in your final SELECT query:
-- create all temporary tables (one of them being #filterNegativeArrears)
select
a.*,
-- the rest of your selections
from
#filterNegativeArrears a
-- the rest of the joined temporary tables
-- the rest of your query (WHERE, ORDER BY etc)

Related

How to do this query using self join or anything but without using window function

Below is the solution but I want to know other ways to accomplish the same results (preferably in PostgreSQL).
This is the DB
Question - How many customers have churned straight after their initial free trial? what percentage is this rounded to the
nearest whole number?
WITH ranking AS (
SELECT
s.customer_id,
s.plan_id,
p.plan_name,
ROW_NUMBER() OVER (
PARTITION BY s.customer_id
ORDER BY s.plan_id) AS plan_rank
FROM dbo.subscriptions s
JOIN dbo.plans p
ON s.plan_id = p.plan_id)
SELECT
COUNT(*) AS churn_count,
ROUND(100 * COUNT(*) / (
SELECT COUNT(DISTINCT customer_id)
FROM dbo.subscriptions),0) AS churn_percentage
FROM ranking
WHERE plan_id = 4 -- Filter to churn plan
AND plan_rank = 2
You can achieve the same results with a single aggregation on customer_id with a few CASE WHEN statements:
SELECT count(*) as total_customers
,count(case when total_subscriptions = 2
and includes_free = 1
and includes_churn = 1 then 1 end) as churn_count
,100 * count(case when total_subscriptions = 2
and includes_free = 1
and includes_churn = 1 then 1 end) / count(*) as target_percent
FROM (
SELECT customer_id
,count(*) as total_subscriptions
,max(case when plan_id = 0 then 1 else 0 end) as includes_free
,max(case when plan_id = 4 then 1 else 0 end) as includes_churn
FROM dbo.subscriptions
GROUP BY customer_id
) AS tbl
-- Remove any records for people who didnt use the free trial
-- or people who are still on the free trial
WHERE includes_free = 1 AND total_subscriptions > 1
The difference between our solutions are:
Yours doesn't specify that the customer actually had a free trial
Mine doesn't include customers who went from Free -> Churn -> (something else)
Depending on your requirements you might want to make further alterations/use a different approach.

Change sub query to join

all I have used subquery[Below] to identify the percentage. But I need a query without subquery. Can anyone please help me, how to use joins to calculate the percentage?
Query used
SELECT 'Dropping_Percentage',
( Cast(dropped_count AS DECIMAL(16, 9)) / Cast(new_count AS DECIMAL(16, 9
)) ) *
100
FROM (SELECT count AS New_count,
'1' a
FROM new_count)a,
(SELECT Count(*) Dropped_count,
'1' b
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o
ON o.id_membid_claimid_c = N.id_membid_claimid_c
WHERE N.id_membid_claimid_c IS NULL)c
WHERE a.a = c.b
"I need a query without subquery."
Answer:
SELECT Count(*) All_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) as Dropped_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) * 1.0 / Count(*) as Dropping_Percentage
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o ON o.id_membid_claimid_c = N.id_membid_claimid_c
Explanation:
Reviewing the main query with assumptions about the content:
SELECT Count(*) Dropped_count, '1' b
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o ON o.id_membid_claimid_c = N.id_membid_claimid_c
WHERE N.id_membid_claimid_c IS NULL
should give you the number of records in pfo_bhi_old that do not appear in pfo_bhi_new. Assumption here is that you need to do the total based on the existing right join. All the matching and non matching records.
Therefore It's possible to count all the existing records by removing the where clause,
COUNT(*) would give you that total.
Next you want to count the ones where there's no match which will give you the "Dropping count" the value you had before with the where clause, that is where ones where the id_membid_claimid_c was null that is SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end). Said differently it will add 1 to the sum only when the id_membid_claimid_c field is null, otherwise it will add zero (0).
I've multiplied the numerator by 1.0 to force SQL Server to use decimal values and make the query easier to read.
Here's what it should look like if you needed to use decimals(16,9) as the result.
SELECT Count(*) All_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) as Dropped_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) * 1.0 / Count(*) as Dropping_Percentage
, CAST(SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) AS DECIMAL(16,9)) / CAST(Count(*) AS DECIMAL(16,9)) as Dropping_Pct_16_9
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o ON o.id_membid_claimid_c = N.id_membid_claimid_c

SQL Server - Matrix of multiple WHERE queries

If I have a table like below:
Source Event Date Qty
Site A Create Account 5/05/2018 6
Site B Create Account 4/05/2018 12
Site A Update Account 6/05/2018 1
Site A Update Notes 7/05/2018 2
Site B Add Dependant 5/05/2018 1
Site C Create Account 5/05/2018 14
And another like this:
Date OrdersRec
4/05/2018 162
5/05/2018 123
6/05/2018 45
7/05/2018 143
And want to build a sort of matrix table to be able to use again and again for various date ranged queries, like this:
Date Create Update UpdateNotes AddDependant OrdersRec
4/05/2018 12 0 0 0 162
5/05/2018 20 0 0 1 123
6/05/2018 0 1 0 0 45
7/05/2018 0 0 2 0 143
What would be the best way of doing so?
I started with an INSERT into for the dates and the CreateAccount numbers, but I'm not sure how to go back and update the other counts for particular dates, or even if thats stupid and I'm missing something obvious.
Thanks!
Just use union all or join. There is no reason to create a new table for this:
select date, sum(v_create) as v_create, sum(v_update) as v_update,
sum(v_updatenotes) as v_updatenotes, sum(v_adddependent) as v_adddependent,
sum(orderrec) as orderec
from ((select t1.date, v.*, 0 as OrdersRec
from table1 t1 cross apply
(values ( (case when event = 'Create Account' then quantity else 0 end),
(case when event = 'Update Account' then quantity else 0 end),
(case when event = 'Update Notes' then quantity else 0 end),
(case when event = 'Add Dependent' then quantity else 0 end)
)
) v(v_create, v_update, v_updatenotes, v_adddependent)
) union all
(select date, 0, 0, 0, 0, orderrec
from table2
)
) t
group by date
order by date;
This handles a lot of edge cases, such as when dates are missing from one or the other tables.
Why don't you create a variable table having all the dates, and use it as your main table.
Then you would so something like this
select m.date,
sum(case when event = 'Create Account' then 1 else 0) as create,
sum(case when event = 'Update Account' then 1 else 0) as updates,
sum(o.ordersrec) as orders
from #main as m
inner join #orders as o on o.date = m.date
inner join #events as e on e.date = m.date
group by m.date

DAX - count attributes for which FACT records do not exist

I am looking for a DAX measure that is equal to If i have in SQL:
SELECT COUNT(NoDataValue WHEN -1 THEN 1 ELSE 0 END)
FROM
(
SELECT Employee.EmployeeID, CASE WHEN FACT.EmployeeID IS NULL
THEN -1
ELSE 1
END as NoDataValue
FROM Employee LEFT OUTER JOIN FACT
On Employee.EmployeeID = FACT.EmployeeID
) X
Essentially i want -1 when there is no data for an employee at the row level but when its aggregated i need count of NoDataValues (How many employees did not have data).
That is working fine at employee level with the measure i created
Annual Independence Compliance No Data:=
  Var NoData=
  SUM ( [NoDataValue] )
RETURN (IF (ISBLANK(NoData) , -1, NoData))
This looks like
But this is not aggregating the counts. I am having trouble of how to do that. This shows up as
I'm not sure if you SQL query is returning what you want but the query should look like below
SELECT SUM(case NoData WHEN -1 THEN 1 ELSE 0 END) as NoDataCount
FROM
(
SELECT Employee.EmployeeID, CASE WHEN FACT.EmployeeID IS NULL
THEN -1
ELSE 1
END as NoData
FROM Employee LEFT OUTER JOIN FACT
On Employee.EmployeeID = FACT.EmployeeID
) X
or
SELECT SUM(IIF(NoData = -1,1,0)) as NoDataCount

SQL query syntax errors when dealing with subpopulations

I'm a software tester using this SQL query to check some data in our db:
select * from (
select sum(case when qm.HEALTHY_TERM_NEWBORN='E' then 1 else 0 end) as num, -- choose measure status columns here as appropriate
sum(case when qm.HEALTHY_TERM_NEWBORN in ('D','E') then 1 else 0 end) as denom,
-- summary level columns
cms.FACILITY_GROUP_ID,
-- time period columns
dt.USA_FISCAL_YEAR_BEGIN_DT
from F_MU_QM_EH_2014_IP_ADMSN qm
inner join CMS_MU_INFO cms on qm.DISCHARGE_CMS_MU_ID=cms.CMS_MU_ID
inner join DATE_DIMENSION dt on dt.CALENDAR_DT=qm.DISCHARGE_DATE
-- do filtering here if any
where cms.FACILITY_GROUP_ID='130170' AND dt.USA_FISCAL_YEAR_BEGIN_DT='10/01/2013'
group by -- summary level grouping
cms.FACILITY_GROUP_ID,
-- time period grouping
dt.USA_FISCAL_YEAR_BEGIN_DT
) x
where denom is not null
The script returns aggregated data from one column of information; however, for some of my testing I need to filter the column based on another column that defines a subpopulation within the first column. I tried this:
select * from (
sum(case when qm.SCIPINF1='E' and qm.SCIPINF1_POPULATION_C=7 then 1 else 0 end) as num
sum(case when qm.SCIPINF1 in ('D','E') and qm.SCIPINF1_POPULATION_C=1 then 1 else 0 end) as denom
-- summary level columns
cms.FACILITY_GROUP_ID,
-- time period columns
dt.USA_FISCAL_YEAR_BEGIN_DT
from F_MU_QM_EH_2014_IP_ADMSN qm
inner join CMS_MU_INFO cms on qm.DISCHARGE_CMS_MU_ID=cms.CMS_MU_ID
inner join DATE_DIMENSION dt on dt.CALENDAR_DT=qm.DISCHARGE_DATE
-- do filtering here if any
where cms.FACILITY_GROUP_ID='130170' AND dt.USA_FISCAL_YEAR_BEGIN_DT='10/01/2013'
group by -- summary level grouping
cms.FACILITY_GROUP_ID,
-- time period grouping
dt.USA_FISCAL_YEAR_BEGIN_DT
) x
where denom is not null
But I'm getting a syntax error near 'sum' according to Oracle, and I'm missing a parenthesis according to MS SQL Server Management Studio. Do any of you beautiful internet people know what I'm doing wrong?
Missing comma between two columns and also select in sub-select
SELECT *
FROM (SELECT Sum(CASE -- select missing here
WHEN qm.SCIPINF1 = 'E'
AND qm.SCIPINF1_POPULATION_C = 7 THEN 1
ELSE 0
END) AS num, --comma missing here
Sum(CASE
WHEN qm.SCIPINF1 IN ( 'D', 'E' )
AND qm.SCIPINF1_POPULATION_C = 1 THEN 1
ELSE 0
END) AS denom, --comma missing here
-- summary level columns
cms.FACILITY_GROUP_ID,
-- time period columns
dt.USA_FISCAL_YEAR_BEGIN_DT