How to update a table using a CTE in Oracle - sql

As an example in Sql server I can achieve this easily like so:
WITH cte_person AS
(
SELECT PersonalIdentificationNumber, PersonName
FROM Employee
)
UPDATE Person
SET Person.PersonName = cte.PersonName
FROM cte_person cte
WHERE Person.PersonalIdentificationNumber = cte.PersonalIdentificationNumber
But how do I achieve this in Oracle using a CTE specifically or is there no support for this? I have searched around and havn't found a satisfying answer. Most seem to wrap the CTE in an inline select statement.

Well, as you asked how to use a CTE in UPDATE, then:
update person p set
p.name = (with cte_person as
(select personalidentificationnumber, name
from employee
)
select c.name
from cte_person c
where c.personalidentificationnumber = p.personalidentificationnumber
)
where exists (select null
from employee e
where e.personalidentificationnumber = p.personalidentificationnumber
);
Though, merge is somewhat simpler as you don't have to additionally check which rows to update (see the exists clause in update example):
merge into person p
using (with cte_person as
(select personalidentificationnumber, name
from employee
)
select c.personalidentificationnumber,
c.name
from cte_person c
) x
on (p.personalidentificationnumber = x.personalidentificationnumber)
when matched then update set
p.name = x.name;
However, this can be simplified - see code Ankit posted (but - as I said - if you want to know how to use a CTE, then that's how).

Oracle does not allow for directly updating a CTE (unlike SQL Server, which allows it). If I understand your requirement correctly, you want to update the names in the Person table using the names from the Employee table, based on matching IDs. One way to do this in Oracle uses a correlated subquery.
UPDATE Person p
SET Name = (SELECT e.Name FROM Employee e
WHERE e.PersonalIdentificationNumber = p.PersonalIdentificationNumber);

You can use MERGE statement for this -
MERGE INTO Person p
USING Employee e
ON (p.PersonalIdentificationNumber = e.PersonalIdentificationNumber)
WHEN MATCHED THEN
UPDATE
SET p.Name = e.Name;

Related

history gives me too many subquery results

I appreciate your help, I hope I'm able to provide adequate information.
I need to roll back owners associated with quite a few entries in our asset table (one owner got applied to a lot of systems). I have a history table with the information I need, and an employee table that gets wedged in there because for some reason the history table stores names rather than employee_id's.
Source data:
asset_table has asset_id, employee_id, asset_tag
employee_table has employee_id, name
history_table has asset_id, old_name, new_name
update asset_table
set employee_id = (select employee_id
from employee_table
where name like (select old_name
from history_table
where asset_table.asset_id=history_table.asset_id
and new_name like 'tobe replaced'))
However, the subquery turns up more than one result per line.
What am I missing to limit that subquery results?
MSSQL server 2012
I guess you need someone who was the last in history:
update at set
employee_id = e.employee_id
from asset_table at
cross apply
(
select top (1)
h.old_name
from history_table h
where at.asset_id = h.asset_id
and h.new_name = 'tobe replaced'
order by h.<date or id or something> desc
) h
inner join employee_table e
on e.name = h.old_name
TOP 1 will fix "more than one result" problem.
Note, LIKE without % works exactly the same way as =.
You can write update statement is as below by using JOIN instead of sub query, Provide table structure and data for more correct query:
update at set at.employee_id = et.employee_id
from asset_table at
inner join history_table ht on at.asset_id = ht.asset_id
and ht.new_name like '%tobe replaced%'
inner join employee_table et on et.name = ht.old_name
and et.employee_id = at.employee_id

Improve query performance for a large join

I am trying to get a list of users who did a pull request on any repos with a specified language.
SELECT distinct(actor_id) as id FROM pull_requests
JOIN (SELECT id FROM repos WHERE language = 'javascript') as res
ON pull_requests.repo_id = res.id
I've been trying to improve the performance of this query. Currently it takes 2sec+ to run.
First thing -- try a semi-join:
SELECT distinct actor_id as id
FROM pull_requests p
where exists (
select null
from repos r
where p.repo_id = r.id and r.language = 'javascript'
)
Secondly -- verify your distinct is necessary based on this change. It probably is in this case, but semi-joins can often times eliminate the need for distinct where the it's used as a crutch for a 1::many returning multiple rows -- the exists will not multiply results based on multiple matches in the repos table.
Try this:
SELECT distinct(A.actor_id) as id FROM pull_requests AS A
INNER JOIN repos AS B ON A.repo_id = B.id AND B.language = 'javascript'
You may need to index on either repo_id and/or id field
Try using IN
SELECT distinct(actor_id) as id
FROM pull_requests
WHERE pull_requests.repo_id IN (SELECT id FROM repos WHERE language = 'javascript')

can I use a variable for the integer expression in a left sql function

I have the following query:
SELECT top 2500 *
FROM table a
LEFT JOIN table b
ON a.employee_id = b.employee_id
WHERE left(a.employee_rc,6) IN
(
SELECT employeeID, access
FROM accesslist
WHERE employeeID = '#client.id#'
)
The sub select in the where clause can return one or several access values, ex:
js1234 BLKHSA
js1234 HDF48R7
js1234 BLN6
In the primary where clause I need to be able to change the integer expression from 6 to 5 or 4 or 7 depending on what the length of the values returned in the sub select. I am at a loss if this is the right way to go about it. I have tried using OR statements but it really slows down the query.
Try using exists instead:
SELECT top 2500 *
FROM table a LEFT JOIN
table b
ON a.employee_id = b.employee_id
WHERE EXISTS (Select 1
FROM accesslist
WHERE employeeID = '#client.id#' and
a.employee_rc like concat(employeeID, '%')
) ;
I don't see how your original query worked. The subquery is returning two columns and that normally isn't allowed in SQL for an in.
Move the subquery to a JOIN:
SELECT TOP 2500 *
FROM table a
LEFT JOIN table b ON a.employee_id = b.employee_id
LEFT JOIN accesslist al ON al.access LIKE concat('%', a.employee_id)
WHERE al.employeeID = '#client.id#'
Like Gordon, I don't quite see how your query worked, so I'm not quite sure if it should be access or employeeID which is matched.
This construct will enable you to do what you said you want to do, have an integer value depend on somethign from a subquery. It's the general idea only, the details are up to you.
select field1, field2
, case when subqueryField1 = 'fred' then 1
when subqueryField1 = 'barney' then 2
else 3 end integerValue
from table1 t1 join (
select idField subqueryField1, etc
from whereever ) t2 on t1.idFeld = t2.idField
where whatever
Also, a couple of things in your query are questionable. First, a top n query without an order by clause doesn't tell the database what records to return. Second, 2500 rows is a lot of data to return to ColdFusion. Are you sure you need it all? Third, selecting * instead of just the fields you need slows down performance. If you think you need every field, think again. Since the employee ids will always match, you don't need both of them.

Update using Distinct SUM

I have found a few good resources that show I should be able to merge a select query with an update, but I just can't get my head around of the correct formatting.
I have a select statement that is getting info for me, and I want to pretty much use those results to Update an account table that matches the accountID in the select query.
Here is the select statement:
SELECT DISTINCT SUM(b.workers)*tt.mealTax as MealCost,b.townID,b.accountID
FROM buildings AS b
INNER JOIN town_tax AS tt ON tt.townID = b.townID
GROUP BY b.townID,b.accountID
So in short I want the above query to be merged with:
UPDATE accounts AS a
SET a.wealth = a.wealth - MealCost
Where MealCost is the result from the select query. I am sure there is a way to put this into one, I just haven't quite been able to connect the dots to get it to run consistently without separating into two queries.
First, you don't need the distinct when you have a group by.
Second, how do you intend to link the two results? The SELECT query is returning multiple rows per account (one for each town). Presumably, the accounts table has only one row. Let's say that you wanted the average MealCost for the update.
The select query to get this is:
SELECT accountID, avg(MealCost) as avg_Mealcost
FROM (SELECT SUM(b.workers)*tt.mealTax as MealCost, b.townID, b.accountID
FROM buildings AS b INNER JOIN
town_tax AS tt
ON tt.townID = b.townID
GROUP BY b.townID,b.accountID
) a
GROUP BY accountID
Now, to put this into an update, you can use syntax like the following:
UPDATE accounts
set accounts.wealth = accounts.wealth + asum.avg_mealcost
from (SELECT accountID, avg(MealCost) as avg_Mealcost
FROM (SELECT SUM(b.workers)*tt.mealTax as MealCost, b.townID, b.accountID
FROM buildings AS b INNER JOIN
town_tax AS tt
ON tt.townID = b.townID
GROUP BY b.townID,b.accountID
) a
GROUP BY accountID
) asum
where accounts.accountid = asum.accountid
This uses SQL Server syntax, which I believe is the same as for Oracle and most other databases. Mysql puts the "from" clause before the "set" and allows an alias on "update accounts".

Simple SQL Problem

I have a SQL query that I cant wrap my mind around. I do not have a large amount of sql experience. So I need some help
I have a table XXX:
Social Security No (SSN).
Name.
organisation. (Finance/IT)
In english what I want is:
To select all SSNs and Names in "Finance" where there is a different name for that SSN in "IT".
My not working attempt:
select ssn , name from XXX where org = "Finance" and name not in (select name from XXX where org="IT" and ssn=the_first_ssn)
Please help.
I have decided to make it a bit more difficult.
SSN can ocur multiple times in "IT":
So I want to select all SSNs and Names in Finance where the SSN does not exist with the same Name in "IT"
You could use a subquery in an exists clause:
select ssn, name
from YourTable a
where organisation = 'Finance'
and exists
(
select *
from YourTable b
where organisation = 'IT'
and a.ssn = b.ssn
and a.name <> b.name
)
The subquery says there must be a row in IT with the same SSN but a different name.
Assuming ssn is a unique key...
select ssn, name
from XXX XXX1
where org = "Finance"
and ssn in
(
select ssn
from XXX XXX2
where org="IT"
and XXX1.name<>XXX2.name
)
SELECT distinct
t1.ssn,
t1.name
from
xxx t1
inner join xxx t2 on t1.ssn=t2.ssn and t1.name<>t2.name
where t1.org='Finance' and t2.org='IT'
I know I'm late to the party, but I'm working on learning SQL and I wanted to try my hand at a solution and compare against the existing answers. I created a table Personnel with some testing data.
My SQL Server only query uses CTEs and an INNER JOIN:
WITH
Finance AS (SELECT SSN, Name FROM Personnel WHERE Org = 'Finance'),
IT AS (SELECT SSN, Name FROM Personnel WHERE Org = 'IT')
SELECT Finance.SSN, Finance.Name
FROM Finance
INNER JOIN IT ON IT.SSN = Finance.SSN
WHERE IT.Name != Finance.Name
Alexander's solution uses a straight INNER JOIN. I rewrote it a little bit, putting the name comparison in the WHERE clause, and dropping DISTINCT because it's not required:
SELECT Finance.SSN, Finance.Name
FROM Personnel Finance
INNER JOIN Personnel IT ON Finance.SSN = IT.SSN
WHERE
(Finance.Org = 'Finance' AND IT.Org = 'IT') AND
(Finance.Name != IT.Name)
Andomar's solution using a correlated subquery inside an EXISTS clause:
SELECT SSN, Name
FROM Personnel a
WHERE
(Org = 'Finance') AND
EXISTS
(
SELECT *
FROM Personnel b
WHERE (Org = 'IT') AND (a.SSN = b.SSN) AND (a.Name != b.Name)
)
barrylloyd's solution using a correlated subquery inside an IN clause:
SELECT SSN, Name
FROM Personnel p1
WHERE
(Org = 'Finance') AND
SSN IN
(
SELECT SSN FROM Personnel p2
WHERE (Org = 'IT') AND (p1.Name != p2.Name)
)
I plugged all of these into SQL Server, and it turns out that queries 1 and 2 both generate the same query plan, and queries 3 and 4 generate the same query plan. The difference between the two groups is the former group actually does an INNER JOIN internally, while the latter group does a left semi-join instead. (See here for an explanation of the different types of joins.)
I'm assuming there is a slight performance advantage favouring the left semi-join; however, for the business case, if you want to see any data columns from the right table (for example, if you want to display both names to compare them), you would have to completely rewrite those queries to use an INNER JOIN-based solution.
So given all that, I would favour solution 2, because the performance is so similar to 3 and 4, and it's far more flexible than those as well. My solution makes the SELECT statement very easy to read, but it's more verbose than 2 and not as portable. I suppose that mine might be better for readability if you have to do additional filtering on each of the two "sub-tables," or if the results of this query are going to be used as an intermediate step to a further goal.