20

I have to write a component that re-creates SQL Server tables (structure and data) in an Oracle database. This component also has to take new data entered into the Oracle database and copy it back into SQL Server.

Translating the data types from SQL Server to Oracle is not a problem. However, a critical difference between Oracle and SQL Server is causing a major headache. SQL Server considers a blank string ("") to be different from a NULL value, so a char column can be defined as NOT NULL and yet still include blank strings in the data.

Oracle considers a blank string to be the same as a NULL value, so if a char column is defined as NOT NULL, you cannot insert a blank string. This is causing my component to break whenever a NOT NULL char column contains a blank string in the original SQL Server data.

So far my solution has been to not use NOT NULL in any of my mirror Oracle table definitions, but I need a more robust solution. This has to be a code solution, so the answer can't be "use so-and-so's SQL2Oracle product".

How would you solve this problem?

Edit: here is the only solution I've come up with so far, and it may help to illustrate the problem. Because Oracle doesn't allow "" in a NOT NULL column, my component could intercept any such value coming from SQL Server and replace it with "@" (just for example).

When I add a new record to my Oracle table, my code has to write "@" if I really want to insert a "", and when my code copies the new row back to SQL Server, it has to intercept the "@" and instead write "".

I'm hoping there's a more elegant way.

Edit 2: Is it possible that there's a simpler solution, like some setting in Oracle that gets it to treat blank strings the same as all the other major database? And would this setting also be available in Oracle Lite?

Sergio
  • 6,900
  • 5
  • 31
  • 55
MusiGenesis
  • 74,184
  • 40
  • 190
  • 334
  • Regarding your Edit 2: There is a horrible solution involving using a CLOB for all these columns. Oracle distinguishes between NULL and an empty CLOB. You could store the CLOB inline in the table to reduce the performance impact. I wouldn't do this (see my actual answer below). – WW. Oct 25 '11 at 06:29
  • @WW.: you're totally right - that's a horrible solution. – MusiGenesis Oct 25 '11 at 13:25
  • 1
    Holy cow. SQL Server has it's own share of stupidness, but this is pure nonsense. (Is this still with current 2014+ Oracle, and has there been an "ANSI NULL" setting added for it? SQL Server had some issues like this pre-"ANSI NULL" handling.) – user2864740 Jan 27 '15 at 16:11
  • 3
    @user2864740 - I don't think it has changed since. Oracle 12c R1 doc (http://docs.oracle.com/database/121/SQLRF/sql_elements005.htm#SQLRF30037) still states this: *"Oracle Database currently treats a character value with a length of zero as null. However, this may not continue to be true in future releases, and Oracle recommends that you do not treat empty strings the same as nulls."*, the same phrase from at least Oracle 8 -:) – Simon Mourier Jan 28 '15 at 06:27
  • 2
    @SimonMourier Hilarious that they tell you to treat them differently, but fail to provide the tools to achieve that in a sane way... and now they'll probably have an hard time changing that due to backward compatibility, since a lot of dbs are using hacks to work around this which will probably break when it gets fixed... – Bakuriu Feb 03 '15 at 14:51

11 Answers11

10

I don't see an easy solution for this.

Maybe you can store your values as one or more blanks -> ' ', which aren't NULLS in Oracle, or keep track of this special case through extra fields/tables, and an adapter layer.

Camilo Díaz Repka
  • 4,805
  • 5
  • 43
  • 68
9

My typical solution would be to add a constraint in SQL Server forcing all string values in the affected columns to have a length greater than 0:

CREATE TABLE Example (StringColumn VARCHAR(10) NOT NULL)

ALTER TABLE Example
ADD CONSTRAINT CK_Example_StringColumn CHECK (LEN(StringColumn) > 0)

However, as you have stated, you have no control over the SQL Database. As such you really have four choices (as I see it):

  1. Treat empty string values as invalid, skip those records, alert an operator and log the records in some manner that makes it easy to manually correct / re-enter.
  2. Convert empty string values to spaces.
  3. Convert empty string values to a code (i.e. "LEGACY" or "EMPTY").
  4. Rollback transfers that encounter empty string values in these columns, then put pressure on the SQL Server database owner to correct their data.

Number four would be my preference, but isn't always possible. The action you take will really depend on what the oracle users need. Ultimately, if nothing can be done about the SQL database, I would explain the issue to the oracle business system owners, explain the options and consequences and make them make the decision :)

NOTE: I believe in this case SQL Server actually exhibits the "correct" behaviour.

Bridge
  • 29,818
  • 9
  • 60
  • 82
Dr8k
  • 1,088
  • 5
  • 11
3

Do you have to permit empty strings in the SQL Server system? If you can add a constraint to the SQL Server system that disallows empty strings, that is probably the easiest solution.

Justin Cave
  • 227,342
  • 24
  • 367
  • 384
3

Its nasty and could have unexpected side effects.. but you could just insert "chr(0)" rather than ''.

drop table x

drop table x succeeded.
create table x ( id number, my_varchar varchar2(10))

create table succeeded.
insert into x values (1, chr(0))

1 rows inserted
insert into x values (2, null)

1 rows inserted
select id,length(my_varchar) from x

ID                     LENGTH(MY_VARCHAR)     
---------------------- ---------------------- 
1                      1                      
2                                             

2 rows selected

select * from x where my_varchar is not null

ID                     MY_VARCHAR 
---------------------- ---------- 
1                      
Matthew Watson
  • 14,083
  • 9
  • 62
  • 82
3

NOT NULL is a database constraint used to stop putting invalid data into your database. This is not serving any purpose in your Oracle database and so I would not have it.

I think you should just continue to allow NULLS in any Oracle column that mirrors a SqlServer column that is known to contain empty strings.

If there is a logical difference in the SqlServer database between NULL and empty string, then you would need something extra to model this difference in Oracle.

WW.
  • 23,793
  • 13
  • 94
  • 121
  • I think this is the only sensible answer here. Basically if the developers of the sql server database are storing empty strings in NOT NULL columns then these columns will need to be nullable in the oracle db. So MusiGenesis had the correct answer from the start and needed to do exactly nothing to solve the problem. – MikeKulls Jul 04 '12 at 23:07
2

For those that think a Null and an empty string should be considered the same. A null has a different meaning from an empty string. It captures the difference between 'undefined' and 'known to be blank'. As an example a record may have been automatically created, but never validated by user input, and thus receive a 'null' in the expectation that when a user validates it, it will be set to be empty. Practically we may not want to trigger logic on a null but may want to on an empty string. This is analogous to the case for a 3 state checkbox of Yes/No/Undefined.

Both SQL and Oracle have not got it entirely correct. A blank should not satisfy a 'not null' constraint, and there is a need for an empty string to be treated differently than a null is treated.

2

I'd go with an additional column on the oracle side. Have your column allow nulls and have a second column that identifies whether the SQL-Server side should get a null-value or empty-string for the row.

Nicholas Hirras
  • 2,592
  • 2
  • 21
  • 28
1

If you are migrating data you might have to substitute a space for an empty string. Not very elegant, but workable. This is a nasty "feature" of Oracle.

Mike Thompson
  • 6,708
  • 3
  • 32
  • 39
  • 2
    I wanted to title my question "why on Earth does Oracle equate blank strings with nulls?" – MusiGenesis Oct 02 '08 at 05:12
  • 1
    After thinking about this one for a few years, I think Oracle got it right. SQL Server defines NULL from a programmer's perspective: to a programmer, a blank string is still *something*, and very distinct from a null variable in most code languages. Oracle defines it from an end-user (represented by the database designer in this case) perspective; if, as a DB designer, I specify a column to be NOT NULL, I do it so that users (including programmers) put a real value there for each row. I can't really think of a single example of a table I've ever designed where I would have wanted ... – MusiGenesis Jun 01 '11 at 01:48
  • 1
    ... people putting blank strings (e.g. zip code, social security number, etc.) into my NOT NULL field. I don't think I was even aware that SQL Server *allows* blanks in NOT NULL fields until I read this question. TL;DR: SQL Server hack allows programmers to circumvent db designers' intentions. – MusiGenesis Jun 01 '11 at 01:54
0

I've written an explanation on how Oracle handles null values on my blog a while ago. Check it here: http://www.psinke.nl/blog/hello-world/ and let me know if you have any more questions. If you have data from a source with empty values and you must convert to an Oracle database where columns are NOT NULL, there are 2 things you can do:

  • remove the not null constraint from the Oracle column
  • Check for each individual column if it's acceptable to place a ' ' or 0 or dummy date in the column in order to be able to save your data.
Non Plus Ultra
  • 867
  • 7
  • 17
0

Well, main point I'd consider is absence of tasks when some field can be null, the same field can be empty string and business logic requires to distinguish these values. So I'd make this logic:

  • check MSSQL if column has NOT NULL constraint
  • check MSSQL if column has CHECK(column <> '') or similar constraint

If both are true, make Oracle column NOT NULL. If any one is true, make Oracle column NULL. If none is true, raise INVALID DESIGN exception (or maybe ignore it, if it's acceptable by this application).

When sending data from MSSQL to Oracle, just do nothing special, all data would be transferred right. When retrieving data to MSSQL, any not-null data should be sent as is. For null strings you should decide whether it should be inserted as null or as empty string. To do this you should check table design again (or remember previous result) and see if it has NOT NULL constraint. If has - use empty string, if has not - use NULL. Simple and clever.

Sometimes, if you work with unknown and unpredictable application, you cannot check for existence of {not empty string} constraint because of various forms of it. If so, you can either use simplified logic (make Oracle columns always nullable) or check whether you can insert empty string into MSSQL table without error.

Sanders the Softwarer
  • 2,478
  • 1
  • 13
  • 28
0

Although, for the most part, I agree with most of the other responses (not going to get into an argument about any I disagree with - not the place for that :) )

I do notice that OP mentioned the following:

"Oracle considers a blank string to be the same as a NULL value, so if a char column is defined as NOT NULL, you cannot insert a blank string."

Specifically calling out CHAR, and not VARCHAR2. Hence, talking about an "empty string" of length 0 (ie '' ) is moot. If he's declared the CHAR as, for example, CHAR(5), then just add a space to the empty string coming in, Oracle's going to pad it anyway. You'll end up with a 5 space string.

Now, if OP meant VARCHAR2, well yeah, that's a whole other beast, and yeah, the difference between empty string and NULL becomes relevant.

  SQL> drop table junk;

  Table dropped.

  SQL>
  SQL> create table junk ( c1     char(5) not null );

  Table created.

  SQL>
  SQL> insert into junk values ( 'hi' );

  1 row created.

  SQL>
  SQL> insert into junk values ( ' ' );

  1 row created.

  SQL>
  SQL> insert into junk values ( '' );
  insert into junk values ( '' )
                            *
  ERROR at line 1:
  ORA-01400: cannot insert NULL into ("GREGS"."JUNK"."C1")


  SQL>
  SQL> insert into junk values ( rpad('', 5, ' ') );
  insert into junk values ( rpad('', 5, ' ') )
                            *
  ERROR at line 1:
  ORA-01400: cannot insert NULL into ("GREGS"."JUNK"."C1")


  SQL>
  SQL> declare
    2    lv_in   varchar2(5) := '';
    3  begin
    4     insert into junk values ( rpad(lv_in||' ', 5) );
    5  end;
    6  /

  PL/SQL procedure successfully completed.

  SQL>
Ditto
  • 3,256
  • 1
  • 14
  • 28