Can we use '=' for comparing with NULL in sql - sql

I have table t1 as
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 19-APR-87 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1500 0 30
7876 ADAMS CLERK 7788 23-MAY-87 1100 20
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
------- ---------- --------- ---------- --------- ---------- ---------- ----------
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10
NULL
Null
null
VP
-----------------------------------------------------------------------------
when I query
select * from t1 where ename=null
It returns "NO rows selected".
As far as I know we cannot use '=' to compare with null.
So why it is not showing error.

So why it is not showing error?
Because the syntax (of the expression) is correct.
However - as you already pointed out - even though the syntax of the expression is correct it will never be true
It's similar to writing where 1=2 - that is "correct" as well, but will never return any rows.

No you can't use = for null values because comparison with null values returns 'unknown' result and rows with 'unknown' result aren't included in result set. Use the query instead.
select * from t1 where ename is null

NULL in most flavors of SQL represents a value which is completely unknown. In this case, when comparing a column against NULL it cannot evaluate to TRUE because both values are completely unknown. Logically, comparing "I don't know" against "I don't know" does not evaluate to TRUE in SQL thinking.
One real-world analogy for the thinking of comparing NULL against NULL would be comparing two randomly-drawn playing cards against each other. We cannot say that there are equal or not equal, because we have never seen them.
Instead of directly comparing for a NULL value, try using the IS NULL construct:
SELECT * FROM t1 WHERE ename IS NULL

Of course you can use = to compare with null. The result is always going to be unknown, of course, but that's a value like any other. It isn't fundamentally different from doing 42 + null, or using any other operator. Would you really want to get an exception (or a syntax error) any time you're using null with some operator? That would kind of defeat the whole purpose of null, wouldn't it?
Of course, comparing something with a literal null is quite likely a mistake, and it might make sense for you to get a warning. But that would also mean that a literal should behave differently from a "real" value - and you really don't want that.
This is mostly analogous to how NaN works in floats. Any operation that involves a NaN results in NaN - except for isNaN, which is analogous to is null in SQL.

There is no error message because there is no error.
You can compare for equality to NULL. It just always returns UNKKNOWN/NULL. If you want to know whether ename is NULL then write ename IS NULL.
A SELECT can return no rows. A query just returns all the rows that are like what you asked. If none of them are like that then none are returned.

You have to use IS NULL instead of = to make this work.
SELECT * FROM t1 WHERE ename IS NULL
If you are using SQL Server, SET ANSI NULL OFF.
Set ANSI NULLS OFF will make NULL = NULL comparision return true
Also check this one In SQL Server, what does “SET ANSI_NULLS ON” mean?
When you compare two NULL expressions, the result depends on the ANSI_NULLS setting:
If ANSI_NULLS is set to ON, the result is NULL, following the ANSI convention that a NULL (or unknown) value is not equal to another NULL or unknown value.
If ANSI_NULLS is set to OFF, the result of NULL compared to NULL is TRUE.
Comparing NULL to a non-NULL value always results in FALSE2.

I suppose because Null means nothing, you are looking for some unknown value and get nothing.
As written in T-SQL help:
To determine whether an expression is NULL, use IS NULL or IS NOT NULL
instead of comparison operators (such as = or !=). Comparison
operators return UNKNOWN when either or both arguments are NULL.
So I think, because comparison operators return UNKNOWN it is not the error it is just behauviour of the SQL server.

Related

SQL statement that repeats itself until there are no more results

I have the below existing statement. It finds parts that exist within other builds. The problem is, my user wants to go further until there are no more results.
For example.
We look up where PartA is used.
We find it is used inside of PartB and PartC.
Then we want to run the query again to find where PartB is used and PartC.
If PartB is not used anywhere else (No results found), we want to return PartB as part of the results.
But if PartC is used somewhere else, we want to keep going until we get no result for each part.
I am not sure if this is even possible so I thought I would ask here.
select ms.contract site,
ms.part_no,
crar1app.INVENTORY_PART_API.GET_DESCRIPTION(ms.contract, ms.part_no) part_desc,
ms.QTY_PER_ASSEMBLY,
ms.PRINT_UNIT uom,
-- ms.ENG_CHG_LEVEL,
crar1app.ENG_PART_REVISION_API.GET_PART_REV(ms.PART_NO, ms.ENG_CHG_LEVEL) rev,
ms.EFF_PHASE_IN_DATE,
ms.EFF_PHASE_OUT_DATE,
ms.BOM_TYPE,
ms.ALTERNATIVE_NO alt
from crar1app.MANUF_STRUCTURE ms
where ms.CONTRACT = nvl('&SITE','10')
and ms.COMPONENT_PART = '&PART_NO'
and ms.EFF_PHASE_IN_DATE <= to_date(nvl('&EFF_DATE',to_char(SYSDATE,'YYYY-MM-DD')),'YYYY-MM-DD')
and (ms.EFF_PHASE_OUT_DATE > to_date(nvl('&EFF_DATE',to_char(SYSDATE,'YYYY-MM-DD')),'YYYY-MM-DD')
or ms.EFF_PHASE_OUT_DATE is null)
and (ms.ALTERNATIVE_NO = 'ML'
or (select 1 from dual where crar1app.MANUF_STRUCT_ALTERNATE_API.GET_OBJSTATE(ms.CONTRACT,ms.PART_NO,ms.ENG_CHG_LEVEL,ms.BOM_TYPE,'ML') in ('Plannable','Buildable')) IS NULL)
I have tried a few things but nothing close to what I am looking for.
Lots of options are available to you in a CONNECT BY query which lets you loop through a hierarchy, eg
SQL> create table parts as
2 select empno part_num, mgr parent_part
3 from scott.emp;
Table created.
SQL>
SQL> select
2 level,
3 part_num,
4 parent_part,
5 sys_connect_by_path(part_num,'-') path,
6 connect_by_isleaf
7 from parts
8 connect by prior part_num = parent_part
9 start with parent_part is null;
LEVEL PART_NUM PARENT_PART PATH CONNECT_BY_ISLEAF
---------- ---------- ----------- ------------------------------ -----------------
1 7839 -7839 0
2 7566 7839 -7839-7566 0
3 7788 7566 -7839-7566-7788 0
4 7876 7788 -7839-7566-7788-7876 1
3 7902 7566 -7839-7566-7902 0
4 7369 7902 -7839-7566-7902-7369 1
2 7698 7839 -7839-7698 0
3 7499 7698 -7839-7698-7499 1
3 7521 7698 -7839-7698-7521 1
3 7654 7698 -7839-7698-7654 1
3 7844 7698 -7839-7698-7844 1
3 7900 7698 -7839-7698-7900 1
2 7782 7839 -7839-7782 0
3 7934 7782 -7839-7782-7934 1

Creating table with defined variable

I work in SQL Developer by Oracle. I have a simple query to creating new table based on condition where. I want to generate new table with defined variable like below. My code isn't working. How can I define variable and put its directly to query without any entry box?
DEFINE STARTDATE DATE:="TO_DATE('2021-06-01','YYYY-MM-DD')"
CREATE TABLE RESULTS AS
SELECT
*
FROM TABLE_1
WHERE DATA=ADD_MONTHS(:STARTDATE,2);
DEFINE creates a substitution variable.
:STARTDATE uses a bind variable, it is not a substitution variable.
&startdate would be a substution variable.
You want something like:
DEFINE STARTDATE = "DATE '2021-06-01'"
CREATE TABLE RESULTS AS
SELECT *
FROM TABLE_1
WHERE DATA=ADD_MONTHS(&STARTDATE,2);
That's not how define variables works. It is just a text replacement, not a pl/sql variable. try this:
DEFINE STARTDATE =TO_DATE('2021-06-01','YYYY-MM-DD')
In SQL*Plus, it is a substitution variable you'd use (BTW, define syntax you used is wrong).
For example:
SQL> select * From emp order by hiredate;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17.12.80 800 20
7499 ALLEN SALESMAN 7698 20.02.81 1600 300 30
7521 WARD SALESMAN 7698 22.02.81 1250 500 30
7566 JONES MANAGER 7839 02.04.81 2975 20
7698 BLAKE MANAGER 7839 01.05.81 2850 30
7782 CLARK MANAGER 7839 09.06.81 2450 10
7844 TURNER SALESMAN 7698 08.09.81 1500 0 30
7654 MARTIN SALESMAN 7698 28.09.81 1250 1400 30
7839 KING PRESIDENT 17.11.81 5000 10
7900 JAMES CLERK 7698 03.12.81 950 30
7902 FORD ANALYST 7566 03.12.81 3000 20
7934 MILLER CLERK 7782 23.01.82 1300 10
7788 SCOTT ANALYST 7566 09.12.82 3000 20
7876 ADAMS CLERK 7788 12.01.83 1100 20
14 rows selected.
Create a table out of it:
SQL> DEFINE STARTDATE = "1980-12-01"
SQL> create table a1 as select * from emp where trunc(hiredate, 'mm') =
2 trunc(add_months(to_date('&startdate', 'yyyy-mm-dd'), 2));
old 2: trunc(add_months(to_date('&startdate', 'yyyy-mm-dd'), 2))
new 2: trunc(add_months(to_date('1980-12-01', 'yyyy-mm-dd'), 2))
Table created.
SQL> select * from a1;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
7499 ALLEN SALESMAN 7698 20.02.81 1600 300 30
7521 WARD SALESMAN 7698 22.02.81 1250 500 30
SQL>

how to query a table using name from another column from another table

I am trying to get data from a table where the table name is stored in another table.
I am trying for select query but not able to get the data.
example
t1
table name | some data
t2 - table name is same coming from t1
t2
id | some data
I need to fetch the t2 name first from t1 that I am able to do. but using that query response I am not sure how to fetch the second query as the result is coming from first query.
Any help is appreciated.
Thanks
It'll have to be some kind of "dynamic" SQL. One option is to create a function that returns ref cursor. Here's an example.
This is your t1 table; it contains some table names.
SQL> select * from tname;
ID TABLE_NAME
---------- ---------------
1 emp
2 dept
Function:
SQL> create or replace function f_tab (par_table_name in varchar2)
2 return sys_refcursor
3 is
4 rc sys_refcursor;
5 begin
6 open rc for 'select * from ' || dbms_assert.sql_object_name(par_table_name);
7 return rc;
8 end;
9 /
Function created.
Testing: query selects table names from the tname table and returns their contents:
SQL> select f_tab(t.table_name) result
2 from tname t
3 order by t.id;
RESULT
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17.12.1980 00:00:00 800 20
7499 ALLEN SALESMAN 7698 20.02.1981 00:00:00 1600 300 30
7521 WARD SALESMAN 7698 22.02.1981 00:00:00 1250 500 30
7566 JONES MANAGER 7839 02.04.1981 00:00:00 2975 20
7654 MARTIN SALESMAN 7698 28.09.1981 00:00:00 1250 1400 30
7698 BLAKE MANAGER 7839 01.05.1981 00:00:00 2850 30
7782 CLARK MANAGER 7839 09.06.1981 00:00:00 2450 10
7788 SCOTT ANALYST 7566 09.12.1982 00:00:00 3000 20
7839 KING PRESIDENT 17.11.1981 00:00:00 5000 10
7844 TURNER SALESMAN 7698 08.09.1981 00:00:00 1500 0 30
7876 ADAMS CLERK 7788 12.01.1983 00:00:00 1100 20
7900 JAMES CLERK 7698 03.12.1981 00:00:00 950 30
7902 FORD ANALYST 7566 03.12.1981 00:00:00 3000 20
7934 MILLER CLERK 7782 23.01.1982 00:00:00 1300 10
14 rows selected.
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>

How do I know what action was taken by the MERGE statement (ORACLE)?

Is there a way to find out what action was taken by the MERGE statement in an ORACLE procedure?
For example, if I need to subsequently perform different procedures depending on the action performed (INSERT or UPDATE)
p.s. Forgot to clarify, I am considering a case where the MERGE statement processes exactly one row
Without modifying the table, its hard to capture what has been done. There are solutions out there that add a PL/SQL layer into the MERGE statement to force the execution of PL/SQL function for each row, but that will hurt performance.
If you really need it, an additional column could be added, eg
SQL> create table t as select empno, ename, sal, ' ' tag from scott.emp where empno != 7934;
Table created.
SQL> create table t1 as select empno, ename, sal*5 sal from scott.emp where job = 'CLERK';
Table created.
SQL>
SQL> select * from t;
EMPNO ENAME SAL T
---------- ---------- ---------- -
7369 SMITH 800
7499 ALLEN 1600
7521 WARD 1250
7566 JONES 2975
7654 MARTIN 1250
7698 BLAKE 2850
7782 CLARK 2450
7788 SCOTT 3000
7839 KING 5000
7844 TURNER 1500
7876 ADAMS 1100
7900 JAMES 950
7902 FORD 3000
13 rows selected.
SQL> select * from t1;
EMPNO ENAME SAL
---------- ---------- ----------
7369 SMITH 4000
7876 ADAMS 5500
7900 JAMES 4750
7934 MILLER 6500
SQL>
SQL> merge into t
2 using ( select * from t1) t1
3 on ( t.empno = t1.empno )
4 when matched then
5 update
6 set t.sal = t1.sal, t.tag = 'U'
7 when not matched then
8 insert (t.empno,t.ename,t.sal,t.tag)
9 values (t1.empno,t1.ename,t1.sal,'I');
4 rows merged.
SQL>
SQL> select * from t;
EMPNO ENAME SAL T
---------- ---------- ---------- -
7369 SMITH 4000 U
7499 ALLEN 1600
7521 WARD 1250
7566 JONES 2975
7654 MARTIN 1250
7698 BLAKE 2850
7782 CLARK 2450
7788 SCOTT 3000
7839 KING 5000
7844 TURNER 1500
7876 ADAMS 5500 U
7900 JAMES 4750 U
7902 FORD 3000
7934 MILLER 6500 I
14 rows selected.
I've just used U/I but this column could be (say) a numeric field or similar to handle multiple MERGE's over time.
But most people heading down this route, typically end up using separate INSERT and UPDATE blocks
sql%rowcount tells us how many rows were merged (inserted / updated / deleted). There is no way to separate that count into sub-totals for each action.
For example, if I need to subsequently perform different procedures depending on the action performed
Is this a hypothetical? If not, edit your question to outline your actual situation. But generally, the options are:
replace the MERGE with separate DML statements for each action;
use row-level auditing to track actions (be careful introducing this if you don't already have it).

Oracle SQL Comparing via DECODE

I try to get something in Oracle, if the commission is greater than 0.2 I would like to get 'GOOD', otherwise 'BAD'. And also if the commission is null I want to get 0. I know that is with NVL, but something get wrong with syntax. Can you help me?
SELECT LAST_NAME,
SALARY,
DECODE(
NVL(COMMISSION_PCT),
COMMISSION_PCT < 0,2, 'BAD', COMMISSION_PCT > 0,2, 'GOOD'
) CommissionResult
FROM EMPLOYEES;
First of all, 0.2 should be written as 0.2, not 0,2. But most importantly, decode is not suitable for this case.
In this case (and all other cases where you could use decode), you can use case, which is more flexible and a more verbose so it is easier to read too.
SELECT LAST_NAME,SALARY,
CASE WHEN NVL(COMMISSION_PCT) < 0.2 THEN
'BAD'
WHEN COMMISSION_PCT > 0.2 THEN
'GOOD'
END as CommissionResult
FROM EMPLOYEES;
In your this case, you will get NULL when the percentage is exactly 0.2. Maybe you just need an ELSE clause instead:
SELECT LAST_NAME,SALARY,
CASE WHEN NVL(COMMISSION_PCT) < 0.2 THEN
'BAD'
ELSE
'GOOD'
END as CommissionResult
FROM EMPLOYEES;
DECODE(
NVL(COMMISSION_PCT),
COMMISSION_PCT < 0,2,'BAD',COMMISSION_PCT > 0,2,'GOOD'
)
Your query is syntactically incorrect.
NVL syntax is incomplete
You have a typo in the decimal number, a comma instead of dot.
DECODE syntax doesn't support comparisons.
For your requirement, you could use CASE expression which is verbose and easy to interpret.
Using CASE
For example, using the standard EMP table in SCOTT schema:
SQL> SELECT ename,
2 sal,
3 CASE
4 WHEN NVL(comm, 0) < 0.2
5 THEN 'BAD'
6 WHEN NVL(comm, 0) > 0.2
7 THEN 'GOOD'
8 END CommissionResult
9 FROM emp;
ENAME SAL COMM
---------- ---------- ----
SMITH 800 BAD
ALLEN 1600 GOOD
WARD 1250 GOOD
JONES 2975 BAD
MARTIN 1250 GOOD
BLAKE 2850 BAD
CLARK 2450 BAD
SCOTT 3000 BAD
KING 5000 BAD
TURNER 1500 BAD
ADAMS 1100 BAD
JAMES 950 BAD
FORD 3000 BAD
MILLER 1300 BAD
14 rows selected.
However, if you must use DECODE, then you need to use SIGN to have the same functionality.
Using DECODE
SQL> SELECT ename,
2 sal,
3 DECODE( SIGN(NVL(comm, 0) - 0.2), -1, 'BAD', +1, 'GOOD') CommissionResult
4 FROM emp;
ENAME SAL COMM
---------- ---------- ----
SMITH 800 BAD
ALLEN 1600 GOOD
WARD 1250 GOOD
JONES 2975 BAD
MARTIN 1250 GOOD
BLAKE 2850 BAD
CLARK 2450 BAD
SCOTT 3000 BAD
KING 5000 BAD
TURNER 1500 BAD
ADAMS 1100 BAD
JAMES 950 BAD
FORD 3000 BAD
MILLER 1300 BAD
14 rows selected.