Oracle: Is there a simple way to say "if null keep the current value" in merge/update statements? - sql

I have a rather weak understanding of any of oracle's more advanced functionality but this should I think be possible.
Say I have a table with the following schema:
MyTable
Id INTEGER,
Col1 VARCHAR2(100),
Col2 VARCHAR2(100)
I would like to write an sproc with the following
PROCEDURE InsertOrUpdateMyTable(p_id in integer, p_col1 in varcahr2, p_col2 in varchar2)
Which, in the case of an update will, if the value in p_col1, p_col2 is null will not overwrite Col1, Col2 respectively
So If I have a record:
id=123, Col1='ABC', Col2='DEF'
exec InsertOrUpdateMyTable(123, 'XYZ', '098'); --results in id=123, Col1='XYZ', Col2='098'
exec InsertOrUpdateMyTable(123, NULL, '098'); --results in id=123, Col1='ABC', Col2='098'
exec InsertOrUpdateMyTable(123, NULL, NULL); --results in id=123, Col1='ABC', Col2='DEF'
Is there any simple way of doing this without having multiple SQL statements?
I am thinking there might be a way to do this with the Merge statement though I am only mildly familiar with it.
EDIT:
Cade Roux bellow suggests using COALESCE which works great! Here are some examples of using the coalesce kewyord.
And here is the solution for my problem:
MERGE INTO MyTable mt
USING (SELECT 1 FROM DUAL) a
ON (mt.ID = p_id)
WHEN MATCHED THEN
UPDATE
SET mt.Col1 = coalesce(p_col1, mt.Col1), mt.Col2 = coalesce(p_col2, mt.Col2)
WHEN NOT MATCHED THEN
INSERT (ID, Col1, Col2)
VALUES (p_id, p_col1, p_col2);

Change the call or the update statement to use
nvl(newValue, oldValue)
for the new field value.

Using MERGE and COALESCE? Try this link for an example
with
SET a.Col1 = COALESCE(incoming.Col1, a.Col1)
,a.Col2 = COALESCE(incoming.Col2, a.Col2)

Related

How can you use NULLIF in the where clause?

I am trying to do something like this:
select
col_1, col_2, etc
from
table
where
col_1 = nullif('', '')
Am I doing this incorrectly? I am not getting any results back.
Edit:
My expected results are to get every record back where col_1 is NULL.
I know I can use where col_1 is null, but I am using SSIS and a variable. Sometimes the col_1 is actually NULL and sometimes it is not.
Sample data:
collaboration first_name last_name city
NULL Bob Smith Chicago
Data Migration John Smith Austin
NULL Pika Chu Houston
Production ash ketchum tokyo
Sometimes I may want to return the records where collaboration is NULL, sometimes I want to return the records where it says Production.
I'd like to use the same query, if possible, with little modification.
Edit Part 2:
I tried to experiment with this.
select
col_1, col_2, etc
from
table
where
case
when col_1 = '' then NULL
else col_1
end
But I am getting the error message:
An expression of non-boolean type specified in a context where a condition is expected, near ORDER.
Query speed it not something I am concerned with.
This is the query you need
select
col_1, col_2, etc
from
table
where
col_1 is null
is null checks if a column is null, nullif(#expr1,#expr2) could be rewritten as:
case when #expr1 = #expr2 return null else return #expr1 end
EDIT:
you can relax filters adding OR condition into the 'where' clause (TIP: remember AND is evaluated before OR)
select
col_1, col_2, etc
from
table
where
(col_1 is null OR col1 like 'production')
if you want to decide runtime wich one you neeed you could write a procedure:
create proc my_proc #var AS varchar(100) = 'NULL§159§' -- this defaults to null, if you put a parameter it queries with parameter passed
as
select
col_1, col_2, etc
from
table
where
WHERE coalesce(col_1,'NULL§159§') = #var
-- added §159§ symbol to the null to make sure the queried string is impossible in the database,
-- obviously into the database the value 'NULL159' hase become a sort of 'reserved word', but hopefully is odd enough not to appear in data
GO
and call it by exec my_proc('production')
Try this, it can handle the column with null values or empty space
SELECT
col_1, col_2, etc
FROM
Table
WHERE
ISNULL(NULLIF(col_1 ,''),'1') = '1'
You can do something like
select
col_1, col_2, etc
from
table
where
col_1 IS NULL OR col_1 = ''
select
col_1, col_2, etc
from
table
where
collaboration IS NULL OR collaboration ='Production'
Crystal ball time from me. This is my guess on what the OP wants:
DECLARE #Prod varchar(15);
--SET #Prod = 'Production';
SELECT {Columns}
FROM YourTable
WHERE Col1 = #Prod
OR (Col1 IS NULL AND #Prod IS NULL);
Try this.
DECLARE #SearchValue VARCHAR(50)
SELECT col_1, col_2, etc
FROM YourTable
WHERE ISNULL(col_1,'') = ISNULL(#SearchValue,'')

Using Common Table Expression and IF EXISTS

I am running a query similar to
DECLARE #VARIABLE NVARCHAR(50) = 'VALUE';
WITH MYCTE_TABLE (Column1,Column2)
AS
SELECT
(ColumnA, Column B
FROM
SomeTable
WHERE
ColumnA = SomeValue)
IF EXISTS(SELECT ColumnZ FROM AnotherTable WHERE Columnz = SomeNumbers)
BEGIN
SELECT * FROM MYCTE_TABLE
END
ELSE
BEGIN
MYSUBQUERY2
END
...
However, I keep getting the following error:
Incorrect syntax near the keyword 'IF'.
Each subquery works well when run independently. It seems the use of a common table expression before the IF EXISTS is causing the issue.
Any help please?
I really doubt, that this is the best approach... You tried to clean and shorten this for brevitiy (thumbs up for this!), but the given information is - maybe - not enough.
You cannot use a CTE in different queries. A CTE is fully inlined as part of the query...
But you could write your values into a table variable like here:
DECLARE #tbl TABLE(Column1 INT, Column2 VARCHAR(100)); --Choose appropriate types
INSERT INTO #tbl
SELECT ColumnA, ColumnB FROM SomeTable WHERE ColumnA=SomeValue;
This table variable can be used in later queries (but in the same job!) like any other table:
SELECT *
FROM SomeTable AS st
INNER JOIN #tbl AS tbl ON ...
... or similiar usages...
Another approach might be this
SELECT Column1,Column2 INTO #SomeTempTable FROM SomeWhere
This will write the result of the SELECT into a temp table (which is session wide).
I'm quite sure, that there might be a better (set-based) approach... Are the two sub-queries identical in their result set's structure? If so, you might use UNION ALL and place your "IF EXISTS" as a WHERE-clause to each sub query.
IF is control flow. WITH is within a query. You can do:
IF EXISTS (SELECT ColumnZ FROM AnotherTable WHERE Columnz=SomeNumbers)
BEGIN
WITH MYCTE_TABLE (Column1,Column2)AS
SELECT (ColumnA, Column B FROM SomeTable WHERE ColumnA=SomeValue)
MYSUBQUERY1
END;
ELSE
BEGIN
WITH MYCTE_TABLE (Column1,Column2)AS
SELECT (ColumnA, Column B FROM SomeTable WHERE ColumnA=SomeValue)
MYSUBQUERY2
END;
Or you could use a temporary table or table variable to store the values.

Oracle/PL SQL/SQL null comparison on where clause

Just a question about dealing will null values in a query.
For example I have the following table with the following fields and values
TABLEX
Column1
1
2
3
4
5
---------
Column2
null
A
B
C
null
I'm passing a variableY on a specific procedure. Inside the procedure is a cursor like this
CURSOR c_results IS
SELECT * FROM TABLEX where column2 = variableY
now the problem is variableY can be either null, A, B or C
if the variableY is null i want to select all record where column2 is null, else where column2 is either A, B or C.
I cannot do the above cursor/query because if variableY is null it won't work because the comparison should be
CURSOR c_results IS
SELECT * FROM TABLEX where column2 IS NULL
What cursor/query should I use that will accomodate either null or string variable.
Sorry if my question is a bit confusing. I'm not that good in explaining things. Thanks in advance.
Either produce different SQL depending on the contents of that parameter, or alter your SQL like this:
WHERE (column2 = variableY) OR (variableY IS NULL AND column2 IS NULL)
Oracle's Ask Tom says:
where decode( col1, col2, 1, 0 ) = 0 -- finds differences
or
where decode( col1, col2, 1, 0 ) = 1 -- finds sameness - even if both NULL
Safely Comparing NULL Columns as Equal
You could use something like:
SELECT * FROM TABLEX WHERE COALESCE(column2, '') = COALESCE(variableY, '')
(COALESCE takes the first non NULL value)
Note this will only work when you the column content cannot be '' (empty string). Else this statement will fail because NULL will match '' (empty string).
(edit)
You could also consider:
SELECT * FROM TABLEX WHERE COALESCE(column2, 'a string that never occurs') = COALESCE(variableY, 'a string that never occurs')
This will fix the '' fail hypothesis.
Below is similar to "top" answer but more concise:
WHERE ((column2 = variableY ) or COALESCE( column2, variableY) IS NULL)
May not be appropriate depending on the data you're looking at, but one trick I've seen (and used) is to compare NVL(fieldname,somenonexistentvalue).
For example, if AGE is an optional column, you could use:
if nvl(table1.AGE,-1) = nvl(table2.AGE,-1)
This relies on there being a value that you know will never be allowed. Age is a good example, salary, sequence numbers, and other numerics that can't be negative. Strings may be trickier of course - you may say that you'll never have anyone named 'xyzzymaryhadalittlelamb" or something like that, but the day you run with that assumption you KNOW they'll hire someone with that name!!
All that said: "where a = b or (a is null and b is null)" is the traditional way to solve it. Which is unfortunate, as even experienced programmers forget that part of it sometimes.
Try using the ISNULL() function. you can check if the variable is null and if so, set a default return value. camparing null to null is not really possible. remember: null <> null
WHERE variableY is null or column2 = variableY
for example:
create table t_abc (
id number(19) not null,
name varchar(20)
);
insert into t_abc(id, name) values (1, 'name');
insert into t_abc(id, name) values (2, null);
commit;
select * from t_abc where null is null or name = null;
--get all records
select * from t_abc where 'name' is null or name = 'name';
--get one record with name = 'name'
You could use DUMP:
SELECT *
FROM TABLEX
WHERE DUMP(column2) = DUMP(variableY);
DBFiddle Demo
Warning: This is not SARG-able expression so there will be no index usage.
With this approach you don't need to search for value that won't exists in your data (like NVL/COALESCE).

Firebird sql to insert a typical record from another table with only one different field

I work on Firebird 2.5
and I have two tables, all their columns are similar except one has a primary key with auto increment and a not null foreign key field (A) for master table
I know I can use sql like this to insert all values from the two tables
insert into table1 select * from table2 where somthing = 'foo'
but what about the field (A) is there any way to insert this value manually in the same sql statement ? as this is the only field need to be entered manually
Thanks
You can specify both the source and target fields explicitly (and you should; don't use select * unless you have a specific reason to):
insert into table1
(
col1,
col2,
col3,
col4
)
select
col1,
col2,
col3,
'foo'
from table2
where something = 'foo'
Came upon this post because I was looking for a solution to do the same, but without hard-coding the field names because the fields maybe added/removed and didn't want to have to remember to update the copy record procedure.
After googling around for awhile, I came up with this solution:
select cast(list(trim(RDB$FIELD_NAME)) as varchar(10000))
from RDB$RELATION_FIELDS
where RDB$RELATION_NAME = 'YOUR_TABLE'
and RDB$FIELD_NAME not in ('ID') -- include other fields to NOT copy
into :FIELD_NAMES;
NEW_ID = next value for YOUR_TABLE_ID_GENERATOR;
execute statement '
insert into YOUR_TABLE (ID,' || FIELD_NAMES || ')
select ' || cast(:NEW_ID as varchar(20)) || ',' ||
FIELD_NAMES || '
from YOUR_TABLE
where ID = ' || cast(:ID_OF_RECORD_TO_COPY as varchar(20));
Hope this saves some time for anyone else who comes across this issue!

Referring to column values directly without using variables in T-SQL

Is there a way in T-SQL (SQL Server 2005) to assign a whole record to a record variable and then refer to the particular values using column names?
I mean, instead of:
select #var1 = col1,
#var2 = col2
from mytable
where ID = 1;
and referring to them as #var1 and #var2, something like
#record =
select col1, col2
from mytable
where ID = 1;
and referring to them like #record.col1 and #record.col2 .
I am beginner in t-sql, so hopefully the question is not too trivial.
You can create a table variable and select the whole resultset into it:
DECLARE #tt TABLE (col1 INT, col2 INT)
INSERT
INTO #tt
SELECT col1, col2
FROM mytable
WHERE id = 1
, but you cannot access its data except than in the SELECT query as well.
With pure TSQL (that it without custom datatypes) the thing you ask is impossible.
sounds like you are a programmer ... look at linq maybe as it does what you want.
You can use a temporary table and SELECT...INTO to avoid specifying the column names at the beginning :
SELECT Field1, Field2
INTO #TempTable
FROM MyTable
WHERE MyTable.MyID = 1
but of course you'll still need the FROM #TempTable part when referring to the column names.
SELECT Field1, Field2
FROM #TempTable
and of course to remember to drop the table at the end :
DROP #TempTable
The app code is where you'd normally refer to a single row at a time as a variable.
You could use XML, but you'd have to play with this...
DECLARE #MyRecord xml
DECLARE #Mytable TABLE (col1 int NOT NULL, col2 varchar(30) NOT NULL)
INSERT #Mytable (col1, col2) VALUES (1, 'bob')
select #MyRecord =
(SELECT *
from #Mytable
where col1 = 1
FOR XML AUTO)
SELECT #myRecord.value('./#col', 'int') --also #myRecord.value('#col', 'int')
--gives error
Msg 2390, Level 16, State 1, Line 12
XQuery [value()]: Top-level attribute nodes are not supported
Buried in the Transact SQL documentation I came across this restriction on variables:
Variables can be used only in expressions, not in place of object names or keywords.
Since you'd need to use an object name to qualify a column I don't believe that this is allowed.