Issues with counting records in secondary table based on complex criteria - sql

I need to be able to count the number of records in a secondary table tblOptyRecordsHistorical which are related to the main table tblOptyRecordsCurrent.
The tables are exactly the same, the main contains the current 'daily snapshot', the secondary table contains previous daily snapshots.
I have a number of flags which use the following basic syntax:
(SELECT COUNT(OpportunityRecordID) AS Expr1
FROM dbo.tblOptyRecordsHistorical AS hist
WHERE (OpportunityGlobalCRMId = curr.OpportunityGlobalCRMId))
AS prevEntries,
This works fine. But one flag, I need to count the number of records in the historical table, but the logic is more complicated and depends on values from the main table:
SELECT OpportunityGlobalCRMId,
(SELECT SUM(CASE WHEN curr.PartnerGlobalCRMID IS NULL THEN CASE WHEN
hist.IgnoreOpportunity != 0 THEN 1 ELSE 0 END ELSE CASE
WHEN curr.CustomerAccountID IS NULL THEN CASE WHEN hist.IgnoreOpportunity = 1 AND
hist.PartnerGlobalCRMID = curr.PartnerGlobalCRMID THEN 1 ELSE 0 END ELSE CASE WHEN
hist.IgnoreOpportunity = 1 AND CONVERT(varchar, hist.CustomerAccountID) +
hist.PartnerGlobalCRMID = CONVERT(varchar, curr.CustomerAccountID) +
curr.PartnerGlobalCRMID AND hist.OpptyIncentiveCreatedDate =
curr.OpptyIncentiveCreatedDate THEN 1 ELSE 0 END END END) AS Expr1 FROM
dbo.tblOptyRecordsHistorical AS hist WHERE (OpportunityGlobalCRMId =
curr.OpportunityGlobalCRMId)) AS prevIgnored
FROM dbo.tblOptyRecordsCurrent AS curr
I've omitted the other flags and fields except for the initial OpportunityGlobalCRMID. This results in the following error: Multiple columns are specified in an aggregated expression containing an outer reference. If an expression is being aggregated contains an outer reference, then that outer reference must be the only column referenced in the expression.

SQL Server does not like mixing of inner (hist table) and outer (curr table) in a aggregate subquery expression. Some explanation is available here.
The proposed solutuon is to re-include the outer table in the sub-query, joining on it's key, in order to make all references inner. In your case, that would mean putting the tblOptyRecordsCurrent table inside the subquery, like this:
SELECT OpportunityGlobalCRMId,
(SELECT SUM(CASE
WHEN curr2.PartnerGlobalCRMID IS NULL
THEN CASE WHEN hist.IgnoreOpportunity != 0 THEN 1 ELSE 0 END
ELSE CASE
WHEN curr2.CustomerAccountID IS NULL
THEN CASE
WHEN hist.IgnoreOpportunity = 1 AND hist.PartnerGlobalCRMID = curr2.PartnerGlobalCRMID THEN 1 ELSE 0 END
ELSE CASE
WHEN hist.IgnoreOpportunity = 1
AND CONVERT(varchar, hist.CustomerAccountID) + hist.PartnerGlobalCRMID
= CONVERT(varchar, curr2.CustomerAccountID) + curr2.PartnerGlobalCRMID
AND hist.OpptyIncentiveCreatedDate = curr2.OpptyIncentiveCreatedDate
THEN 1
ELSE 0
END
END
END) AS Expr1
FROM dbo.tblOptyRecordsHistorical AS hist
inner join dbo.tblOptyRecordsCurrent AS curr2 on curr2.OpportunityGlobalCRMId = hist.OpportunityGlobalCRMId
WHERE curr2.OpportunityGlobalCRMId = curr.OpportunityGlobalCRMId) AS prevIgnored
FROM dbo.tblOptyRecordsCurrent AS curr
Haven't tested the code however.

Related

Need to create a case statement for multiple criteria with varchar and date data types

Need to create a case statement for multiple criteria with varchar and date data types, my code sp far:
select
least(coalesce(case when 1.other_id=10 then 1.datestr else 0 end,
case when 2.info='Yes' then 2.date else 0 end))as date1,
from
test1 as 1
left join test2 as 2 on 1.id = 2.id
I essentially want to get the lowest date between 2 conditions. Both conditions have some filters (other_id=10 in first, info="Yes" in second). For those conditions I want to compare least dates.
I think this is the logic:
select (case when 1.other_id = 10 and 2.info = 'Yes'
then least(case when 1.other_id=10 then 1.datestr end,
case when 2.info='Yes' then 2.date end
)
when 1.other_id = 10 then 1.date
else 2.date
end)
Or, you can use more appropriate default values in your expression. Something like this (it might depend on the database):
select least(case when 1.other_id = 10 then 1.datestr else '9999-01-01' end,
case when 2.info = 'Yes' then 2.date else '9999-01-01' end)
) as date1,

Find What Column i Joined on when using OR

In My database I have a row with multiple Codes, EG:
ID,Code1,Code2,Code3,Code4
These codes reference a Procedure Name in another table EG:
Code1 = 'Procedure one'
Code2 = 'Procedure two'
ect.
I needed to convert this single row to show one line for every code, and corresponding procedure name EG:
ID,ProcedureName
1,'Procedure One'
1,'Procedure two'
2,'Procedure one'
To get this to work I'm using an outer join with an OR statement, not the most performance effective, but since the ProcedureName Table isn't that large, i'm not too fussed about overhead at the moment, more about getting it to work.
FROM Events EV
LEFT JOIN ProcedureName PN
ON (PN.CODE = Ev.Code1)
OR (PN.CODE = Ev.Code2)
OR (PN.CODE = Ev.Code3)
OR (PN.CODE = Ev.Code4)
This works, however Now I have the problem of being able to tell What procedure is the Primary, and Secondary. Usually the Primary/secondary is denoted purely by whatever one is in the first Code. IE the primary would be whatever is in Code1, secondary in code2, ect.
However since I have now Joined using an OR, i now have no idea what Code that the procedure has joined to.
I've thought of just doing a case statement
CASE
WHEN PN.CODE = Ev.Code1 THEN
'(Primary) ' + ISNULL(PN.NAME, '')
WHEN PN.CODE = Ev.Code2 THEN
'(Secondary) ' + ISNULL(PN.NAME, '')
WHEN PN.CODE = Ev.Code3 THEN
'(Tertiary) ' + ISNULL(PN.NAME, '')
WHEN PN.CODE = Ev.Code4 THEN
'(Quaternary) ' + ISNULL(PN.NAME, '')
END AS ProcedureName,
However this has the major issue of, on the off chance, that both code1 and code2 are the same code. Which means they will both show up as primary.
Can anyone give me any hints as to how to find out what the OR join actually Joined on? did it join on code1, code2? is there perhaps a better way to write the join that will allow me to have multiple lines per ID (depending on amount of codes) whilst still allowing me to find out where they are Code1 or code2?
I would reword the question slightly. In reality the it doesn't "join on a column", it joins on the result of a boolean expression.
So, what you want is to find out which parts of the boolean expression are true or not...
SELECT
*,
CASE WHEN PN.CODE = Ev.Code1 THEN 1 ELSE 0 END AS MatchingCode1,
CASE WHEN PN.CODE = Ev.Code2 THEN 1 ELSE 0 END AS MatchingCode2,
CASE WHEN PN.CODE = Ev.Code3 THEN 1 ELSE 0 END AS MatchingCode3,
CASE WHEN PN.CODE = Ev.Code4 THEN 1 ELSE 0 END AS MatchingCode4
FROM
Events EV
LEFT JOIN
ProcedureName PN
ON PN.CODE IN (Ev.Code1, Ev.Code2, Ev.Code3, Ev.Code4)
If you want that as a single column, you could use binary arithmetic.
SELECT
*,
CASE WHEN PN.CODE = Ev.Code1 THEN 1 ELSE 0 END +
CASE WHEN PN.CODE = Ev.Code2 THEN 2 ELSE 0 END +
CASE WHEN PN.CODE = Ev.Code3 THEN 4 ELSE 0 END +
CASE WHEN PN.CODE = Ev.Code4 THEN 8 ELSE 0 END AS MatchingCodes
FROM
Events EV
LEFT JOIN
ProcedureName PN
ON PN.CODE IN (Ev.Code1, Ev.Code2, Ev.Code3, Ev.Code4)
Here a value of 1 in MatchingCodes means that Code1 is a match. Similarly a value of 3 means Code1 and Code2 both match, or a value of 15 means that all the codes match.
EDIT: (After making it clear that you want multiple rows)
This is similar to Gordon's answer, but has slightly different behaviour; you get 1 row per match instead of 4 rows all the time, or one row with NULLs if there is no match.
SELECT
*
FROM
Events EV
OUTER APPLY
(
SELECT 1 AS MatchedCode, * FROM ProcedureName WHERE CODE = EV.Code1
UNION ALL
SELECT 2 AS MatchedCode, * FROM ProcedureName WHERE CODE = EV.Code2
UNION ALL
SELECT 3 AS MatchedCode, * FROM ProcedureName WHERE CODE = EV.Code3
UNION ALL
SELECT 4 AS MatchedCode, * FROM ProcedureName WHERE CODE = EV.Code4
)
PN
I think apply does what you want:
select e.id, v.which, v.code
from Events e cross apply
(values ('procedure1', code1), ('procedure2', code2), ('procedure3', code3), ('procedure4', code4)
) v(which, code)
If you want to filter out codes that are NULL, then add:
where v.code is null

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

How to execute multiple case when statements for each iteration in SAS?

I'm using proc sql, and using multiple case when statements to add columns with either a 0 or 1 if the condition is met. It's a big bottleneck right now since it has to scan through each id for each case when statement. So I'm trying to figure out a way to somehow nest the case statements to perform each iteration, instead of having to iterate for all case statements.
This is an example of my code that is taking too long right now.
SELECT *,
CASE WHEN loannumber IN (
SELECT loannumber FROM PREPAY_LOAN_IDS
) THEN 1
ELSE 0 END AS PREPAY_FLAG,
CASE WHEN loannumber IN (
SELECT loannumber FROM DPD_30_IDS
) THEN 1
ELSE 0 END AS DPD_30_FLAG,
CASE WHEN loannumber IN (
SELECT loannumber FROM DPD_60_IDS
) THEN 1
ELSE 0 END AS DPD_60_FLAG,
CASE WHEN loannumber IN (
SELECT loannumber FROM DPD_90_IDS
) THEN 1
ELSE 0 END AS DPD_90_FLAG,
CASE WHEN loannumber IN (
SELECT loannumber FROM DPD_120_IDS
) THEN 1
ELSE 0 END AS DPD_120_FLAG,
CASE WHEN loannumber IN (
SELECT loannumber FROM FORECLOSURE_IDS
) THEN 1
ELSE 0 END AS FORECLOSURE_FLAG
FROM(
SELECT *
FROM MORTGAGES
)
The below query will work faster than the one you have posted as the input table is not completely access to retrieve the results. Try running this query and see how it performs.
SELECT M.*,
CASE WHEN PLI.loannumber IS NOT NULL THEN 1
ELSE 0 END AS PREPAY_FLAG,
CASE WHEN D3I.loannumber IS NOT NULL THEN 1
ELSE 0 END AS DPD_30_FLAG,
CASE WHEN D6I.loannumber IS NOT NULL THEN 1
ELSE 0 END AS DPD_60_FLAG,
CASE WHEN D9I.loannumber IS NOT NULL THEN 1
ELSE 0 END AS DPD_90_FLAG,
CASE WHEN D12I.loannumber IS NOT NULL THEN 1
ELSE 0 END AS DPD_120_FLAG,
CASE WHEN FCI.loannumber IS NOT NULL THEN 1
ELSE 0 END AS FORECLOSURE_FLAG
FROM MORTGAGES M
LEFT JOIN
PREPAY_LOAN_IDS PLI
ON M.loannumber = PLI.loannumber
LEFT JOIN
DPD_30_IDS D3I
ON M.loannumber = D3I.loannumber
LEFT JOIN
DPD_30_IDS D6I
ON M.loannumber = D6I.loannumber
LEFT JOIN
DPD_90_IDS D9I
ON M.loannumber = D9I.loannumber
LEFT JOIN
DPD_90_IDS D12I
ON M.loannumber = D12I.loannumber
LEFT JOIN
FORECLOSURE_IDS FCI
ON M.loannumber = FCI.loannumber
;
Since you're using SAS, here's a data step alternative, assuming that each of your datasets is already either sorted by or has an index on loannumber:
data want;
merge MORTGAGES(in = Mortgages)
PREPAY_LOAN_IDS(in = PLIDs keep = loannumber)
/*etc*/
;
by loannumber;
if Mortgages;
PREPAY_FLAG = PLIDs;
/*etc*/
run;
N.B. You will get duplicated records from MORTGAGES if you have duplicates in any of your other tables.

Minimal number of checks in case statement to be valid over multiple column range

given the following table definition
I have a query written as so:
select id,
(
case
when partial >= 2 and full >=2
then sum(partial+full)
when partial >=2
then partial
when full >= 2
then full
else 0
end
) counts
from Foo
What is the minimum number of checks that I have to do to ensure that the inner when clauses:
partial>=2 and full >=2 are not invoked twice. That is does the case when/then syntax treat everything as else ifs and not just straight ifs?
You can do
select id,
(
case when partial >= 2 then partial else 0 end +
case when full >= 2 then full else 0 end
) counts
from Foo
Sample SQL FIDDLE