Converting a nested decode into equivalent CASE statement (needed for conversion from Oracle to PostgreSQL) - sql

I am on Oracle 11.2.0.4. I have a nested DECODE statement that I need to convert into CASE statement. Can someone help on it. I am not sure of how it is done and in fact I don't fully understand the logic of it. If someone can explain what it basically intends to do and what would be the rewritten function using CASE that is Very useful to me. Here is the function...(note: do not worry about table joins , there are 3 tables and one condition etc. please focus on the DECODE and its conversion to CASE). Also I must manually convert and tools are not an option.
CREATE OR REPLACE FUNCTION TMP_Func
RETURN NUMBER
IS
V NUMBER;
BEGIN
SELECT DECODE (
NVL (tab1.flag1, '~'),
'D', DECODE (tab1.code_oid, NVL (tab3.bu_id, '-'), 1, 0),
'C', DECODE (tab1.code_oid, NVL (tab3.cost_id, '-'), 1, 0),
DECODE (tab2.oid,
DECODE (tab1.co_id, NULL, tab2.oid, tab1.co_id), 1,
0))
INTO V
FROM tab1, tab2, tab3
WHERE tab2.OID = tab1.sec_id;
RETURN V;
END;

something like this :
select CASE WHEN COALESCE(tab1.flag1,'~') = 'D' THEN
CASE WHEN tab1.code_oid=COALESCE(tab3.bu_id, '-') THEN 1 else 0 end
WHEN COALESCE(tab1.flag1,'~')='C' THEN
CASE WHEN tab1.code_oid=COALESCE(tab3.cost_id, '-') THEN 1 else 0 end
else
CASE WHEN tab2.oid=COALESCE(tab1.co_id,tab2.oid) THEN 1 else 0 end
end
FROM tab1, tab2, tab3
WHERE tab2.OID = tab1.sec_id;
NVL is replaced by COALESCE
DECODE(a,b,c,d,e,...,f) is replaced by :
CASE WHEN a=b THEN c
WHEN a=d THEN e
...
else f
end
your last decode ( DECODE (tab1.co_id, NULL, tab2.oid, tab1.co_id), 1,
0))) is in fact an NVL( tab1.co_id,tab2.oid)

That would be something like this:
SELECT CASE
WHEN NVL (tab1.flag1, '~') = 'D'
THEN
CASE
WHEN tab1.code_oid = NVL (tab3.bu_id, '-') THEN 1
ELSE 0
END
WHEN NVL (tab1.flag1, '~') = 'C'
THEN
CASE
WHEN tab1.code_oid = NVL (tab3.cost_id, '-') THEN 1
ELSE 0
END
ELSE
CASE
WHEN tab2.oid =
CASE
WHEN tab1.co_id IS NULL THEN tab2.oid
ELSE tab1.co_id
END
THEN
1
ELSE
0
END
END
INTO v
FROM ...
Note that your FROM clause contains 3 tables, but WHERE joins just two of them. What about tab3? Also, consider switching to ANSI join.

The following should do what you want:
SELECT CASE COALESCE(TAB1.FLAG1, '~')
WHEN 'D' THEN CASE
WHEN TAB1.CODE_OID = COALESCE(TAB3.BU_ID, '-') THEN 1
ELSE 0
END
WHEN 'C' THEN CASE
WHEN TAB1.CODE_OID = COALESCE(TAB3.COST_ID, '~') THEN 1
ELSE 0
END
ELSE CASE
WHEN TAB2.OID = COALESCE(TAB1.CO_ID, TAB2.OID) THEN 1
ELSE 0
END
END
INTO V
FROM TAB1
INNER JOIN TAB2
ON TAB2.OID = TAB2.SEC_ID
CROSS JOIN TAB3;
Note that NVL and COALESCE are slightly different in a couple of respects, although neither seems to be a factor here. First, NVL always takes two arguments, while COALESCE can take as many as you care to supply - it returns the first non-NULL argument. Second, NVL always evaluates both of its arguments (e.g. if a function is specified for one or both of the arguments to NVL, both functions are called), while COALESCE only evaluates as many of the arguments are necessary to find a non-NULL result; thus, if COALESCE is given two functions for arguments and the first returns a non-NULL value, the second function is never called. Not an issue here, but perhaps important (due to side effects) in other cases.
Best of luck.

Related

Using function inside CASE

I am trying to use the SUBSTR and NVL functions inside the case. The case is in the where clause of the select statement.
The code below gives the following error:
ORA-00905: missing keyword
AND ( CASE
WHEN SUBSTR(upper(p_open_invoice),1,1) = 'Y' THEN
NVL(P.AMOUNT_DUE_REMAINING,0) = 0
ELSE
1=1
END)
This looks like a syntax error around equal operator of NVL function.
That is not how case expressions work (in Oracle) -- there is no boolean type to return.
The simplest method is to remove the `case and express this as simple logic:
AND (SUBSTR(upper(p_open_invoice), 1, 1) <> 'Y' OR
COALESCE(P.AMOUNT_DUE_REMAINING, 0) = 0
)
If p_open_invoice can be NULL, you need to take that into account as well.
You cannot use a collation as a result for case..when statements, it's better converting the condition to
AND (( SUBSTR(upper(p_open_invoice),1,1) = 'Y' AND NVL(P.AMOUNT_DUE_REMAINING,0) = 0 )
OR SUBSTR(upper(p_open_invoice),1,1) != 'Y' )
If you're accustomed to programming in PL/SQL you may have seen that there's a BOOLEAN type in PL/SQL. However, this is not true in the Oracle database itself. The way I usually work around this is to use character expressions which return 'Y' or 'N' instead of TRUE or FALSE.
Keeping this in mind - if you really want to use a CASE expression similar to what you had originally you can use the following:
AND CASE
WHEN SUBSTR(upper(p_open_invoice),1,1) = 'Y'
THEN CASE
WHEN NVL(P.AMOUNT_DUE_REMAINING,0) = 0 THEN 'Y'
ELSE 'N'
END
ELSE 'Y'
END = 'Y'
Here the CASE expression returns either 'Y' or 'N', which is then compared with 'Y'.

Issue with replacing negative values with zero

Checked similar threads but no similar issues were experienced by other users.
The code does not seem to work and the error message says "Unexpected token 'Then' at line 11".
Lines 6 to 8 get the calculated column that determines the savings but if it is a loss, it becomes negative. I wanted to replace all negative values with 0 using Case but for some reason 'then' is treated as an error.
Select Distinct Reports.rptviewGovtTransparencyCode.project_title As Title,
Reports.rptviewContract.AwardedDateTime As Awarded,
Reports.rptviewContract.estimated_value As Budget,
Convert(decimal,Replace(Reports.rptviewCustomFieldAnswer.Answer, ',',
'')) As Value,
Reports.rptviewContract.estimated_value -
Convert(decimal,Replace(Reports.rptviewCustomFieldAnswer.Answer, ',',
'')) As Saving,
case when (Reports.rptviewContract.estimated_value -
Convert(decimal,Replace(Reports.rptviewCustomFieldAnswer.Answer, ',', ''))
<= 0 then 0) As Saving2
From Reports.rptviewGovtTransparencyCode
Inner Join Reports.rptviewContract On Reports.rptviewContract.contract_id =
Reports.rptviewGovtTransparencyCode.contract_id
Inner Join Reports.rptviewCustomField On Reports.rptviewCustomField.OrgId =
Reports.rptviewContract.OrgId
Inner Join Reports.rptviewCustomFieldAnswer
On Reports.rptviewCustomFieldAnswer.TargetAreaId =
Reports.rptviewContract.project_id And Reports.rptviewCustomField.Id =
Reports.rptviewCustomFieldAnswer.CustomFieldId
Inner Join Reports.rptviewContractPrimaryContact
On Reports.rptviewContract.contract_id =
Reports.rptviewContractPrimaryContact.ContractId
Where Reports.rptviewGovtTransparencyCode.department = '1capital' And
Reports.rptviewCustomField.Title = 'awarded value'
This:
case when (Reports.rptviewContract.estimated_value -
Convert(decimal,Replace(Reports.rptviewCustomFieldAnswer.Answer, ',', ''))
<= 0 then 0)
is missing the case's end, and also has a wrong parenthesis couple enclosing both the coondition and the "then" after the when clause. Replace it with the following:
case when Reports.rptviewContract.estimated_value -
Convert(decimal,Replace(Reports.rptviewCustomFieldAnswer.Answer, ',', ''))
<= 0 then 0 end
In order to demonstrate better, what you had before was:
case when (condition then result)
while the correct was
case when condition then result end
You shall not p...hide the "then" inside the parenthesis: the "when" cannot see it.

Switching fields in WHERE clause SQL 2005 [duplicate]

I am creating a SQL query in which I need a conditional where clause.
It should be something like this:
SELECT
DateAppr,
TimeAppr,
TAT,
LaserLTR,
Permit,
LtrPrinter,
JobName,
JobNumber,
JobDesc,
ActQty,
(ActQty-LtrPrinted) AS L,
(ActQty-QtyInserted) AS M,
((ActQty-LtrPrinted)-(ActQty-QtyInserted)) AS N
FROM
[test].[dbo].[MM]
WHERE
DateDropped = 0
--This is where i need the conditional clause
AND CASE
WHEN #JobsOnHold = 1 THEN DateAppr >= 0
ELSE DateAppr != 0
END
The above query is not working. Is this not the correct syntax or is there another way to do this that I don't know?
I don't want to use dynamic SQL, so is there any other way or do I have to use a workaround like using if else and using the same query with different where clauses?
Try this
SELECT
DateAppr,
TimeAppr,
TAT,
LaserLTR,
Permit,
LtrPrinter,
JobName,
JobNumber,
JobDesc,
ActQty,
(ActQty-LtrPrinted) AS L,
(ActQty-QtyInserted) AS M,
((ActQty-LtrPrinted)-(ActQty-QtyInserted)) AS N
FROM
[test].[dbo].[MM]
WHERE
DateDropped = 0
AND (
(ISNULL(#JobsOnHold, 0) = 1 AND DateAppr >= 0)
OR
(ISNULL(#JobsOnHold, 0) != 1 AND DateAppr != 0)
)
You can read more about conditional WHERE here.
Try this one -
WHERE DateDropped = 0
AND (
(ISNULL(#JobsOnHold, 0) = 1 AND DateAppr >= 0)
OR
(ISNULL(#JobsOnHold, 0) != 1 AND DateAppr != 0)
)
To answer the underlying question of how to use a CASE expression in the WHERE clause:
First remember that the value of a CASE expression has to have a normal data type value, not a boolean value. It has to be a varchar, or an int, or something. It's the same reason you can't say SELECT Name, 76 = Age FROM [...] and expect to get 'Frank', FALSE in the result set.
Additionally, all expressions in a WHERE clause need to have a boolean value. They can't have a value of a varchar or an int. You can't say WHERE Name; or WHERE 'Frank';. You have to use a comparison operator to make it a boolean expression, so WHERE Name = 'Frank';
That means that the CASE expression must be on one side of a boolean expression. You have to compare the CASE expression to something. It can't stand by itself!
Here:
WHERE
DateDropped = 0
AND CASE
WHEN #JobsOnHold = 1 AND DateAppr >= 0 THEN 'True'
WHEN DateAppr != 0 THEN 'True'
ELSE 'False'
END = 'True'
Notice how in the end the CASE expression on the left will turn the boolean expression into either 'True' = 'True' or 'False' = 'True'.
Note that there's nothing special about 'False' and 'True'. You can use 0 and 1 if you'd rather, too.
You can typically rewrite the CASE expression into boolean expressions we're more familiar with, and that's generally better for performance. However, sometimes is easier or more maintainable to use an existing expression than it is to convert the logic.
The problem with your query is that in CASE expressions, the THEN and ELSE parts have to have an expression that evaluates to a number or a varchar or any other datatype but not to a boolean value.
You just need to use boolean logic (or rather the ternary logic that SQL uses) and rewrite it:
WHERE
DateDropped = 0
AND ( #JobsOnHold = 1 AND DateAppr >= 0
OR (#JobsOnHold <> 1 OR #JobsOnHold IS NULL) AND DateAppr <> 0
)
Often when you use conditional WHERE clauses you end upp with a vastly inefficient query, which is noticeable for large datasets where indexes are used. A great way to optimize the query for different values of your parameter is to make a different execution plan for each value of the parameter. You can achieve this using OPTION (RECOMPILE).
In this example it would probably not make much difference, but say the condition should only be used in one of two cases, then you could notice a big impact.
In this example:
WHERE
DateDropped = 0
AND (
(ISNULL(#JobsOnHold, 0) = 1 AND DateAppr >= 0)
OR
(ISNULL(#JobsOnHold, 0) <> 1 AND DateAppr <> 0)
)
OPTION (RECOMPILE)
Source Parameter Sniffing, Embedding, and the RECOMPILE Options
This seemed easier to think about where either of two parameters could be passed into a stored procedure. It seems to work:
SELECT *
FROM x
WHERE CONDITION1
AND ((#pol IS NOT NULL AND x.PolicyNo = #pol) OR (#st IS NOT NULL AND x.State = #st))
AND OTHERCONDITIONS

SQL Server 2005 - RIGHT() not working when adding to CHARINDEX()

I'm trying to use the RIGHT function to get the substring of a value if it consists of a '/', but it doesn't work when I add a number to the CHARINDEX value; only without.
Here is a sample of the code:
SELECT CASE
WHEN
CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA')) = 0
THEN
REPLACE(ISNULL(d.target_grade,'NA'),'N/A','NA')
ELSE
RIGHT(d.target_grade, CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA'))+1)
END as target_grade
FROM tbl --etc.
This returns for example
target_grade
-------------
C/D
It should return though this
target_grade
-------------
D
If I remove the +1, however, the RIGHT function works exactly as it should
target_grade
-------------
/D
What am I doing wrong here? Is my logic flawed?
I recommend avoiding overly-complex string manipulations by leveraging the power of the CASE statement. Try something like this:
CASE
when d.target_grade is null then 'NA'
when d.target_grade = 'N/A' then 'NA'
when charindex('/', d.target_grade) = 0 then d.target_grade
else substring(d.target_grade, charindex('/', d.target_grade) + 1, XX) -- Replace XX with the max posssible length of d.target_grade
END
Since RIGHT wants "how many characters to keep" rather than "where to start the string from", your current logic is wrong.
Simpler, if you already have "where to start the string from" is to use SUBSTRING:
SELECT CASE
WHEN
CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA')) = 0
THEN
REPLACE(ISNULL(d.target_grade,'NA'),'N/A','NA')
ELSE
SUBSTRING(d.target_grade,
CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA'))+1
,8000)
END as target_grade
FROM tbl
Silly me, like #MarkBannister said, charindex counts from the left of the string, but right counts from the right of the string. Therefore the above should be
SELECT CASE
WHEN
CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA')) = 0
THEN
REPLACE(ISNULL(d.target_grade,'NA'),'N/A','NA')
ELSE
-- Use -1 NOT 1
RIGHT(d.target_grade, CHARINDEX('/',REPLACE(ISNULL(d.target_grade,'NA'), 'N/A', 'NA'))-1)
END as target_grade
FROM tbl --etc.
-1 NOT +1

Conditional WHERE clause in SQL Server

I am creating a SQL query in which I need a conditional where clause.
It should be something like this:
SELECT
DateAppr,
TimeAppr,
TAT,
LaserLTR,
Permit,
LtrPrinter,
JobName,
JobNumber,
JobDesc,
ActQty,
(ActQty-LtrPrinted) AS L,
(ActQty-QtyInserted) AS M,
((ActQty-LtrPrinted)-(ActQty-QtyInserted)) AS N
FROM
[test].[dbo].[MM]
WHERE
DateDropped = 0
--This is where i need the conditional clause
AND CASE
WHEN #JobsOnHold = 1 THEN DateAppr >= 0
ELSE DateAppr != 0
END
The above query is not working. Is this not the correct syntax or is there another way to do this that I don't know?
I don't want to use dynamic SQL, so is there any other way or do I have to use a workaround like using if else and using the same query with different where clauses?
Try this
SELECT
DateAppr,
TimeAppr,
TAT,
LaserLTR,
Permit,
LtrPrinter,
JobName,
JobNumber,
JobDesc,
ActQty,
(ActQty-LtrPrinted) AS L,
(ActQty-QtyInserted) AS M,
((ActQty-LtrPrinted)-(ActQty-QtyInserted)) AS N
FROM
[test].[dbo].[MM]
WHERE
DateDropped = 0
AND (
(ISNULL(#JobsOnHold, 0) = 1 AND DateAppr >= 0)
OR
(ISNULL(#JobsOnHold, 0) != 1 AND DateAppr != 0)
)
You can read more about conditional WHERE here.
Try this one -
WHERE DateDropped = 0
AND (
(ISNULL(#JobsOnHold, 0) = 1 AND DateAppr >= 0)
OR
(ISNULL(#JobsOnHold, 0) != 1 AND DateAppr != 0)
)
To answer the underlying question of how to use a CASE expression in the WHERE clause:
First remember that the value of a CASE expression has to have a normal data type value, not a boolean value. It has to be a varchar, or an int, or something. It's the same reason you can't say SELECT Name, 76 = Age FROM [...] and expect to get 'Frank', FALSE in the result set.
Additionally, all expressions in a WHERE clause need to have a boolean value. They can't have a value of a varchar or an int. You can't say WHERE Name; or WHERE 'Frank';. You have to use a comparison operator to make it a boolean expression, so WHERE Name = 'Frank';
That means that the CASE expression must be on one side of a boolean expression. You have to compare the CASE expression to something. It can't stand by itself!
Here:
WHERE
DateDropped = 0
AND CASE
WHEN #JobsOnHold = 1 AND DateAppr >= 0 THEN 'True'
WHEN DateAppr != 0 THEN 'True'
ELSE 'False'
END = 'True'
Notice how in the end the CASE expression on the left will turn the boolean expression into either 'True' = 'True' or 'False' = 'True'.
Note that there's nothing special about 'False' and 'True'. You can use 0 and 1 if you'd rather, too.
You can typically rewrite the CASE expression into boolean expressions we're more familiar with, and that's generally better for performance. However, sometimes is easier or more maintainable to use an existing expression than it is to convert the logic.
The problem with your query is that in CASE expressions, the THEN and ELSE parts have to have an expression that evaluates to a number or a varchar or any other datatype but not to a boolean value.
You just need to use boolean logic (or rather the ternary logic that SQL uses) and rewrite it:
WHERE
DateDropped = 0
AND ( #JobsOnHold = 1 AND DateAppr >= 0
OR (#JobsOnHold <> 1 OR #JobsOnHold IS NULL) AND DateAppr <> 0
)
Often when you use conditional WHERE clauses you end upp with a vastly inefficient query, which is noticeable for large datasets where indexes are used. A great way to optimize the query for different values of your parameter is to make a different execution plan for each value of the parameter. You can achieve this using OPTION (RECOMPILE).
In this example it would probably not make much difference, but say the condition should only be used in one of two cases, then you could notice a big impact.
In this example:
WHERE
DateDropped = 0
AND (
(ISNULL(#JobsOnHold, 0) = 1 AND DateAppr >= 0)
OR
(ISNULL(#JobsOnHold, 0) <> 1 AND DateAppr <> 0)
)
OPTION (RECOMPILE)
Source Parameter Sniffing, Embedding, and the RECOMPILE Options
This seemed easier to think about where either of two parameters could be passed into a stored procedure. It seems to work:
SELECT *
FROM x
WHERE CONDITION1
AND ((#pol IS NOT NULL AND x.PolicyNo = #pol) OR (#st IS NOT NULL AND x.State = #st))
AND OTHERCONDITIONS