Is that possible to have an IF statement inside a SET? - sql

I have a table in Oracle with a column named ERROR_CODE (which is a VARCHAR2) initially set to NULL for every value. I would like to update it with an UPDATE statement like in the following statement:
UPDATE SNAPSHOT_TEST
SET ERROR_CODE =
IF(...) THEN NVL2(ERROR_CODE, CONCAT(ERROR_CODE, ',8'), '9')
ELIF(...) THEN NVL2(ERROR_CODE, CONCAT(ERROR_CODE, ',9'), '9')
ELIF(...) ...
END IF;
I tried to use a CASE statement to achieve this result and it works but it's not what I need, because multiple conditions can match at the same time and if that happens I need to concatenate the error code as in the statement I inserted before, having '8,9' in the end, for example.
is there a way to achieve this in Oracle?

You could use CASE expression:
UPDATE SNAPSHOT_TEST
SET ERROR_CODE =
CASE WHEN ... THEN NVL2(ERROR_CODE, CONCAT(ERROR_CODE, ',8'), '9')
WHEN ... THEN NVL2(ERROR_CODE, CONCAT(ERROR_CODE, ',9'), '9')
ELSE ...
END;
having '8,9' in the end
UPDATE SNAPSHOT_TEST
SET ERROR_CODE =
ERROR_CODE || CASE WHEN ... THEN '8' END || CASE WHEN ... THEN '9' END

As the other answerers said, we would need some minimal sample data and sample conditions to recommend a solution.
For instance, reading "... multiple conditions ... having 8,9 in the end ..." makes me think of LISTAGG, but I cannot formulate this as an answer because I have not the slightest clue about the conditions.

multiple conditions can match at the same time and if that happens I need to concatenate the error code
So, concatenate the error codes, with a case expression deciding if each is needed:
update snapshot_test
set error_code =
ltrim(
case when (...) then ',8' end
|| case when (...) then ',9' end
|| case when (...) then ',10' end
...
, ',');
As they all add a comma, the ltrim removes the extra one created by the first match.
db<>fiddle demo with completely arbitrary and artificial conditions, since we have nothing real to work with.
(Rather belatedly, I realise this is pretty much what #LukaszSzozda's edit is doing; except this includes the commas.)

I managed to find a solution on my own: even if Lukasz's question, after his edit, may actually work, it is more performing in this case to have separate UPDATE in cascade as in the following snippet:
UPDATE SNAPSHOT_TEST
SET ERROR_CODE = NVL2(ERROR_CODE, CONCAT(ERROR_CODE, ',8'), '8')
WHERE (...);
UPDATE SNAPSHOT_TEST
SET ERROR_CODE = NVL2(ERROR_CODE, CONCAT(ERROR_CODE, ',9'), '9')
WHERE (...);
In this way, if the ERROR_CODE is NULL it will be set for the first time with the first value matched, otherwise another error code will be added with a comma.

Related

Update Column: Case With Concat

I am have a blank column, it is called column_blank. I want to concat two other columns (concat1, concat2) and based on the concat value, add a value into column_blank. I am extremely green and appreciate your help.
This is what I am thinking. I am not too confident about the DECLARE statement
DECLARE DIRECTION CONCAT := (CONCAT1, CONCAT2);
INSERT INTO
MYDATA_EXTRACT_DATA.COLUMN_BLANK
SELECT
CASE COLUMN_BLANK
WHEN (DIRECTION) IS '123456', THEN ('654321'),
WHEN (DIRECTION) IS '789101', THEN ('654321'),
ELSE '000'
END CASE
FROM MYDATA_EXTRACT_DATA
That's just an ordinary update statement:
update mydata_extract_data set
column_blank = case when concat1 || concat2 in ('123456', '789101') then '654321'
else '000'
end;

Problem with CASE statement syntax in teradata (BTEQ)

I've the following code with regards to inserting and checking values into the teradata database.My point is that data that is read from the flat file whose value after trimming leading 0 is 0 or NULL should not be loaded and if otherwise, the value should be loaded into the target table.......
VALUES
(
CASE STUD_ID WHEN TRIM(LEADING '0' FROM :STUD_ID) NOT IN ('0', NULL) THEN TRIM(LEADING '0' FROM :STUD_ID)
ELSE NEXT
END,
:B,
:C
)
I've unsure of if the next statement does exist for teradata in the conditioning statement...I've got this error
Illegal expression in WHEN clause of CASE expression.
Statement# 1, Info =0
I tried with the select statement in the VALUES area,
VALUES
(
SELECT (CASE STUD_ID WHEN TRIM(LEADING '0' FROM :STUD_ID) != '0' THEN TRIM(LEADING '0' FROM :STUD_ID)
ELSE 1000
END )
FROM :STUD_ID,
:B,
:C
)
I got this error statement...
Syntax error, expected something like ')' between '(' and
the 'SELECT' keyword.
The CASE ... WHEN syntax expects a single value for comparison (i.e. CASE some_expression WHEN 1 THEN 'Y'). Try the CASE WHEN ... form instead:
CASE
WHEN TRIM(LEADING '0' FROM :STUD_ID) NOT IN ('0', NULL)
THEN TRIM(LEADING '0' FROM :STUD_ID)
ELSE NEXT
END
You can also do this:
COALESCE(NULLIF(TRIM(LEADING '0' FROM :STUD_ID),0),NEXT) -- Return "NEXT" if 0 or NULL
There are several issues, ravioli fixed the syntax.
But your logic is flawed, too: TRIM(LEADING '0' FROM :STUD_ID) NOT IN ('0', NULL) will never be true because after trimming zeroes you never get '0' and additionally any cpmparison to NULL yields unknown. CASE WHEN TRIM(LEADING '0' FROM :STUD_ID) = '' OR :STUD_ID IS NULL THEN fixes this.
But based on your previous question you want to skip this row and this is not possible in BTEQ. Either switch to a load utility/TPT (preferred if it's a larger number of rows) or load the data as is in a staging table and apply the filter when INSERT/SELECTing into the target.

Oracle ORA-12704 with case expression on CLOB

I have an Oracle update statement with a case that is trying to either set or ignore a value (leave original value the same ).
With this code, either of the two individual sets compile on their own ( the two commented out ones ).
But when combined in a case statement, I get "PL/SQL: ORA-12704: character set mismatch"
The column is a CLOB column.
Alternatively is there a way to conditionally not do a set in an update statement?
LONGDESCRIPT = ( CASE v_CanUpdateAssetDescription WHEN 1 THEN p_description ELSE LONGDESCRIPT END ),
-- LONGDESCRIPT = LONGDESCRIPT,
--LONGDESCRIPT = p_description,
Apparently the character set of p_description is not the same as that of LONGDESCRIPT. In a simple assignment Oracle can work with this, but in a CASE expression all values returned from the different paths through the CASE expression must be of exactly the same type. As it appears you're doing this in PL/SQL you might try doing something like the following:
DECLARE
cCLOB_var YOUR_TABLE.LONGDESCRIPT%TYPE;
BEGIN
cCLOB_var := p_description;
UPDATE YOUR_TABLE
SET LONGDESCRIPT = CASE v_CanUpdateAssetDescription
WHEN 1 THEN cCLOB_var
ELSE LONGDESCRIPT
END
...etc...
END;
You might also try using a CAST:
UPDATE YOUR_TABLE
SET LONGDESCRIPT = CASE v_CanUpdateAssetDescription
WHEN 1 THEN CAST(p_description AS YOUR_TABLE.LONGDESCRIPT%TYPE)
ELSE LONGDESCRIPT
END
Not sure if the latter will work or not, but it might be worth a shot.
Oddly I found that to_clob did the trick.
And even more oddly, I only needed TO_CLOB for the LONGDESCRIPT value.
LONGDESCRIPT = ( CASE v_CanUpdateAssetDescription WHEN 1 THEN p_description ELSE to_clob(LONGDESCRIPT) END ),

SQL: How to make a replace on the field ''

I have a very but tricky question for you guys. So, listen I have a field with spaces and numbers in one of my table columns. The key part is transform the content in a decimal field. The drawback is basically that for some rows I could get something like:
' 1584.00 '
' 156546'
'545.00 '
' '
So, to clean up my column, I have done a LTRIM and RTRIM so spaces gone. So now for a couple of records where the record were just spaces the new content is ''. Finally I need to convert this result to a decimal.
Issue: The thing is that for field that contend just the spaces the new result is '' and I'm not able to apply a REPLACE on this because it's a blank and the code below doesn't work:
SELECT REPLACE('','','0')
-- Final current verison
SELECT CAST(COALESCE(REPLACE(REPLACE([Gross_Weight],' ','0'),',',''),'0') AS DECIMAL(13,3))
How could I figure it out?
thanks so much
SELECT COALESCE(NULLIF(MyColumn, ''), 0)
This has the side-effect that you will also turn NULL values into 0, which you might not want. If that's a problem then a simple CASE statement should do the trick:
SELECT CASE WHEN MyColumn = '' THEN 0 ELSE CAST(MyColumn AS DECIMAL(10, 4)) END
Obviously you'll also have to incorporate any other manipulations that you're already doing.
No need for replace, just concatenate a zero to your column, like
SELECT RTRIM('0' + LTRIM(column))
I presume your data is in a table.
Lets call this table 'DATA' and the column 'VALUE'
Then you might use the below query
UPDATE DATA SET VALUE = 0 where VALUE = ''
To select the value do the below
select case ltrim(rtrim([Gross_Weight])) when ''
THEN 0
ELSE ltrim(rtrim([Gross_Weight])) END
Let me know if i get the requirement wrong.

Procedure to apply formatting to all rows in a table

I had a SQL procedure that increments through each row and and pads some trailing zeros on values depending on the length of the value after a decimal point. Trying to carry this over to a PSQL environment I realized there was a lot of syntax differences between SQL and PSQL. I managed to make the conversion over time but I am still getting a syntax error and cant figure out why. Can someone help me figure out why this wont run? I am currently running it in PGadmin if that makes any difference.
DO $$
DECLARE
counter integer;
before decimal;
after decimal;
BEGIN
counter := 1;
WHILE counter <> 2 LOOP
before = (select code from table where ID = counter);
after = (SELECT SUBSTRING(code, CHARINDEX('.', code) + 1, LEN(code)) as Afterward from table where ID = counter);
IF before = after
THEN
update table set code = before + '.0000' where ID = counter;
ELSE
IF length(after) = 1 THEN
update table set code = before + '000' where ID = counter;
ELSE IF length(after) = 2 THEN
update table set code = before + '00' where ID = counter;
ELSE IF length(after) = 3 THEN
update table set code = before + '0' where ID = counter;
ELSE
select before;
END IF;
END IF;
counter := counter + 1;
END LOOP
END $$;
Some examples of the input/output of the intended result:
Input 55.5 > Output 55.5000
Input 55 > Output 55.0000
Thanks for your help,
Justin
There is no need for a function or even an update on the table to format values when displaying them.
Assuming the values are in fact numbers stored in a decimal or float column, all you need to do is to apply the to_char() function when retrieving them:
select to_char(code, 'FM999999990.0000')
from data;
This will output 55.5000 or 55.0000
The drawback of the to_char() function is that you need to anticipate the maximum number of digits of that can occur. If you have not enough 9 in the format mask, the output will be something like #.###. But as too many digits in the format mask don't hurt, I usually throw a lot into the format mask.
For more information on formatting functions, please see the manual: https://www.postgresql.org/docs/current/static/functions-formatting.html#FUNCTIONS-FORMATTING-NUMERIC-TABLE
If you insist on storing formatted data, you can use to_char() to update the table:
update the_table
set code = to_char(code::numeric, 'FM999999990.0000');
Casting the value to a number will of course fail if there a non-numeric values in the column.
But again: I strong recommend to store numbers as numbers, not as strings.
If you want to compare this to a user input, it's better to convert the user input to a proper number and compare that to the (number) values stored in the database.
The string matching that you are after doesn't actually require a function either. Using substring() with a regex will do that:
update the_table
set code = code || case length(coalesce(substring(code from '\.[0-9]*$'), ''))
when 4 then '0'
when 3 then '00'
when 2 then '000'
when 1 then '0000'
when 0 then '.0000'
else ''
end
where length(coalesce(substring(code from '\.[0-9]*$'), '')) < 5;
substring(code from '\.[0-9]*$') extracts everything the . followed by numbers that is at the end of the string. So for 55.0 it returns .0 for 55.50 it returns .50 if there is no . in the value, then it returns null that's why the coalesce is needed.
The length of that substring tells us how many digits are present. Depending on that we can then append the necessary number of zeros. The case can be shortened so that not all possible length have to be listed (but it's not simpler):
update the_table
set code = code || case length(coalesce(substring(code from '\.[0-9]*$'), ''))
when 0 then '.0000'
else lpad('0', 5- length(coalesce(substring(code from '\.[0-9]*$'), '')), '0')
end
where length(coalesce(substring(code from '\.[0-9]*$'), '')) < 5;
Another option is to use the position of the . inside the string to calculate the number of 0 that need to be added:
update the_table
set code =
code || case
when strpos(code, '.') = 0 then '0000'
else rpad('0', 4 - (length(code) - strpos(code, '.')), '0')
end
where length(code) - strpos(code, '.') < 4;
Regular expressions are quite expensive not using them will make this faster. The above will however only work if there is always at most one . in the value.
But if you can be sure that every value can be cast to a number, the to_char() method with a cast is definitely the most robust one.
To only process rows where the code columns contains correct numbers, you can use a where clause in the SQL statement:
where code ~ '^[0-9]+(\.[0-9][0-9]?)?$'
To change the column type to numeric:
alter table t alter column code type numeric