Oracle : Update command using where clause is case sensitive? - oracle

enter image description here
SQL> select *from ORA_DEPT;
DEPARTMENT_ID DEPARTMENT_NAME
10 HR
10 Account
20 TEST
30 QA
40 Dev
SQL> update ORA_DEPT set department_name='PR' where department_name='account';
0 rows updated.
SQL> update ORA_DEPT set department_name='PR' where department_name='dev';
0 rows updated.
SQL> update ORA_DEPT set department_name='PR' where department_name='dev';
0 rows updated

SQL is generally case insensitive, in the sense that object names (table, column, procedure, bind variable etc.) are generally case insensitive. (They can be made case sensitive, but that's irrelevant here.)
You are applying the concept to data, namely to strings of characters. There is no language on Earth where strings are not case sensitive.
If you want to make the where clause case insensitive, you can do so explicitly:
...
where lower(department_name) = 'account'
Or, you can use the "nuclear option" - you can force all such comparisons to be case insensitive, by changing your NLS options. Namely:
alter session set nls_comp = linguistic;
alter session set nls_sort = binary_ci;
Note that you need both changes; if the comp parameter is binary, or if the sort parameter is not *_ci, you won't get what you want.
Use the "nuclear option" at your own risk, and only if you have a very good reason for it.
And, in any case, in your update statement, if you give the value 'PR', don't ever expect that it will be saved as 'pr', or vice versa; not unless you have a trigger that does that in the middle of the process, or some other kind of silly thing like that.

Related

conditional join (Oracle)

We have front-end app where user enters customer and/or PO to retrieve data.
For instance, if user want to retrieve all POs for a customer, he will enter '%' in PO field.
If user wants to retrieve all data, he will enter '%' in every field.
I am trying this, but it does not work
SELECT *
FROM PO_INFO
WHERE customer_id = case when '%' then customer_id else 'Macys' end
AND purchase_order = case when '%' then purchase_order else '79124' end
What am I missing?
You should not (some would say must not) just plug the user-entered search values into your SQL query as literals. That is,
AND purchase_order = case when '%' then purchase_order else '79124' end
... is not going to perform or scale well because every single search looks to Oracle like a brand new SQL query that has to get parsed and optimized (i.e., "hard parsed). This is an expensive process that also requires a lot of latches, meaning multiple users trying to run searches at the same time will have to wait for each other.
Instead, you should construct your SQL using bind variables. So,
AND purchase_order = :1 -- or something. We'll discuss the exact logic later...
That :1 is a bind variable, a placeholder for a value your code will supply when it submits the query. Now, everyone doing a search is using the same query (just each supplying different values for the bind variables). No more excessive hard parsing and performance disaster averted.
But, here is the next problem. One query for all means it only gets hard parse one time (good) but it also means everyone runs using the same execution plan. But in a search like this, one size does not fit all. Suppose the execution plan Oracle comes up with uses an index on column 'ABC'. If a user does not supply a bind variable value for column 'ABC', that execution plan will still be followed, but with terrible results.
So, what we want really is one SQL for each set of bind variables that have values or don't, but not one SQL for each distinct set of literal search values.
Build your SQL in code by starting with this string:
SELECT * FROM PO_INFO WHERE 1=1
Then, for each search condition add this (if the value is %)
AND (:1 IS NULL) -- and pass `NULL`, not "%" as the value for :1
(Aside: the reason for this condition, which is essentially NULL IS NULL is to make to so the number and order of the bind variables that have to be passed in is always the same, regardless of what the end user does or does not give you a value for. This makes it much easier to submit the SQL in some languages, like PL/SQL).
If the search condition is not %, add this:
AND (customer_id /* or whatever column */ = :1) -- and pass the user-entered value
So, for example, if the user specified values for customer_id and po_date but not purchase_order, your final SQL might look like:
SELECT *
FROM PO_INFO
WHERE 1=1
AND customer_id = :1
AND po_date := 2
AND :3 IS NULL -- this is purchase order and :3 you will pass as null
If you do all this, you'll get the least amount of hard-parsing and the best execution plan for each search.

MD5 of entire row in oracle

Below is the query to get MD5 of entire row in snowflake
SELECT MD5(TO_VARCHAR(ARRAY_CONSTRUCT(*)))FROM T
taken from here
what is the alternative query in oracle to achieve such requirement without having to put all column names manually.
You may use the packaged function dbms_sqlhash.gethash as described below, but remember:
the package was removed from the documentation (I guess in 11g), so in the recent releases this is an undocumented feature
if you calculate the hash code from more than one row you must define order (order by on a unique key(s)) . Otherwise the calculated hash is not deterministic. (This was probaly the reason of the removal)
the columns with other data types than varchar2 are converted to strings before the hash calculation, so the result is dependent on the NLS setting. You must stabilize the NLS setting to get reproducible results, e.g. with alter session set nls_date_format='dd-mm-yyyy hh24:mi:ss';
The column must be concatenated with some special delimiter (that does not occure in the data) to aviod collision: 'A' || null is the same as null || 'A'. This are unknown internals, so it is rather hard to compare the result MD5 hash with hash calculated on other (non Oracle) data.
You need extra grant to execute the package
Some additional info
Example
select * from tab where x=1;
X Y Z
---------- - -------------------
1 a 13.10.2021 00:00:00
select
dbms_sqlhash.gethash(
sqltext => 'select * from tab where x=1',
digest_type => 2 /* dbms_crypto.hash_md5 */
) MD5
from dual;
MD5
--------------------------------
215A9C4642A3691F951DD8060877D191
Order Independent Hash Code of a Table
Contrary to a file (where the order matter) in a database table the order is not relevant. It would be therefore meaningfull to have a possibility to calculate an order independent hash code of a table.
Unfortunately this feature is currently not available in Oracle, but was implemented as a prototype as described here

Can N function cause problems with existing queries?

We use Oracle 10g and Oracle 11g.
We also have a layer to automatically compose queries, from pseudo-SQL code written in .net (something like SqlAlchemy for Python).
Our layer currently wraps any string in single quotes ' and, if contains non-ANSI characters, it automatically compose the UNISTR with special characters written as unicode bytes (like \00E0).
Now we created a method for doing multiple inserts with the following construct:
INSERT INTO ... (...)
SELECT ... FROM DUAL
UNION ALL SELECT ... FROM DUAL
...
This algorithm could compose queries where the same string field is sometimes passed as 'my simple string' and sometimes wrapped as UNISTR('my string with special chars like \00E0').
The described condition causes a ORA-12704: character set mismatch.
One solution is to use the INSERT ALL construct but it is very slow compared to the one used now.
Another solution is to instruct our layer to put N in front of any string (except for the ones already wrapped with UNISTR). This is simple.
I just want to know if this could cause any side-effect on existing queries.
Note: all our fields on DB are either NCHAR or NVARCHAR2.
Oracle ref: http://docs.oracle.com/cd/B19306_01/server.102/b14225/ch7progrunicode.htm
Basicly what you are asking is, is there a difference between how a string is stored with or without the N function.
You can just check for yourself consider:
SQL> create table test (val nvarchar2(20));
Table TEST created.
SQL> insert into test select n'test' from dual;
1 row inserted.
SQL> insert into test select 'test' from dual;
1 row inserted.
SQL> select dump(val) from test;
DUMP(VAL)
--------------------------------------------------------------------------------
Typ=1 Len=8: 0,116,0,101,0,115,0,116
Typ=1 Len=8: 0,116,0,101,0,115,0,116
As you can see identical so no side effect.
The reason this works so beautifully is because of the elegance of unicode
If you are interested here is a nice video explaining it
https://www.youtube.com/watch?v=MijmeoH9LT4
I assume that you get an error "ORA-12704: character set mismatch" because your data inside quotes considered as char but your fields is nchar so char is collated using different charsets, one using NLS_CHARACTERSET, the other NLS_NCHAR_CHARACTERSET.
When you use an UNISTR function, it converts data from char to nchar (in any case that also converts encoded values into characters) as the Oracle docs say:
"UNISTR takes as its argument a text literal or an expression that
resolves to character data and returns it in the national character
set."
When you convert values explicitly using N or TO_NCHAR you only get values in NLS_NCHAR_CHARACTERSET without decoding. If you have some values encoded like this "\00E0" they will not be decoded and will be considered unchanged.
So if you have an insert such as:
insert into select N'my string with special chars like \00E0',
UNISTR('my string with special chars like \00E0') from dual ....
your data in the first inserting field will be: 'my string with special chars like \00E0' not 'my string with special chars like à'. This is the only side effect I'm aware of. Other queries should already use NLS_NCHAR_CHARACTERSET encoding, so it shouldn't be any problem using an explicit conversion.
And by the way, why not just insert all values as N'my string with special chars like à'? Just encode them into UTF-16 (I assume that you use UTF-16 for nchars) first if you use different encoding in 'upper level' software.
use of n function - you have answers already above.
If you have any chance to change the charset of the database, that would really make your life easier. I was working on huge production systems, and found the trend that because of storage space is cheap, simply everyone moves to AL32UTF8 and the hassle of internationalization slowly becomes the painful memories of the past.
I found the easiest thing is to use AL32UTF8 as the charset of the database instance, and simply use varchar2 everywhere. We're reading and writing standard Java unicode strings via JDBC as bind variables without any harm, and fiddle.
Your idea to construct a huge text of SQL inserts may not scale well for multiple reasons:
there is a fixed length of maximum allowed SQL statement - so it won't work with 10000 inserts
it is advised to use bind variables (and then you don't have the n'xxx' vs unistr mess either)
the idea to create a new SQL statement dynamically is very resource unfriedly. It does not allow Oracle to cache any execution plan for anything, and will make Oracle hard parse your looong statement at each call.
What you're trying to achieve is a mass insert. Use the JDBC batch mode of the Oracle driver to perform that at light-speed, see e.g.: http://viralpatel.net/blogs/batch-insert-in-java-jdbc/
Note that insert speed is also affected by triggers (which has to be executed) and foreign key constraints (which has to be validated). So if you're about to insert more than a few thousands of rows, consider disabling the triggers and foreign key constraints, and enable them after the insert. (You'll lose the trigger calls, but the constraint validation after insert can make an impact.)
Also consider the rollback segment size. If you're inserting a million of records, that will need a huge rollback segment, which likely will cause serious swapping on the storage media. It is a good rule of thumb to commit after each 1000 records.
(Oracle uses versioning instead of shared locks, therefore a table with uncommitted changes are consistently available for reading. The 1000 records commit rate means roughly 1 commit per second - slow enough to benefit of write buffers, but quick enough to not interfer with other humans willing to update the same table.)

Is it possible to configure Oracle session/connection to automatically uppercase strings?

Does anyone know if it's possible to configure an Oracle session or connection so that every string that gets persisted is automatically uppercased?
For example, if I invoke a SQL like this: "INSERT INTO STUDENT (name) VALUES ('john doe')"
The information in my table would be persisted like this:
STUDENT
--------------------
ID | 1
NAME | JOHN DOE
I've checked this entry but couldn't find anything like this: http://docs.oracle.com/cd/B19306_01/server.102/b14225/ch3globenv.htm#sthref186
Thanks!
There is no session-level configuration parameter for that, no.
You could write a trigger on the STUDENT table that would automatically store the data in uppercase but you'd need to do that for every table.
CREATE TRIGGER trg_student
BEFORE INSERT ON student
FOR EACH ROW
BEGIN
:new.name := upper( :new.name );
END;
Depending on the problem you are trying to solve, you could potentially set your session's NLS settings to ignore case sensitivity so that the string 'John Doeis considered to be equal to the stringJOHN DOE`. The options, limitations, and downsides to this will vary with the specific version of Oracle.

Inserting data into Oracle

I am trying to migrate a DB from Informix to Oracle.Informix had an option like while inserting into a table if the size of the value exceeds the column length then Informix automatically trims the data.But Oracle does not support this and always throws an exception.Is there a way to prevent and allow trim or we have to respect religiously?
There is no automatic trimming of data in Oracle, you have to trim it explicitly yourself e.g.
insert into mytable (id, text) values (123, substr(var,1,4000));
Oracle does support a variety of SQL functions which trim variables. I suspect the one you'll want is 'SUBSTR()'. The problem is that you will need to specify the desired length explicitly. In this example T23.WHATEVER is presumed to be VARCHAR2(30) and T24.TOO_LONG_COLUMN is, er, longer:
insert into t23
(id
, whatever)
select pk_col
, substr(too_long_col, 1, 30)
from t42
/
As well as Tony's suggestion, you can use a CAST
select cast ('1234' as varchar2(3)) a
from dual
If you are doing data migration, look into DML Error Logging
Having all your non-conformant data put into a corresponding table with the failure reason is positively dreamy.

Resources