Oracle where condition priority - sql

I've a table with a varchar column (A) and another integer column(B) indicating the type of data present in A. If B is 0, then A will always contain numeric digits.
So when I form an sql like this
SELECT COUNT(*) FROM TAB WHERE B = 0 AND TO_NUMBER(A) = 123;
I get an exception invalid number.
I expect B = 0 to be evaluated first, and then TO_NUMBER(A) second, but from the above scenario I suspect TO_NUMBER(A) is evaluated first. Is my guess correct?

In contrast to programming languages like C, C#, Java etc., SQL doesn't have so called conditional logical operators. For conditional logical operators, the right operand is only evaluated if it can influence the result. So the evaluation of && stops if the left operand returns false. For || it stops if the left operand returns true.
In SQL, both operands are always evaluated. And it's up to the query optimizer to choose which one is evaluated first.
I propose you create the following function, which is useful in many cases:
FUNCTION IS_NUMBER(P_NUMBER VARCHAR2)
RETURN NUMBER DETERMINISTIC
IS
X NUMBER;
BEGIN
X := TO_NUMBER(P_NUMBER);
RETURN X;
EXCEPTION
WHEN OTHERS THEN RETURN NULL;
END IS_NUMBER;
Then you can rewrite your query as:
SELECT COUNT(*) FROM TAB WHERE B = 0 AND IS_NUMBER(A) = 123;
You can also use the function to check whether a string is a number.

Here's a simple way to force the check on B to occur first.
SELECT COUNT(*) FROM TAB
WHERE 123 = DECODE(B, 0, TO_NUMBER(A), NULL);

you can use subquery to be confident in the correctness of the result
select /*+NO_MERGE(T)*/ count(*)
from (
select *
from TAB
where B = 0
) T
where TO_NUMBER(A) = 123

in your particular example, you can compare varchars like this:
SELECT COUNT(*) FROM TAB WHERE B = 0 AND A = '123';
or if you trust oracle to do the implicit conversions, this should almost always work (i don't know in what cases it won't work, but it would be hard to debug if something went wrong)
SELECT COUNT(*) FROM TAB WHERE B = 0 AND A = 123;

It should test B = 0 first.
I not sure your guess is correct or not though without seeing sample data.
SELECT *
FROM DUAL
WHERE 1=0 AND 1/0=0
You can try add 1/0 = 0 as the last statement (like this query)
to your query to know if Oracle short circuits the logical operator.
sqlfiddle here.

Related

If output comes in negative value, how to make it null?

Hi I am working on one query, what I want to do is, whenever output comes in negative value,
I want to make it null, following is my query, can anyone help me with this ?
select (to_char((coalesce(14515200/3600000,0) - (coalesce(30512.65/3600,0) - (coalesce(1800/3600,0) + (coalesce(1800/3600,0))))),'FM99,999,999,999'))::character varying as test
If you want a result of NULL for negative values, use
nullif(greatest(/* your expression */, 0), 0)
The only drawback here is that a result of exactly 0 will also become NULL. If you want to avoid that, you could use a user defined function:
CREATE FUNCTION neg_to_null(double precision) RETURNS double precision
LANGUAGE plpgsql AS
'BEGIN; RETURN CASE WHEN $1 < 0.0 THEN NULL ELSE $1; END;';
I use PL/pgSQL to avoid function inlining.
Move your logic to the from clause and use case:
select (case when test_n >= 0 then test_n::character varying end)
from (select . . . as test_n -- your expression as number) x
If your expression actually uses values from a table, you can use a subquery, CTE, or lateral join to define test_n.

Is there a more concise version of the CASE WHEN foo.bar IS NULL THEN 0 ELSE 1 END paradigm?

The goal is simply to check whether a field is NULL or not, yielding a value of 1 when the field is not NULL, and 0 otherwise. I can't use COALESCE here, because if the field isn't NULL, I just want to return 1, not the value of the field.
I have a gut feeling that there's already a function that does this - something like NULL_STATUS_INTEGER(foo.bar), for example - instead of needing to write out the full CASE statement of CASE WHEN foo.bar IS NULL THEN 0 ELSE 1 END. I'm not saying that the CASE version is absurdly long, but I just find it strange that there's not a shorter way to do this.
I'm primarily interested in solutions that aren't confined to one SQL vendor, but I happen to be using SAS, if there happens to be a SAS-specific way to do this in PROC SQL.
Comparisons are also boolean, return a true/false
proc sql;
create table want as
select *, name = 'Alfred' as flag
from sashelp.class;
quit;
data want;
set sashelp.class;
flag = (name = 'Alfred');
run;
EDIT:
Another option is to use IFN()/IFC(), which factor in three conditions, FALSE, TRUE, MISSING. IFN()/IFC() can be used in SQL or a data step in SAS.
x = ifc(name="Alfred", 1, 0, .);

Oracle: True value in WHERE

I am building a SQL query inside a function. The functions parameters are my criterias for the WHERE clause. Parameters can be null too.
foo (1,2,3) => SELECT x FROM y WHERE a=1 AND b=2 AND c=3;
foo (null, 2, null) => SELECT x FROM y WHERE b=2;
My approach to do that in code is to add a very first alltime true in the WHERE-clause (e.g. 1=1 or NULL is NULL or 2 > 1)
So I do not need to handle the problem that the 1st WHERE condition is after a "WHERE" and all others are after a "AND".
String sql="SELECT x FROM y WHERE 1=1";
if (a!=null)
{
sql += " AND a="+a;
}
Is there a better term than 1=1 or my other samples to EXPLICITLY have a always true value? TRUE and FALSE is not working.
Oracle does not support a boolean type in SQL. It exists in PL/SQL, but can't be used in a query. The easiest thing to do is 1=1 for true and 0=1 for false. I'm not sure if the optimizer will optimize these out or not, but I suspect the performance impact is negligible. I've used this where the number of predicates is unknown until runtime.
I think this construct is superior to anything using nvl, coalesce, decode, or case, because those bypass normal indexes and I've had them lead to complicated and slow execution plans.
So, yes I'd do something like you have (not sure what language you're building this query in; this is not valid PL/SQL. Also you should use bind variables but that's another story):
sql = "SELECT x FROM y WHERE 1=1";
if (a != null)
{
sql += " AND a=" + a;
}
if (b != null)
{
sql += " AND b=" + b;
}
... etc.
I think this is better than Gordon Linoff's suggestion, because otherwise this would be more complicated because you'd have to have another clause for each predicate checking if there was a previous predicate, i.e. whether you needed to include the AND or not. It just makes the code more verbose to avoid a single, trivial clause in the query.
I've never really understood the approach of mandating a where clause even when there are no conditions. If you are constructing the logic, then combine the conditions. If the resulting string is empty, then leave out the where clause entirely.
Many application languages have the equivalent of concat_ws() -- string concatenation with a separator (join in Python, for instance). So leaving out the where clause does not even result in code that is much more complicated.
As for your question, Oracle doesn't have boolean values, so 1=1 is probably the most common approach.
use NVL so you can set a default value. Look up NVL or NVL2. Either might work for you.
check out: http://www.dba-oracle.com/t_nvl_vs_nvl2.htm
In sql the standard way to "default null" is to use coalesce. So lets say your input is #p1, #p2, and #p3
Then
select *
from table
where a = coalesce(#p1,a) and
b = coalesce(#p2,b) and
c = coalesce(#p3,c)
Thus if (as an example) #p1 is null then it will compare a = a (which is always true). If #p1 is not null then it will filter on a equals that value.
In this way null becomes a "wildcard" for all records.
you could also use decode to get the results you need since it is oracle
Select
decode(a, 1, x,null),
decode(b, 2, x,null),
decode(c, 3, x,null)
from y
What about using OR inside of the parentheses while ANDs exist at outside of them
SELECT x
FROM y
WHERE ( a=:prm1 OR :prm1 is null)
AND ( b=:prm2 OR :prm2 is null)
AND ( c=:prm3 OR :prm3 is null);

ORACLE - Select Count on a Subquery

I've got an Oracle table that holds a set of ranges (RangeA and RangeB). These columns are varchar as they can hold both numeric and alphanumeric values, like the following example:
ID|RangeA|RangeB
1 | 10 | 20
2 | 21 | 30
3 | AB50 | AB70
4 | AB80 | AB90
I need to to do a query that returns only the records that have numeric values, and perform a Count on that query. So far I've tried doing this with two different queries without any luck:
Query 1:
SELECT COUNT(*) FROM (
SELECT RangeA, RangeB FROM table R
WHERE upper(R.RangeA) = lower(R.RangeA)
) A
WHERE TO_NUMBER(A.RangeA) <= 10
Query 2:
WITH A(RangeA,RangeB) AS(
SELECT RangeA, RangeB FROM table
WHERE upper(RangeA) = lower(RangeA)
)
SELECT COUNT(*) FROM A WHERE TO_NUMBER(A.RangeA) <= 10
The subquery is working fine as I'm getting the two records that have only numeric values, but the COUNT part of the query is failing. I should be getting only 1 on the count, but instead I'm getting the following error:
ORA-01722: invalid number
01722. 00000 - "invalid number"
What am I doing wrong? Any help is much appreciated.
You can test each column with a regular expression to determine if it is a valid number:
SELECT COUNT(1)
FROM table_of_ranges
WHERE CASE WHEN REGEXP_LIKE( RangeA, '^-?\d+(\.\d*)?$' )
THEN TO_NUMBER( RangeA )
ELSE NULL END
< 10
AND REGEXP_LIKE( RangeB, '^-?\d+(\.\d*)?$' );
Another alternative is to use a user-defined function:
CREATE OR REPLACE FUNCTION test_Number (
str VARCHAR2
) RETURN NUMBER DETERMINISTIC
AS
invalid_number EXCEPTION;
PRAGMA EXCEPTION_INIT(invalid_number, -6502);
BEGIN
RETURN TO_NUMBER( str );
EXCEPTION
WHEN invalid_number THEN
RETURN NULL;
END test_Number;
/
Then you can do:
SELECT COUNT(*)
FROM table_of_ranges
WHERE test_number( RangeA ) <= 10
AND test_number( RangeB ) IS NOT NULL;
Try this query:
SELECT COUNT(*)
FROM table R
WHERE translate(R.RangeA, 'x0123456789', 'x') = 'x' and
translate(R.RangeB, 'x0123456789', 'x') = 'x'
First, you don't need the subquery for this purpose. Second, using to_number() or upper()/lower() are prone to other problems. The function translate() replaces each character in the second argument with values from the third argument. In this case, it removes numbers. If nothing is left over, then the original value was an integer.
You can do more sophisticated checks for negative values and floating point numbers, but the example in the question seemed to be about positive integer values.
Coming to this question almost four years later (obviously, pointed here from a much newer thread). The other answers show how to achieve the desired output, but do not answer the OP's question, which was "what am I doing wrong?"
You are not doing anything wrong. Oracle is doing something wrong. It is "pushing" the predicate (the WHERE condition) from the outer query into the inner query. Pushing predicates is one of the most basic ways in which the Optimizer makes queries more efficient, but in some cases (and the question you ask is a PERFECT illustration) the result is not, in fact, logically equivalent to the original query.
There are ways to prevent the Optimizer from pushing predicates; or you can write the query in a better way (as shown in the other answers). But if you wanted to know why you saw what you saw, this is why.

What does =+ mean in an Oracle query

Normally in C++ programming language, the plus means addition, in the example below
int x;
x += 1;
However in plsql query, I am confused about the same usage. That usage does not mean addition. In that case, what is the meaning of =+ ?
Select c.* From alf_numeric a, run_of_id b, tail_of_st c
WHERE category_id IN(33,36) AND a.flow_id =+ b.flow_id
Any idea?
This:
...
FROM alf_numeric a, run_of_id b
WHERE a.flow_id = b.flow_id (+)
would mean:
...
FROM alf_numeric a
LEFT JOIN run_of_id b
ON a.flow_id = b.flow_id
My guess is that:
a.flow_id =+b.flow_id
is parsed as the (simple):
a.flow_id = (+b.flow_id)
and so is the same as:
a.flow_id = b.flow_id
It looks to me that the '+' part of '=+' is a no-op. Try running the following statements:
CREATE TABLE test1 (v1 NUMBER);
INSERT INTO test1(v1) VALUES (-1);
INSERT INTO test1(v1) VALUES (1);
CREATE TABLE test2(v2 NUMBER);
INSERT INTO test2(v2) VALUES (-1);
INSERT INTO test2(v2) VALUES (1);
SELECT *
FROM test1 t1
INNER JOIN test2 t2
ON (t1.v1 = t2.v2)
WHERE t1.v1 =+ t2.v2;
which returns
V1 V2
-1 -1
1 1
Thus, it appears the '+' operator isn't doing anything, it's just answering whatever is there. As a test of this, run the following statement:
SELECT V1, +V1 AS PLUS_V1, ABS(V1) AS ABS_V1, -V1 AS NEG_V1 FROM TEST1;
and you'll find it returns
V1 PLUS_V1 ABS_V1 NEG_V1
-1 -1 1 1
1 1 1 -1
which seems to confirm that a unary '+' is effectively a no-op.
Share and enjoy.
In your SELECT statement, the clause
a.flow_id =+b.flow_id
is mainly a comparison. It tests whether the value of a.flow_id is equal to the value of b.flow_id. So the + operator in this case is an arithmetic operator working on a single operand. It turns the sign of the value to positive.
Update:
It seems I was slightly wrong. The operator doesn't change the sign. It has basically no effect.
It's probably a typo for the old left join syntax in Sybase, which would be =* instead of =+. If that's true, you can rewrite the query in a clearer way using joins, like:
select c.*
From alf_numeric a
left join
run_of_id b
on a.flow_id = b.flow_id
cross join
tail_of_st c
WHERE category_id IN(33,36)
Which would basically return the entire table tail_of_st for each entry in alf_numeric, with a filter on category_id (not sure what table that's in.) A mysterious query!
In your C++ example, the + designates the positive sign, it has nothing to do with addition. Just as you can write x = -1, you can also write x = +1 (which is equal to x = 1, since + as sign can be omitted - and is, in most cases, since it does in fact have no effect whatsoever). But both these cases are an assignment in C++, not an addition - no actual calculation is involved; you're probably thinking of x += 1 (the order is important!), which would increase x by 1.
In your SQL query, I think the + is supposed to have a special meaning - it should probably indicate an outer join. Although if I read that document correctly, it should actually be a.flow_id = b.flow_id (+); as it is here, I doubt that the query parser will recognize it as an outer join, but will instead just interpret it as a positive sign, just as in your C++ example.
I believe that's a join syntax thing. The standard way is to say something like tableA join tableB on <whatever> but some DBs, such as Sybase and Oracle support alternate syntax. In Sybase, it's =* or *=. Postgres probably does the same. From the format, I'd guess a right outer join, but it's hard to say. I looked in the PG docs, but didn't immediately see it.
BTW, in C you'd have x += 1 not x = +1.