Oracle SQL SELECT unpopulated records with the "NOT NULL" constraint - sql

In order to retrieve the data I need, I must SELECT records where there is no data. Some of the rows are unpopulated(?).
Many of these "unpopulated" rows are set with the Not Null constraint. This makes things difficult! I cannot simply search for NULL rows because they are NOT NULL.
I have been able to select or exclude unpopulated rows with a few methods. These methods seem to randomly work or not work.
Example: select or exclude records where st.sart_code or st.sart_hold or st.sart_status or st.sart_date is unpopulated.
SELECT
sp.sprite_id, sp.sprite_last, sp.sprite_first,
st.sart_code
/* 4 data retrieval methods are listed below.
For st.sart_code, I have substituted:
st.sart_hold, st.sart_status, and st.sart_date in the methods 2-4*/
FROM
sprite sp
JOIN sart st
on sp.sprite_pidm = st.sart_pidm
METHOD 1 - select records with rows that do not have the value EVEA -- st.sart_code could contain multiple values for one sp.sprite_id. This is a checklist of items. I am looking for records that do not have EVEA in the checklist
Varchar2 type with a Not Null constraint
WHERE
Sp.sprite_change_ind is null
and
st.sart_pidm NOT IN
(SELECT st.sart_pidm
FROM sart st
WHERE st.sart_code = 'EVEA')
METHOD 2 - select records with rows that do not have the value A2 -- st.sart_hold could contain multiple values for one sp.sprite_id. st.sart_hold may be blank/unpopulated (record has no holds) or contain several different holds. The values are account hold types. I am looking for records that do not have that particular "A2" hold.
Varchar2 type with a Not Null constraint
EDIT I just realized that this works ONLY if there is at least one hold already. If the person has no holds, this script will not select the records (even though the person also has no A2 hold).
WHERE
Sp.sprite_change_ind is null
and
group by sp.sprite_id, sp.sprite_last, sp.sprite_first, st.sart_hold
having sum(case when st.sart_hold = 'A2' then 1 else 0 end) = 0;
METHOD 3 - select records with rows that have no value for st.sart_status -- st.sart_status could contain only 1 of 3 possible values or NO value for one sp.sprite_id. The values are file statuses. I am looking for records that have no status
Varchar2 type with a Not Null constraint
WHERE
Sp.sprite_change_ind is null
and
trim(st.sart_status) is null
METHOD 4 - select records with rows that are NOT missing ANY values in st.sart_date (all date fields in list are populated) -- st.sart_date could either contain a date or be blank/unpopulated for one sp.sprite_id. The value is a received date for a checklist item. I am excluding ANY record that has no date for any of the checklist items (there may be many items with corresponding dates).
Date type with a Not Null constraint
This is a little different, so I am including the first part again.
with MYVIEW AS
(
SELECT
sp.sprite_id AS Per_ID
sp.sprite_last,
sp.sprite_first,
st.sart_date as RECEIVED_DATE
FROM
sprite sp
JOIN sart st
on sp.sprite_pidm = st.sart_pidm
WHERE
Sp.sprite_change_ind is null
)
Select
Per_ID as "ID",
max(SPRITE_LAST_NAME) as "Last",
max(SPRITE_FIRST_NAME) as "First",
FROM MYVIEW
GROUP BY Per_ID
HAVING SUM(NVL2(RECEIVED_DATE,0,1)) = 0
My questions: I have had a difficult time finding methods of working with Not Null constraint fields.
EDIT: How do I see what is in the "not null" constrained field when it is not populated?
Why do the methods above not always work when looking for unpopulated fields? Do certain methods only work with certain data types (varchar2, number, date)? Or does it have to do with the type of JOIN I use? Something else?
Are there other methods out there someone could please direct me to? Any guidance would be greatly appreciated!
What is the correct terminology for "selecting records where there are unpopulated fields of [ColumnName DataType() NOT NULL]?" If I knew the terminology for what I am trying to ask, I could search for it.
NOTE My scripts are usually MUCH more involved than the examples above. I usually have at least 3 joins and many WHERE clauses.
Please let me know if this question is too involved! I am new here. :-)

Probably more a long comment than an answer, but since there isn't much activity here...
Oracle SQL SELECT blank records with the “NOT NULL” constraint Auntie Anita
- How do you have blanks if they are not null - is that partly what you're asking? Alex Poole
- That is one of the problems. Auntie Anita
Few things to know:
In Oracle the empty string '' is the same thing as NULL for VARCHAR/CHAR. That's a departure from "standard" SQL that makes a distinction between empty strings and NULL strings.
TRIM will return NULL for NULL/empty strings/space only strings.
but strings composed of spaces/invisible characters are not null. Even if they only contains the character CHR(0) (aka NUL -- with only one L )
and TRIM does not remove invisible characters. Only spaces.
To convince yourself, try those:
select NVL2(CAST('' AS VARCHAR2(20)), 'NOT NULL','NULL') FROM DUAL
select NVL2(CAST('' AS CHAR(20)), 'NOT NULL','NULL') FROM DUAL
select NVL2(TRIM(' '), 'NOT NULL','NULL') FROM DUAL
select NVL2(' ', 'NOT NULL','NULL') FROM DUAL
select NVL2(CHR(10), 'NOT NULL','NULL') FROM DUAL
select NVL2(CHR(0), 'NOT NULL','NULL') FROM DUAL
select NVL2(TRIM(' '||CHR(10)), 'NOT NULL','NULL') FROM DUAL
select NVL2(TRIM(' '||CHR(0)), 'NOT NULL','NULL') FROM DUAL
So, my guess is your "not null empty fields" in fact contain either some invisible characters -- or maybe even a single CHR(0). This is quite possible as in some languages, the NUL character is used as string terminator -- and might have sneaked into your DB at data import time for empty/missing values. Intentionally or not.
To check for that, you might want to try RAWTOHEX to examine your suspect data fields. In the following example, notice how the middle NUL character is lurking unnoticed when displayed as a string. But not in the raw hex dump:
SQL> select 'abc' || chr(0) || 'def' AS str,
RAWTOHEX('abc' || CHR(0) || 'def') AS hex FROM DUAL
STR HEX
abcdef 61626300646566
^^^^^^ ^^
Is there something Yes !
special here?
Please let me know if this question is too involved! I am new here. :-)
:D "StackOverflow" is usually much more efficient if you are able to narrow down your issue. Ideally providing some reproducible case (formelly know as SSCCE or MCVE).
Take time to examine closely your data, and if needed, don't hesitate to post an other more focused answer.

Related

Comparing two empty Strings in Oracle SQL

Hi today I have met with weird situation. I had a where clause where was condition which returns String and I wanted to check if it's empty or not. And when it returns empty string Oracle still treat it like a different Strings. So I went further and prepared simple queries:
select 1 from dual where 1 = 1;
returns: 1
select 1 from dual where 'A' = 'A';
returns: 1
And now what I cannot understand:
select 1 from dual where '' = '';
No result.
Even if I check if they are different there is still no result.
select 1 from dual where '' != '';
No result.
Can someone explain it for me ?
Oracle treats empty strings as NULL. It's a gotcha. Make a note of it and hope it never bites you in the butt in production.
The reason is as #Captain Kenpachi explained. If want to compare two strings (or other types that are the same) and want to be tolerant of NULLs (or empty string in Oracle as it treats it as the same) then you need to involve an IS test.
You could try the common cheat of using a rogue value that will never be used but Murphy's Law dictates that one day someone will. This technique also has the drawback that the rogue value should match the type of the thing you are comparing i.e. comparing strings you need a rogue string while comparing dates you need a rouge date. This also means you can't cut-and-paste it liberally without applying a little thought. Example:
WHERE NVL(col1,'MyRougeValue')=NVL(col2,'MyRougeValue')
The standard version is to explicitly test for NULLs
WHERE (col1=col2 OR (col1 IS NULL AND col2 IS NULL))
The opposite becomes WHERE NOT(col1=col2 OR (col1 IS NULL AND col2 IS NULL))
I have seen the a long winded opposite version (as seen in Toad's data compare tool)
WHERE (col1<>col2 OR (col1 IS NULL AND col2 IS NOT NULL) OR (col1 IS NOT NULL AND col2 IS NULL))
Oracle does have a handy DECODE function that is basically is IF a IS b THEN c ELSE d so equality is WHERE DECODE(col1,col2,1,0)=1 and the opposite is WHERE DECODE(col1,col2,1,0)=0. You may find this a little slower than the explicit IS test. It is proprietary to Oracle but helps make up for the empty string problem.

Compare strings with trailing spaces in Firebird SQL?

I have an existing database with a table with a string[16] key field.
There are rows whose key ends with a space: "16 ".
I need to allow user to change from "16 " to e.g. "16" but also do a unique key check (i.e. the table does not have already a record with key="16").
I run the following query:
select * from plu__ where store=100 and plu_num = '16'
It returns the row with key="16 "!
How do I check for unique key so that keys with trailing spaces are not included?
EDIT: The DDL and the char_length
CREATE TABLE PLU__
(
PLU_NUM Varchar(16),
CAPTION Varchar(50),
...
string[16] - there is no such datatype in Firebird. There are CHAR(16) and VARCHAR(16) (and BLOB SUBTYPE TEXT, but it is improbable here). So you omit some crucial points about your system. You do not work with Firebird, but with some undisclosed intermediate layer, that is no one knows how opaque or transparent.
I suspect you or your system chose CHAR datatype instead of VARCHAR where all data is right-padded with space to the max. OR maybe the COLLATION of the column/table/database is so that trailing spaces do not matter.
Additionally, you may be just wrong. You claim that the row being Selected does contain the trailing blank, but I do not see it. For example, add CHAR_LENGTH(plu_num) to the columns in your SELECT and see what is there.
Additionally, if plu_num is number - should it not be integer or int64 rather than text?
Bottom of your screenshot shows "(NONE)". I suspect that is the "connection charset". This is allowed for backward compatibility with programs made 20 years ago, but it is quite dangerous today. You have to consult your system documentation, how to set the connection charset to URF-8 or Windows-1250 or something meaningful.
"How do I check for unique key so that keys with trailing spaces are not included?" you do not. You just can not do it reliably, because of different transactions and different programs making simultaneous connections. You would check it, decide you are clear, but right before you would insert your row - some other computer would insert it too. That gap can not be crossed that way, between your two commands of checking and inserting - anyone else can do it too. It is called race conditions.
You have to ask the server to do the checks.
For example, you have to introduce UNIQUE CONSTRAINT on the pair of columns (store, plu_num). That way the server would refuse to store two rows with the same values in those columns, visible in the same transaction.
Additionally, is it even normal to have values with spaces? Convert the field to integer datatype and be safe.
Or if you want to keep it textual and non-numeric you still can
Introduce CHECK CONSTRAINT that trim(plu_num) is not distinct from plu_num (or if plu_num is declared as a NOT NULL column to the server, then trim(plu_num) = plu_num). That way the server would refuse storing any value with spaces before or after the text.
In a case the datatype or the collation of the column makes no difference for comparing texts with and without trailing spaces (and in case you can not change that datatype or collation), you may try adding tokens, like ('+' || trim(plu_num) || '+') = ('+' || plu_num || '+')
Or instead of that CHECK CONSTRAINT, you can have proactively remove those spaces: set new before update or insert TRIGGER on the table, that would do like NEW.plu_num = TRIM(NEW.plu_num)
Documentation:
https://www.firebirdsql.org/refdocs/langrefupd20-distinct.html
http://www.firebirdtest.com/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-ddl-tbl.html#fblangref25-ddl-tbl-constraints
http://www.firebirdtest.com/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-ddl-tbl.html#fblangref25-ddl-tbl-altradd
http://www.firebirdtest.com/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-ddl-trgr.html
http://www.firebirdtest.com/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-datatypes-chartypes.html
Also, via http://www.translate.ru a bit more verbose:
http://firebirdsql.su/doku.php?id=constraint
http://firebirdsql.su/doku.php?id=alter_table
You may also check http://www.firebirdfaq.org/cat3/
Additionally, if you add the constraints onto existing table with non-valid data entered earlier before you introduced those checks, you might trap yourself into "non-restorable backup" situation. You would have to check for it, and sanitize your old data to abide by newly introduced constraints.
Option #4 explained in detail is below. Just this seems be a bad idea of database design! One should not just "let people edit number to remove trailing blanks", one should make the database design so that there would be no any numbers with trailing blank and would be no any way to insert them into the database.
CREATE TABLE "_NEW_TABLE" (
ID INTEGER NOT NULL,
TXT VARCHAR(10)
);
Select id, txt, '_'||txt||'_', char_length(txt) from "_NEW_TABLE"
ID TXT CONCATENATION CHAR_LENGTH
1 1 _1_ 1
2 2 _2_ 1
4 1 _1 _ 2
5 2 _2 _ 2
7 1 _ 1_ 2
8 2 _ 2_ 2
Select id, txt, '_'||txt||'_', char_length(txt) from "_NEW_TABLE"
where txt = '2'
ID TXT CONCATENATION CHAR_LENGTH
2 2 _2_ 1
5 2 _2 _ 2
Select id, txt, '_'||txt||'_', char_length(txt) from "_NEW_TABLE"
where txt || '+' = '2+' -- WARNING - this PROHIBITS index use on txt column, if there is any
ID TXT CONCATENATION CHAR_LENGTH
2 2 _2_ 1
Select id, txt, '_'||txt||'_', char_length(txt) from "_NEW_TABLE"
where txt = '2' and char_length(txt) = char_length('2')

Assign a case value to a column rather than an alias

This should be a simple one, but I have not found any solution:
The normal way is using an alias like this:
CASE WHEN ac_code='T' THEN 'time' ELSE 'purchase' END as alias
When using alias in conjunction with UNION ALL this causes problem because the alias is not treated the same way as the other columns.
Using an alias to assign the value is not working. It is still treated as alias, though it has the column name.
CASE WHEN ac_code='T' THEN 'time' ELSE 'purchase' END as ac_subject
I want to assign a value to a column based on a condition.
CASE WHEN ac_code='T' THEN ac_subject ='time' ELSE ac_subject='purchase' END
Now I get the error message
UNION types character varying and boolean cannot be matched
How can I assign a value to a column in a case statement without using an alias in the column (shared by other columns in UNION)?
Here is the whole (simplified) query:
SELECT hr_id,
CASE WHEN hr_subject='' THEN code_name ELSE hr_subject END
FROM hr
LEFT JOIN code ON code_id=hr_code
WHERE hr_job='123'
UNION ALL
SELECT po_id,
CASE WHEN po_subject='' THEN code_name ELSE po_subject END
FROM po
LEFT JOIN code ON code_id=po_code
WHERE po_job='123'
UNION ALL
SELECT ac_id,
CASE WHEN ac_code='T' THEN ac_subject='time' ELSE ac_subject='purchase' END
FROM ac
WHERE ac_job='123'
There is no alias in your presented query. You are confusing terms. This would be a column alias:
CASE WHEN hr_subject='' THEN code_name ELSE hr_subject END AS ac_subject
In a UNION query, the number of columns, column names and data types in the returned set are determined by the first row. All appended rows have to match the row type. Column names in appended rows (including aliases) are just noise and ignored. Maybe useful for documentation, nothing else.
The = operator does not assign anything in a SELECT query. It's the equality operator that returns a boolean value. TRUE if both operands are equal, etc. This returns a boolean value: ac_subject='time' Hence your error message:
UNION types character varying and boolean cannot be matched
The only way to "assign" a value to a particular output column in this query is to include it at the right position in the SELECT list.
The information in the question is incomplete, but I suspect you are also confusing the empty string ('') with the NULL value. A distinction that you need to understand before doing anything else with relational databases. Maybe start here. In this case you would rather use COALESCE to provide a default for NULL values:
SELECT hr_id, COALESCE(hr_subject, code_name) AS ac_subject
FROM hr
LEFT JOIN code ON code_id=hr_code
WHERE hr_job = '123'
UNION ALL
SELECT po_id, COALESCE(po_subject, code_name)
FROM po
LEFT JOIN code ON code_id=po_code
WHERE po_job = '123'
UNION ALL
SELECT ac_id, CASE WHEN ac_code = 'T' THEN 'time'::varchar ELSE 'purchase' END
FROM ac
WHERE ac_job = '123'
Just an educated guess, assuming type varchar. You should have added table qualification to column names to clarify their origin. Or table definitions to clarify everything.
The CASE expression is supposed to return a value, e.g. 'time'.
Your value is another expression subject ='time' which is a boolean (true or false).
Is this on purpose? Does the other query you glue with UNION have a boolean in that place, too? Probably not, and this is what the DBMS complains about.
I found the problem.
CASE WHEN hr_subject=’’ THEN code_name ELSE hr_subject END
The columns code_name and hr_subject was different length. This caused the unpredictable result. I think that aliases can work now.
Thank you for your support.

Oracle sql null value is not selected

In NAME table FIRST column is having null but no rows are selected. Please help me to understand.
SELECT * FROM NAME WHERE FIRST != '1'
Any comparison with null is false - = and <>, >, <, and so on. You cannot use null in an IN list as well - it would be ignored. Moreover, two nulls are not even equal to each other.
To get the nulls, you need to ask for them explicitly, like this:
SELECT * FROM NAME WHERE FIRST IS NULL OR FIRST != '1'
Any comparison to NULL returns NULL, which is equivalent to FALSE. This is true eve of not-equals.
If you want to include NULL values, do one of the following:
where first <> '1' or first is null
or
where coalesce(first, '<null>') <> '1'
In Oracle, null is not considered a legal value to select unless you explicitly ask for it:
select * from name where (first != '1') or first is null
You could also use NVL (similar to coalesce):
select * from name where nvl(first,'0') != '1'
That is correct because NULL can never be compared with anything else....
The only option that you have is to include a NULL check as an or in the command
SELECT * FROM NAME WHERE FIRST!=1 OR FIRST IS NULL
According to Oracle Documentation NULL is defined as a value not knownm or when the value is not meaningful. That is solely the reason why Oracle mentions not consider a value of ZERO as NULL. This is just an FYI, an addon. Thanks!
NULL is dumb. Period.
NULL is evil.
If X is NULL and Y is NULL, then X does in fact equal Y because they are both NULL.
It's also a PITA that I can't say
IF X IN ('a','B','C', null)
Because this condition happens. But now I have to say
IF ( X IN ('a','B','C') or X is NULL )
which is a waste of time and a risk of error if I forget the parentheses.
What irks me further is that NULL shouldn't happen in the first place. Fields (er... ok kids, I'll call them Columns) should always be initialized. Period. Stop the nulls. Don't allow them. Default values should always be zeroes or blanks so that those folks that are too lazy to initialize columns in their software will have them initialized for them automatically.
There are many instances where a failure to define default values of zeroes and blanks makes life more difficult than it has to be.

How do I extract a substring from a random position in a string using built-in functions?

I have a series of data stored in the following fashion:
Word of various kinds (ANT\username1) and even more words
This is another row, the words are random (ANT\username2)
Thankfully the username only ever shows once (ANT\username1)
Above represents three seperate rows.
The general flow of this data is:
Parenthesis can appear anywhere in the text
The username portion of each string (ANT\usernamex) will only ever appear once
The text preceeding and proceeding the username portion is always different lengths.
The username text may not always be present
As you probably already guessed what I need to do is take the username from each row and where it isn't present return null. Unfortunately I have no idea how to approach this - I've played around with left() and right() functions but don't really know how else to tackle this. Would appreciate if any answers that use a number of functions to accomplish the task have a quick blurb explaining the flow of logic (so I can then read the documentation for the functions to learn).
Note the specific results when the data is not as expected. This works for exactly the format '(ANT\....)'.
-- sample table
create table t(s varchar(max));
insert t select
'Word of various kinds (ANT\) blank' union all select
'Word of various kinds (ANT) blank' union all select
'Word of various kinds (ANT\ no closing' union all select
'Word of various kinds (ANT\(ANT\me) double up' union all select
'' union all select
'(ANT\' union all select
null union all select
'Word of various kinds (ANT\username1) and even more words' union all select
'This is another row, the words are random (ANT\username2)' union all select
'Thankfully the username only ever shows once (ANT\username1)';
-- Query
select Original = s,
Extracted = nullif(STUFF(LEFT(s, CharIndex(')',s+')',
PatIndex('%(ANT\%', s)) -1), 1,
PatIndex('%(ANT\%', s + '(ANT\')+4,''),'')
from t;