Updating one table from another table - sql

I have created a new table for my use , lets say t1 which has 8 columns in it. I have populated 3 columns through a procedure. Column 1 is name. Now I want to populate the 4th column for corresponding name. This would be update with where clause.
The scenario is I have created a query which has the result, calling that t2 which has name and total_amount. Now I want to populate total_amount into the 4th column of t1.
The approach that I'm right now following is looping through each name in t1 and finding its counter total_amount in t2(with clause) and updating the value in t1. But it is taking infinite time. First is because of looping in t1 , secondly the t2 is itself a query which is executing again and again.
Now, the actual task is much more complicated and I have just provided the crux of it. Please suggest me an approach which is fast.
create or replace procedure proc
is
temp_value number(18,2);
CURSOR total is
select name, age, sex from data_table where
{conditions};
/*Gives me name and age in 1st and 2nd column and likewise data in 3rd column */
begin
FOR temp IN total LOOP
with aa as (SELECT b.name,
NVL (SUM (c.amount), 0) as total_amount
FROM data_table2 b, data_table3 c
WHERE {joins and groub by}
)
/* This gives me total amount for corresponding name. There is no repetition of name */
select nvl(sum(total_amount),0) into temp_value from aa where name = temp.name;
update t1 set amount = temp_value where name = temp.name;
END LOOP;
END;
/

Cant add a comment to question, hence putting it here.
Per your example:
with aa as (SELECT b.name,
NVL (SUM (c.amount), 0) as total_amount
FROM data_table2 b, data_table3 c
WHERE {joins and groub by}
)
/* This gives me total amount for corresponding name. There is no repetition of name */
select nvl(sum(total_amount),0) into temp_value from aa where name = temp.name;
update t1 set amount = temp_value where name = temp.name;
In your with clause you you take some sum, and then populate the sum of those sum for all names in your cursor. Why cant you directly do:
SELECT SUM(NVL(total_amount, 0)) INTO temp_vale FROM
data_tabl1, data_tabl2, data_tabl3
WHERE
--JOIN CONDITIONS
AND data_tabl1.total)name = --data_tabl2/3.name
GROUP BY --clause;
Why I say this is, with clause is not always a good idea. If your 'with' has a huge data, then it will run forever. 'With' is used to take care of repetitive tables with small data being joined again and again.
Also for tuning purpose, try some hints.
Also, NVL(SUM..) why not SUM(NVL(total_amount, 0))?

Related

How to add a LIKE statement or INDEX function in the IN statemeng using PROC SQL

I use the below code to create an indicator that is 0 if the values in the code are not found in a record of another table and 1 if all pertinant values match a record on another table.
proc sql;
create table test as
select id
,a.company_yr in (select company_yr from table2)
and a.industry in (select industry from table2)
and a.sector in (select sector from table2) as match_ind
from work.table1 a;
quit;
My problem is that the company_yr, industry and sector aren't always a perfect match because of abbreviations or other mix ups in the data (e.g., 'FORD MOTORS' in table1 and 'FORD' in table2). I need some way to use a LIKE statement or INDEX statement in conjunction with a TRIM statement to allow me to match parts of the string to make the indicator more accurate. I haven't been able to find a way to effectively accomplish this yet.
Try something like this where you replace the "strip()" function with whatever you want to accomplish the desired normalization, perhaps with nested "compress()" functions, and some "upcase()" for good fun. You could turn the equality in the subquery to a LIKE, but it's not clear what you'd match on.
proc sql;
create table test as
select
id,
exists (select 1 from work.table2 b
where strip(a.industry) = strip(b.industry)
and strip(a.sector) = strip(b.sector)
) as match_ind
from work.table1 a;
quit;

PL/SQL Increase value of new row, with value of previous

I need to increase value of next NEWLOSAL row, to be bigger than one, from previous of NEWHISA.
Like HISAL and LOSAL column.
NEWLOSAL need to be previous NEWHISAL + 1.
not that sure if this is what you want:
update table1 t1
set t1.Newlosal=case when t1.grade=1 then (t1.Newhisal+1) else (select t2.Newhisal+1 from table1 t2 where t2.grade = (t1.grade-1)) end
WHERE EXISTS (
SELECT 1
FROM table1 t2
WHERE t2.grade=(t1.grade-1))
This can efficiently be done using the merge statement and a window function:
merge into table1 tg
using
(
select id, -- I assume this is the PK column
lag(newhisal) over (order by grade) + 1 as new_losal
from table1
) nv on (nv.id = tg.id)
when matched then update
set tg.newlosal = nv.new_losal;
In SQL rows in a table (or a result) or not ordered, so the concept of a "previous" row only makes sense if you define a sort order. That's what the over (order by grade) does in the window function. From the screen shot I can not tell by which column this should be sorted.
The screen shot also doesn't reveal the primary key column of your table. I assumed it's named ID. You have to change that to reflect your real PK column name.
I also didn't include a partition by clause in the window function assuming that the formula should be applied for all rows in the same way. If this is not the case you need to be more specific with your sample data.

What is the difference between the IN operator and = operator in SQL?

I am just learning SQL, and I'm wondering what the difference is between the following lines:
WHERE s.parent IN (SELECT l.parent .....)
versus
WHERE s.parent = (SELECT l.parent .....)
IN
will not generate an error if you have multiple results on the subquery. Allows to have more than one value in the result returned by the subquery.
=
will generate an error if you have more than one result on the subquery.
SQLFiddle Demo (IN vs =)
when you are using 'IN' it can compare multiple values....like
select * from tablename where student_name in('mari','sruthi','takudu')
but when you are using '=' you can't compare multiple values
select * from tablenamewhere student_name = 'sruthi'
i hope this is the right answer
The "IN" clause is also much much much much slower. If you have many results in the select portion of
IN (SELECT l.parent .....),
it will be extremely inefficient as it actually generates a separate select sql statement for each and every result within the select statement ... so if you return 'Cat', 'Dog', 'Cow'
it will essentially create a sql statement for each result... if you have 200 results... you get the full sql statement 200 times...takes forever... (This was as of a few years ago... maybe imporved by now... but it was horribly slow on big result sets.)
Much more efficient to do an inner join such as:
Select id, parent
from table1 as T
inner join (Select parent from table2) as T2 on T.parent = T2.parent
For future visitors.
Basically in case of equals (just remember that here we are talking like where a.name = b.name), each cell value from table 1 will be compared one by one to each cell value of all the rows from table 2, if it matches then that row will be selected (here that row will be selected means that row from table 1 and table 2) for the overall result set otherwise will not be selected.
Now, in case of IN, complete result set on the right side of the IN will be used for comparison, so its like each value from table 1 will be checked on whether this cell value is present in the complete result set of the IN, if it is present then that value will be shown for all the rows of the IN’s result set, so let say IN result set has 20 rows, so that cell value from table 1 will be present in overall result set 20 times (i.e. that particular cell value will have 20 rows).
For more clarity see below screen shot, notice below that how complete result set from the right of the IN (and NOT IN) is considered in the overall result set; whole emphasis is on the fact that in case comparison using =, matching row from second table is selected, while in case of IN complete result from the second table is selected.
In can match a value with more than one values, in other words it checks if a value is in the list of values so for e.g.
x in ('a', 'b', 'x') will return true result as x is in the the list of values
while = expects only one value, its as simple as
x = y returns false
and
x = x returns true
The general rule of thumb is:
The = expects a single value to compare with. Like this:
WHERE s.parent = 'father_name'
IN is extremely useful in scenarios where = cannot work i.e. scenarios where you need the comparison with multiple values.
WHERE s.parent IN ('father_name', 'mother_name', 'brother_name', 'sister_name')
Hope this is useful!!!
IN
This helps when a subquery returns more than one result.
=
This operator cannot handle more than one result.
Like in this example:
SQL>
Select LOC from dept where DEPTNO = (select DEPTNO from emp where
JOB='MANAGER');
Gives ERROR ORA-01427: single-row subquery returns more than one row
Instead use
SQL>
Select LOC from dept where DEPTNO in (select DEPTNO from emp
where JOB='MANAGER');
1) Sometimes = also used as comparison operator in case of joins which IN doesn't.
2) You can pass multiple values in the IN block which you can't do with =. For example,
SELECT * FROM [Products] where ProductID IN((select max(ProductID) from Products),
(select min(ProductID) from Products))
would work and provide you expected number of rows.However,
SELECT * FROM [Products] where ProductID = (select max(ProductID) from Products)
and ProductID =(select min(ProductID) from Products)
will provide you 'no result'. That means, in case subquery supposed to return multiple number of rows , in that case '=' isn't useful.

comparing 2 consecutive rows in a recordset

Currently,I have this objective to meet. I need to query the database for certain results. After done so, I will need to compare the records:
For example: the query return me with 10 rows of records, I then need to compare: row 1 with 2, row 2 with 3, row 3 with 4 ... row 9 with 10.
The final result that I wish to have is 10 or less than 10 rows of records.
I have one approach currently. I do this within a function, hand have the variables call "previous" and "current". In a loop I will always compare previous and current which I populate through the record set using a cursor.
After I got each row of filtered result, I will then input it into a physical temporary table.
After all the results are in this temporary table. I'll do a query on this table and insert the result into a cursor and then returning the cursor.
The problem is: how can I not use a temporary table. I've search through online about using nested tables, but somehow I just could not get it working.
How to replace the temp table with something else? Or is there other approach that I can use to compare the row columns with other rows.
EDIT
So sorry, maybe I am not clear with my question. Here is a sample of the result that I am trying to achieve.
TABLE X
Column A B C D
100 300 99 T1
100 300 98 T2
100 300 97 T3
100 100 97 T4
100 300 97 T5
101 11 11 T6
ColumnA is the primary key of the table. ColumnA has duplicates because table X is an audit table that keep tracks of all changes.column D acts as the timestamp for that record.
For my query, I am only interested in changes in column A,B and D. After the query I would like to get the result as below:
Column A B D
100 300 T1
100 100 T4
100 300 T5
101 11 T6
I think Analytics might do what you want :
select col1, col2, last(col1) over (order by col1, col2) LASTROWVALUE
from table1
this way, LASTROWVALUE will contain de value of col1 for the last row, which you can directly compare to the col1 of the current row.
Look this URL for more info : http://www.orafaq.com/node/55
SELECT ROW_NUMBER() OVER(ORDER BY <Some column name>) rn,
Column1, <Some column name>, CompareColumn,
LAG(CompareColumn) OVER(ORDER BY <Some column name>) PreviousValue,
LEAD(CompareColumn) OVER(ORDER BY <Some column name>) NextValue,
case
when CompareColumn != LEAD(CompareColumn) OVER(ORDER BY <Some column name>) then CompareColumn||'-->'||LEAD(CompareColumn) OVER(ORDER BY <Some column name>)
when CompareColumn = LAG(CompareColumn) OVER(ORDER BY <Some column name>) then 'NO CHANGE'
else 'false'
end
FROM <table name>
You can use this logic in a loop to change behaviour.
Hi It's not very clear what exactly yuo want to accomplish. But maybe you can fetch the results of the original query in a PLSQL collection and use that to do your comparison.
What exactly are you doing the row comparison for? Are you looking to eliminate duplicates, or are you transforming the data into another form and then returning that?
To eliminate duplicates, look to use GROUP BY or DISTINCT functionality in your SELECT.
If you are iterating over the initial data and transforming it in some way then it is hard to do it without using a temporary table - but what exactly is your problem with the temp table? If you are concerned about the performance of a cursor then maybe you could do one outer SELECT that compares the results of two inner SELECTs - but the trick is that the second SELECT is offset by one row, so you achieve the requirement of comparing row 1 against row2, etc.
I think you are complicating things with the temp table.
It can be made using a cursor and 2 temporary variables.
Here is the pseudo code:
declare
v_temp_a%xyz;
v_temp_b%xyz;
i number;
cursor my_cursor is select xyz from xyz;
begin
i := 1;
for my_row in my_cursor loop
if (i = 1)
v_temp_a := my_row;
else
v_temp_b := v_temp_a;
v_temp_a := my_row;
/* at this point v_temp_b has the previous row and v_temp_a has the currunt row
compare them and put whatever logic you want */
end if
i := i + 1;
end loop
end

Use of CASE statement values in THEN expression

I am attempting to use a case statement but keep getting errors. Here's the statement:
select TABLE1.acct,
CASE
WHEN TABLE1.acct_id in (select acct_id
from TABLE2
group by acct_id
having count(*) = 1 ) THEN
(select name
from TABLE3
where TABLE1.acct_id = TABLE3.acct_id)
ELSE 'All Others'
END as Name
from TABLE1
When I replace the TABLE1.acct_id in the THEN expression with a literal value, the query works. When I try to use TABLE1.acct_id from the WHEN part of the query, I get a error saying the result is more than one row. It seems like the THEN expression is ignoring the single value that the WHEN statement was using. No idea, maybe this isn't even a valid use of the CASE statement.
I am trying to see names for accounts that have one entry in TABLE2.
Any ideas would be appreciated, I'm kind of new at SQL.
First, you are missing a comma after TABLE1.acct. Second, you have aliased TABLE1 as acct, so you should use that.
Select acct.acct
, Case
When acct.acct_id in ( Select acct_id
From TABLE2
Group By acct_id
Having Count(*) = 1 )
Then ( Select name
From TABLE3
Where acct.acct_id = TABLE3.acct_id
Fetch First 1 Rows Only)
Else 'All Others'
End as Name
From TABLE1 As acct
As others have said, you should adjust your THEN clause to ensure that only one value is returned. You can do that by add Fetch First 1 Rows Only to your subquery.
Then ( Select name
From TABLE3
Where acct.acct_id = TABLE3.acct_id
Fetch First 1 Rows Only)
Fetch is not accepting in CASE statement - "Keyword FETCH not expected. Valid tokens: ) UNION EXCEPT. "
select name from TABLE3 where TABLE1.acct_id = TABLE3.acct_id
will give you all the names in Table3, which have a accompanying row in Table 1. The row selected from Table2 in the previous line doesn't enter into it.
Must be getting more than one value.
You can replace the body with...
(select count(name) from TABLE3 where TABLE1.acct_id = TABLE3.acct_id)
... to narrow down which rows are returning multiples.
It may be the case that you just need a DISTINCT or a TOP 1 to reduce your result set.
Good luck!
I think that what is happening here is that your case must return a single value because it will be the value for the "name" column. The subquery (select acct_id from TABLE2 group by acct_id having count(*) = 1 ) is OK because it will only ever return one value. (select name from TABLE3 where TABLE1.acct_id= TABLE3.acct_id) could return multiple values depending on your data. The problem is you trying to shove multiple values into a single field for a single row.
The next thing to do would be to find out what data causes multiple rows to be returned by (select name from TABLE3 where TABLE1.acct_id= TABLE3.acct_id), and see if you can further limit this query to only return one row. If need be, you could even try something like ...AND ROWNUM = 1 (for Oracle - other DBs have similar ways of limiting rows returned).