Oracle correlated UPDATE - sql

I'm having difficulty with Oracle 10g syntax for a correlated UPDATE. I am processing this code in a PL/SQL procedure.
I would approach the problem in SQL Server as so:
UPDATE table_a a
SET a.prov_id=pn.prov_id,
a.step=1
from (
SELECT p.prov_id
FROM note n
INNER JOIN provider p ON n.author_id=p.user_id
where n.enc_id=a.enc_id
AND TRUNC(n.note_time)=a.attr_date
) pn
The equivalent Oracle syntax is:
UPDATE table_a a
SET a.prov_id=(
SELECT p.prov_id
FROM note n
INNER JOIN provider p ON n.author_id=p.user_id
where n.enc_id=a.enc_id
AND TRUNC(n.note_time)=a.attr_date
),
a.step=1
WHERE EXISTS (
SELECT *
FROM note n
INNER JOIN provider p ON n.author_id=p.user_id
where n.enc_id=a.enc_id
AND TRUNC(n.note_time)=a.attr_date
)
Does this in fact run the sub-query twice? Is there a more-abbreviated syntax than this?

As to whether the sub-query runs twice, use EXPLAIN PLAN.
I like to use the merge command instead of update for these correlated updates, something like: (Not tested, if you want a tested answer, please provide DDL and insert statements.)
merge into table_a TRGT
using (select P.prov_id, N.enc_id, trunc(n.note_time) as trunc_note_time
from note N
inner join provider P ON N.author_id=P.user_id) SRC
on (TRGT.enc_id = SRC.enc_id and TRGT.attr_date = SRC.trunc_note_time)
when matched then update set prov_id = SRC.prov_id
, step = 1
Sometimes you can update an inline view, something like:
update (select A.prov_id, A.step, P.prov_id as p_prov_id
from note N
inner join provider P on N.author_id=p.user_id
inner join table_a A
on N.enc_id=A.enc_id
and trunc(N.note_time)=A.attr_date)
set prov_id = p_prov_id
, step = 1
The inline view version won't always work. Can't find infor on the error, but essentially the inline view needs to have a unique key that Oracle can tie back to the tables in question.

Related

Is this questionable SQL Update syntax with aliased table correct?

I saw this SQL Update statement in a trigger and am unsure if the update works accurately - based on looking at where the table alias is and the update table syntax.
The syntax doesn't give any error on execution, and updates the record correctly when executing on random samples on my test DB.
However, on a larger PROD DB with more records, is there a possibility that the update fails or skips altogether? There were reports that random records did not have the SAMPLE.ISCOMPOSITESAMPLE field set.
Questionable syntax
UPDATE SAMPLE SET
SAMPLE.SAMPLETYPE = (SELECT DESCRIPTION FROM SAMPLETYPE WHERE SAMPLETYPENO = C.SAMPLETYPENO),
SAMPLE.ISCOMPOSITESAMPLE = (SELECT COMPOSITESAMPLE FROM SAMPLETYPE WHERE SAMPLETYPENO = C.SAMPLETYPENO)
FROM SAMPLE C
INNER JOIN INSERTED T ON C.SAMPLENO = T.SAMPLENO
Syntax I am familiar with (similar to above but intentionally not optimised for comparison)
UPDATE C SET
SAMPLETYPE = (SELECT DESCRIPTION FROM SAMPLETYPE WHERE SAMPLETYPENO = C.SAMPLETYPENO),
ISCOMPOSITESAMPLE = (SELECT COMPOSITESAMPLE FROM SAMPLETYPE WHERE SAMPLETYPENO = C.SAMPLETYPENO)
FROM SAMPLE C
INNER JOIN INSERTED T ON C.SAMPLENO = T.SAMPLENO
The query is correct and will work. However, there are two things that I would fix:
TheUPDATE SAMPLE does update the table whose alias is C. This is documented as correct and something that really irks me, because aliases should be respected. You should use the alias for the update.
The correlated subqueries are not using fully qualified column names.
So, using correlated subqueries, I would recommend:
UPDATE S
SET SAMPLETYPE = (SELECT ST.DESCRIPTION FROM SAMPLETYPE ST WHERE ST.SAMPLETYPENO = S.SAMPLETYPENO),
ISCOMPOSITESAMPLE = (SELECT ST.COMPOSITESAMPLE FROM SAMPLETYPE ST WHERE ST.SAMPLETYPENO = S.SAMPLETYPENO)
FROM SAMPLE S INNER JOIN
INSERTED I
ON S.SAMPLENO = I.SAMPLENO ;
You can also write this -- probably more efficiently -- using a LEFT JOIN:
UPDATE S
SET SAMPLETYPE = ST.DESCRIPTION,
ISCOMPOSITESAMPLE = ST.COMPOSITESAMPLE
FROM SAMPLE S INNER JOIN
INSERTED I
ON S.SAMPLENO = I.SAMPLENO LEFT JOIN
SAMPLETYPE ST
ON ST.SAMPLETYPENO = S.SAMPLETYPENO;

SQL Fieldname vs Aliasname

I have been trying to figure out why the following SQL works
SELECT c_Supplier.Supplier_ID AS A_ID, c_Supplier.Name, c_Supplier.RFC, c_Supplier_Direccion.Description, c_Supplier_Direccion.Address, c_Supplier_Phone.Phone
FROM c_Supplier LEFT JOIN (c_Supplier_Direccion LEFT JOIN c_Supplier_Phone ON c_Supplier_Direccion.Supplier_Direccion_ID = c_Supplier_Phone.Supplier_Direccion_ID) ON c_Supplier.Supplier_ID = c_Supplier_Direccion.Supplier_ID
WHERE (c_Supplier.Supplier_ID=1);
But when I try to use the aliasname (A_ID) in the WHERE clause, I got an error
SELECT c_Supplier.Supplier_ID AS A_ID, c_Supplier.Name, c_Supplier.RFC, c_Supplier_Direccion.Description, c_Supplier_Direccion.Address, c_Supplier_Phone.Phone
FROM c_Supplier LEFT JOIN (c_Supplier_Direccion LEFT JOIN c_Supplier_Phone ON c_Supplier_Direccion.Supplier_Direccion_ID = c_Supplier_Phone.Supplier_Direccion_ID) ON c_Supplier.Supplier_ID = c_Supplier_Direccion.Supplier_ID
WHERE (A_ID=1);
Any ideas?
I don't understand your question. This is a reasonably formed SQL query:
SELECT c_Supplier.Supplier_ID AS Entidad_ID, c_Supplier.Name,
c_Supplier.RFC, c_Supplier_Direccion.Description,
c_Supplier_Direccion.Address, c_Supplier_Phone.Phone
FROM c_Supplier LEFT JOIN
(c_Supplier_Direccion LEFT JOIN
c_Supplier_Phone
ON c_Supplier_Direccion.Supplier_Direccion_ID = c_Supplier_Phone.Supplier_Direccion_ID
) ON c_Supplier.Supplier_ID = c_Supplier_Direccion.Supplier_ID
WHERE (c_Supplier.Supplier_ID = 1);
(I would recommend table aliases for readability, but that is a separate issue.)
It has no alias called A_ID anywhere in the query, so there is no reason to ever expect a reference to A_ID to work (unless it is a column in one of the tables).
And, SQL doesn't allow the re-use of table aliases in the SELECT where they are defined or the WHERE clause. This is not an MS Access limitation; it is how the SQL language is defined.
If you want to do so in MS Access, you can use a subquery and reference the table alias in the outer query.

Update using Inner Join throwing syntax error

I would like to update columns in Table A based on values in Table B. Using below format, but getting syntax error.
update TableA
set
TableA.MOdule_id =TableB.MOdule_id
from TableA
inner join
TableB
on TableA.end_Slot_id =TableB.Slot_Id
where TableA.Slot_Id = 'AAA';
It would be great help, if anyone can help on this.
A quick search for "informix sql update" returns two reference manual pages that describe the syntax for the UPDATE command. Neither one indicates support for the nonstandard FROM clause.
Standard SQL uses a correlated subquery for the purpose. Your query should look something like
update TableA
set MOdule_id =
(select TableB.MOdule_id
from TableB
on TableA.end_Slot_id = Slot_Id)
where Slot_Id = 'AAA'
and exists (
select 1
from TableB
on TableA.end_Slot_id = Slot_Id
and TableA.Slot_Id = 'AAA'
);
The EXISTS clause ensures that only rows that exist in B are applied to A. Without it, any missing rows would be updated to NULL.

ORA-904 When referencing the base table, in a query that includes a nested table and other joins

I've distilled down a problem query to the following example, and I can't find anything on metalink indicating that this is a known bug, or expected behaviour.
The following script is self contained so anyone could reproduce this, I'm using Oracle 11.2.0.3.0 enterprise 64-bit running on RHEL, queries being submitted via SQL Developer, the same results occur in SQL Plus.
First : create the nested table type.
create type nested_type_test as table of varchar(32);
Second : create a table using the type as the nested type.
create table nested_table_test (
uuid varchar(32)
, some_values nested_type_test
)
NESTED TABLE some_values STORE AS nested_some_values;
Third : create an arbitary table that we will use to create a join statement with.
create table join_table (
uuid varchar(32)
);
Now for some simple SQL statements:
select *
from nested_table_test ntt, table(some_Values) sv;
select *
from nested_table_test ntt, table(some_Values) sv
where ntt.uuid = 'X';
Both work, I havn't inserted data but for the purposes of this bug / test we do not need to since the issue is a parse issue. Next statement, I add the join to the arbitary table I generated for this purposes.
select *
from nested_table_test ntt, table(some_Values) sv
inner join join_table jt on jt.uuid = ntt.uuid;
This now produces : ORA-00904: "NTT"."UUID": invalid identifier - yet I know that the field exists and could reference it in a where clause. Taking out the TABLE(some_values) clause like this,
select *
from nested_table_test ntt
inner join join_table jt on jt.uuid = ntt.uuid;
and the query works, so I know it is isolated to the existance of the nested table clause within the statement.
If I switch to using a manual join instead of an ANSI join, it then parses and executes again.
select *
from nested_table_test ntt, table(some_Values) sv, join_table jt
where jt.uuid = ntt.uuid;
Alternatively, and even more hack-ish:
select *
from nested_table_test ntt, table(some_Values) sv
inner join join_table jt on 1=1
where jt.uuid = ntt.uuid;
Also parses - so it is not the ntt.uuid itself that is the problem, but where it occurs within the statement that the parser seems to struggle with.
Known bug, Unknown bug or expected behaviour?
Edit : Using CROSS JOIN table(some_values) sv causes a seg fault and a trace file on the server whilst dumping the connection - that one for sure is a bug. It's also why I'm not in pure ANSI join syntax.
When you are mixing up oracle and ANSI join syntax it's the expected behavior. ANSI join takes precedence over , comma join(cross join), parsed from left to right, so it means that the ntt has not been joined yet when you were trying to reference ntt.uuid column and that is why you might get that error. To that end choose one of the join types. For instance:
select *
from nested_table_test ntt
, table(some_Values) sv
, join_table jt
where jt.uuid = ntt.uuid;
OR
select *
from nested_table_test ntt
cross join table(some_Values) sv
join join_table jt
on (jt.uuid = ntt.uuid)

SQL Update with multiple INNER JOIN

I have two tables,
SELECT [SHADOW_ID]
,[DATA]
,[TSN]
,[HEALTH_PLAN_CATEGORY_VALUE_ID]
FROM [stbl834]
and
SELECT [HEALTH_PLAN_CATEGORY_VALUE_ID]
,[TSN]
FROM [uvwCLIENT_HEALTH_PLAN]
Right now HEALTH_PLAN_CATEGORY_VALUE_ID are all set to NULL in stbl834, I need to fetch these values from uvwCLIENT_HEALTH_PLAN based on different TSN values from stbl834. Is there a way to do this using JOIN statements? I need to avoid any sort of loops.
First run a select
SELECT *
FROM [stbl834] A
INNER JOIN [uvwCLIENT_HEALTH_PLAN] B ON A.TSN = B.TSN
and verify that you have correct number of rows and that the values in the columns match. this would ensure that you have a correct join key. If this looks correct use the below update
UPDATE [stbl834]
SET [HEALTH_PLAN_CATEGORY_VALUE_ID] = B.[HEALTH_PLAN_CATEGORY_VALUE_ID]
FROM [stbl834] A
INNER JOIN [uvwCLIENT_HEALTH_PLAN] B ON A.TSN = B.TSN
Select HEALTH_PLAN_CATEGORY_VALUE_ID, TSN
from stbl834 left join uvwCLIENT_HEALTH_PLAN
on stbl834.TSN=uvwCLIENT_HEALTH_PLAN.TSN
Do you need to insert them into stbl834? If so --
update stbl834
set HEALTH_PLAN_CATEGORY_VALUE_ID = uvwCLIENT_HEALTH_PLAN.HEALTH_PLAN_CATEGORY_VALUE_ID
from stbl834 left join uvwCLIENT_HEALTH_PLAN
on stbl834.TSN=uvwCLIENT_HEALTH_PLAN.TSN
Alternatively, you can do this for any RDBMS that does not support the UPDATE..FROM syntax:
UPDATE stbl834
SET health_plan_category_value_id = (SELECT health_plan_category_value_id
FROM uvwclient_health_plan
WHERE uvwclient_health_plan.tns = stbl834.tns)
This solution is SQL Ansi compatible, meaning it will work for any RDBMS. Please make sure the sub-query (SELECT) will only return record value for a given TNS, or else you will have to ensure that by using TOP or LIMIT (whatever is supported by your RDBMS).
You can try out this too, this might serve your purpose.
update stbl834
set stbl834.HEALTH_PLAN_CATEGORY_VALUE_ID= uvwCLIENT_HEALTH_PLAN.HEALTH_PLAN_CATEGORY_VALUE_ID
inner join uvwCLIENT_HEALTH_PLAN.TSN=HEALTH_PLAN_CATEGORY_VALUE_ID.TSN