Oracle ORA-01427: single-row subquery returns more than one row, but actually no rows - sql

I can already hear the groans looking at my title but please bear with me a moment. :)
I have two tables that have a few columns in common and are updated through different means. Given a specific identifier I want to update the first table with values from the second table if the first table is missing some information.
Table A looks something like:
Dept_ID Reviewer Reviewer_Team Reviewer_Code
ACM Null Null Null
EOT Null Null Null
QQQ Joe Joe's Group XYZ
ACM Null Null Null
ZZZ Null Null Null
Table B looks something like:
Dept_ID Reviewer Reviewer_Team Reviewer_Code
AAA Al Al's Group 123
BBB Bob Bob's Group 234
ZZZ Zoe Zoe's Group 567
If Reviewer_Code is Null in Table A we want to find Table A's Dept_ID in Table B, and update Table A's other fields to match Table B. Note that Table A might have multiple records with the same Dept_ID in which case we'd expect them to have the same values updated from Table B.
Sounds easy. Using the above tables as an example there are no matches in Table B, so the ACM and EOT records would not be updated at this step. Table A's ZZZ record though would get updated based on Table B's ZZZ record.
However there's a chance that there would be no matches in Table B. So pretend Table A doesn't have the ZZZ record, just the ACM and EOT that have Nulls.
I'm new to Oracle (coming from SQL Server) so maybe I'm testing this wrong, but what I have is a bunch of queries one after another in a .sql window of Oracle SQL Developer. This seems to work for me just fine normally. When it gets to this query though I get the dreaded "single-row subquery" error.
Here's the query I've tried a few different ways:
UPDATE VchrImpDetailCombined vchr
SET (Reviewer, Reviewer_Team, Reviewer_Code) =
(SELECT DISTINCT b.Reviewer, b.Reviewer_Team, b.Reviewer_Code
FROM GlobPMSDeptIdMapping b
WHERE b.Dept_Id = vchr.Dept_Id)
WHERE vchr.Reviewer_Code IS NULL
AND vchr.Business_L1 = 'CF'
AND vchr.Dept_ID IS NOT NULL;
or
UPDATE VchrImpDetailCombined vchr
SET (Reviewer, Reviewer_Team, Reviewer_Code) =
(SELECT DISTINCT b.Reviewer, b.Reviewer_Team, b.Reviewer_Code
FROM GlobPMSDeptIdMapping b
inner join VchrImpDetailCombined a
on b.Dept_Id = a.Dept_Id
WHERE b.Dept_Id = vchr.Dept_Id)
WHERE vchr.Reviewer_Code IS NULL
AND vchr.Business_L1 = 'CF'
AND vchr.Dept_ID IS NOT NULL;
I've tried a few other things as well such as doing "WHERE EXISTS SELECT blahblah", or "WHERE b.Dept_ID IS NOT NULL", etc.
Now, given my example data above, the subquery should have 0 records, keeping in mind there actually isn't a ZZZ record in Table A like my example, just the ACM and EOT. Table B simply doesn't have records with the matching Dept_ID in Table A. So my expectation would be for a 0 record update and happily moving along to the next query.
When I run these queries in a string of other queries I get the error. If I run the query all by its lonesome I simply get a "3 rows updated" which seems odd that anything is updating considering there should be no matches. But the 3 rows updated would seem to match the 3 ACM and EOT records even though Table B has nothing to update from given the criteria.
I must be missing something obvious, but I just can't seem to grasp it. There's a bajillion of these ORA-01427 questions so I was so sure I could find the answer already out there, but couldn't seem to find it.
Any ideas?

You need to instruct Oracle that it should perform the update only when there are data with which to do so (and I would have expected such to be needed for SQL Server as well, but I'm uncertain). This will overcome that obstacle, at the expense of performing an additional subquery:
UPDATE VchrImpDetailCombined vchr
SET (Reviewer, Reviewer_Team, Reviewer_Code) = (
SELECT b.Reviewer, b.Reviewer_Team, b.Reviewer_Code
FROM GlobPMSDeptIdMapping b
WHERE b.Dept_Id = vchr.Dept_Id
)
WHERE vchr.Reviewer_Code IS NULL
AND vchr.Business_L1 = 'CF'
AND vchr.Dept_ID IN (
SELECT Dept_Id
FROM GlobPMSDeptIdMapping
);
Per my comment on the question, I removed the DISTINCT from the (original) subquery, as it's either unneeded or ineffective.

Related

Is it optimal to use multiple joins in update query?

My update query checks whether the column “Houses” is null in any of the rows in my source table by joining the id between the target & source table (check query one). The column Houses being null in this case indicates that the row has expired; thus, I need to expire the row id in my target table and set the expired date. The query works fine, but I was wondering if it can be improved; I'm new to SQL, so I don't know if using two joins is the best way to accomplish the result I want. My update query will later be used against millions of rows. No columns has been indexed yet.
Query:
(Query one)
Update t
set valid_date = GETDATE()
From Target T
JOIN SOURCE S ON S.ID = T.ID
LEFT JOIN SOURCE S2 ON S2.Houses = t.Houses
WHERE S2.Houses is null
Target:
ID
namn
middlename
Houses
date
1
demo
hello
2
null
2
demo2
test
4
null
3
demo3
test1
5
null
Source:
ID
namn
middlename
Houses
1
demo
hello
null
3
demo
world
null
Expected output after running update query :
ID
namn
middlename
Houses
date
1
demo
hello
2
2022-12-06
2
demo2
test
4
null
3
demo3
test1
5
2022-12-06
I would recommend exists:
update t
set valid_date = getdate()
from target t
where exists (select 1 from source s where s.id = t.id and s.houses is null)
Note that your original query does not exactly do what you want. It cannot distinguish source rows that do not exist from source rows that exist and whose houses column is null. In your example, it would update row 2, which is not what you seem to want. You would need an INNER JOIN instead of the LEFT JOIN.
With EXISTS, you want an index on source(id, houses) so the subquery can execute efficiently against target rows. This index is probably worthwhile for the the JOIN as well.
I don't see why you'd need to join on the column houses at all.
Find all rows in source that have value NULL in the column houses.
Then update all rows in target that have the IDs of the source rows.
I prefer to write these kind of complex updates using CTEs. It looks more readable to me.
WITH
CTE
AS
(
SELECT
Target.ID
,Target.Date
FROM
Source
INNER JOIN Target ON Target.ID = Source.ID
WHERE Source.Houses IS NULL
)
UPDATE CTE
SET Date = GETDATE();
To efficiently find rows in source that have value NULL in the column houses you should create an index, something like this:
CREATE INDEX IX_Houses ON Source
(
Houses
);
I assume that ID is a primary key with a clustered unique index, so ID would be included in the IX_Houses index implicitly.

Get name for different status with same ID in SQL

I have a dataset with ID_1 and ID_2 who are individuals and a different STATUS for same ID_1 and ID_2 like this:
And I want to assign the NAME with the same ID_1 and ID_2 for the second STATUS, like this:
SELECT * FROM example WHERE (status = 1 and status = 2)
If we perform a self join (Substitute your tableName in where it says "TableName" below; we join the table to itself. Each is it's own data sets and since we sometimes want the name from the "related" records; a left join is needed. Together based on ID_1, and ID_2; but only when the the status on the second table is 1 greater than the first table so all status 2 join with status 1 for the same ID_1 and ID_2. When the status is 1 then no join occurs.. We then use coalesce to return the the desired name (one from 'B' table if such a join occurred, otherwise the base name from table aliased 'A'.
SELECT A.Index, A.ID_1, A.ID_2, A.Name, coalesce(B.Name, A.Name) as Name_P
FROM TableName A
LEFT JOIN tableName B
on A.ID_1=B.ID_1
and A.ID_2=B.ID_2
and A.Status-1 = B.Status --This is changed from comment having give it more thought...
In theory (as this is untested without complete DDL and test data. This could be provided on one of my dbfiddle sites https://dbfiddle.uk/ for example.)
Index 1-7 would always return Robert for coalesce(B.Name, A.Name) [using A.Name value] because the join would find no status 0 records with ID_1 and ID_2 matching. So B.Name would be Null, and A.Name (Robert) would be returned; thus is the nature of coalesce() in this example.
For index 8-9, B.Name would have a value of Robert so it would be returned. Coalesce() returns the first non-null value in the series coalesce(value1, value2,value3, value...)
But I'm assuming several things here.
status is always separated by 1
status is numeric
there are no status 0
if status 3 exists, you'd want it related to status 2 names.
several other things which you may not have provided sample data for
several other edge cases which may result in an improper result.
We need to understand your data and situation better in order to know how to help you.
For example if index 10 "Fred" Exists with ID_1 111 and ID_2 of 1 and status 3... what should the Name_P be? Justin? Robert? neither? Both?
Now I don't' know if you're doing this to return data in a set, or if you need the Name_P as some sort of "Extra" so for now I'm returning as part of the data set. If you want more: clarify the question by providing sample data expected results and WHY those results. Where it gets challenging is defining limits on the data could you have status 3 and what happens then? or are we just working with status 1 & 2... things we need to understand
Consider the complete DDL & Fiddle with sample data to help us help you so we can test our "attempts" against data you have where you know what the expected results are. The image is ok; but DDL and a minimally complete verifiable example is better.

Do I have to make sure my record only match with one row when using JOIN with DELETE statement?

I have a query like this:
DELETE A
FROM A
LEFT JOIN B ON
A.ID=B.ID
WHERE B.Status='OK'
The records from these join might resulted in more than one rows, for example:
Table A
ID
1
2
Table B
ID Status
1 OK
1 OK
2 OK
Do I have to make sure my record only match with one row? Because in these example, ID 1 will have 2 rows.
Apologize for bad english.
In SQL Server you don't have to ensure a 1 row result per row to delete. The engine will delete all rows from the deleting table that matches your joining or where conditions, even if they are being selected for more than 1 row.
The important part is which table are your deleting, make sure not to delete the wrong one!

SQL - include results you are looking for in a column and set all other values to null

I have two tables, one with orders and another with order comments. I want to join these two tables. They are joined on a column "EID" which exists in both tables. I want all orders. I also want to see all comments with only certain criteria AND all other comments should be set to null. How do I go about this?
Orders Table
Order_Number
1
2
3
4
Comments Table
Comments
Cancelled On
Ordered On
Cancelled On
Cancelled On
In this example I would like to see for my results:
Order_Number | Comments
1 | Cancelled On
2 | Null
3 | Cancelled On
4 | Cancelled On
Thanks!
This seems like a rather trivial left join.
select o.order_number, c.comments
from orders o
left join comments c
on o.eid = c.eid
and (here goes your criteria for comments)
Tested on Oracle, there might be subtle syntax differences for other DB engines.
It depends on one condition:
Are you trying to SET the other comments to null? (replace the values in the table)
or
Are you trying to DISPLAY the other comments as null? (dont display them)
If you want to change the values in the table use
UPDATE `table` SET `column` = null WHERE condition;
otherwise use:
SELECT column FROM table JOIN othertable WHERE condition;

in sql how to return single row of data from more than one row in the same table

I have a single table of activities, some labelled 'Assessment' (type_id of 50) and some 'Counselling' (type_id of 9) with dates of the activities. I need to compare these dates to find how long people wait for counselling after assessment. The table contains rows for many people, and that is the primary key of 'id'. My problem is how to produce a result row with both the assessment details and the counselling details for the same person, so that I can compare the dates. I've tried joining the table to itself, and tried nested subqueries, I just can't fathom it. I'm using Access 2010 btw.
Please forgive my stupidity, but here's an example of joining the table to itself that doesn't work, producing nothing (not surprising):
Table looks like:
ID TYPE_ID ACTIVITY_DATE_TIME
----------------------------------
1 9 20130411
1 v 50 v 20130511
2 9 20130511
3 9 20130511
In the above the last two rows have only had assessment so I want to ignore them, and just work on the situation where there's both assessment and counselling 'type-id'
SELECT
civicrm_activity.id, civicrm_activity.type_id,
civicrm_activity.activity_date_time,
civicrm_activity_1.type_id,
civicrm_activity_1.activity_date_time
FROM
civicrm_activity INNER JOIN civicrm_activity AS civicrm_activity_1
ON civicrm_activity.id = civicrm_activity_1.id
WHERE
civicrm_activity.type_id=9
AND civicrm_activity_1.type_id=50;
I'm actually wondering whether this is in fact not possible to do with SQL? I hope it is possible? Thank you for your patience!
Sounds to me like you only want to get the ID numbers where you have a TYPE_ID entry of both 9 and 50.
SELECT DISTINCT id FROM civicrm_activity WHERE type_id = '9' AND id IN (SELECT id FROM civicrm_activity WHERE type_id = '50');
This will give you a list of id's that has entries with both type_id 9 and 50. With that list you can now go and get the specifics.
Use this SQL for the time of type_id 9
SELECT activity_date_time FROM civicrm_activity WHERE id = 'id_from_last_sql' AND type_id = '9'
Use this SQL for the time of type_id 50
SELECT activity_date_time FROM civicrm_activity WHERE id = 'id_from_last_sql' AND type_id = '50'
Your query looks OK to me, too. The one problem might be that you use only one table alias. I don't know, but perhaps Access treats the table name "specially" such that, in effect, the WHERE clause says
WHERE
civicrm_activity.type_id=9
AND civicrm_activity.type_id=50;
That would certainly explain zero rows returned!
To fix that, use an alias for each table. I suggest shorter ones,
SELECT A.id, A.type_id, A.activity_date_time,
B.type_id, B.activity_date_time
FROM civicrm_activity as A
JOIN civicrm_activity as B
ON A.id = B.id
WHERE A.type_id=9
AND B.type_id=50;