DECODE In Oracle PL/SQL - oracle

I am trying to use DECODE in PL/SQL statement (for sample HR schema)
but I got this error:
''The number specified in exact fetch is less than the rows returned''
This statement got a DEPARTMENT_ID from the user , compare it with a decode section, and shows where is that department located.
declare
v_dep varchar2(30);
v_User_Input number(4):=&EnterLocID;
begin
select decode(v_User_Input,10,'Seattle',
20,'Toronto',
30,'Toronto',
40,'London',
50,'South San Francisco',
60,'Southlake',
70,'Munich',
80,'Oxford',
90,'Toronto',
'else' )into v_dep from locations l,departments d
where v_User_Input = d.department_id;
DBMS_OUTPUT.PUT_LINE(v_dep);
end;
/

The way I see it, you forgot to join LOCATIONS and DEPARTMENTS tables so they are cross-joined, and then you applied a filter to D.DEPARTMENT_ID. However, there's a possibility that query still returns 2 or more rows which can't fit into a scalar V_DEP variable.
Therefore, this is what you might need to do (I don't have your tables so I can't test it, but I hope you'll get the idea):
FROM locations l join departments d on d.location_id = l.location_id
WHERE l.location_id = v_User_Input
Also, as you can see, I modified the WHERE clause and used l.location_id instead of your d.department_id. Why?
because substitution variable suggests so (&EnterLocID) - location ID, not department ID
if it was really d.department_id, then why are you joining locations and departments in the first place? Omit locations table if you don't need it.
Finally, why decode? If you joined these two tables, then you have everything you need, i.e. something like this should do it:
select l.location_name
into v_dep
from locations l join departments d on d.location_id = l.location_id
where l.location_id = v_user_input
Or, if it has to be decode for some reason (educational?), then
SELECT DECODE (v_User_Input,
10, 'Seattle',
20, 'Toronto',
30, 'Toronto',
40, 'London',
50, 'South San Francisco',
60, 'Southlake',
70, 'Munich',
80, 'Oxford',
90, 'Toronto',
'else')
INTO v_dep
FROM ldepartments d
WHERE d.department_id = v_user_input

Related

Query to count values from different tables

From a database composed of two tables, EMP and DEP, I need to list all locations (DEPT.LOC ),how many departments are in this location and how many employees (EMP.EMPNO) are in this departments.
Create a view listing all locations (Loc) along with the number of departments in that location and the number of employees employed in those departments.
I wrote the below query but I get the error: missing expression.
SELECT D1.LOC, COUNT(D1.DEPTNO),COUNT(SELECT * FROM EMP E,DEPT D WHERE E.DEPTNO = D.DEPTNO AND D.LOC = D1.LOC ) FROM DEPT D1 GROUP BY D1.LOC
The way you tried is almost right (although it is not the most natural way to do it) - and it can be salvaged.
Your inner select returns several rows per department. COUNT(...) applies to a single expression - it is over a column, but it counts individual expressions, not multi-row inputs.
Instead, in the inner select you should select count(*), not *; and then the outer aggregate should be sum(). Something like this:
SELECT D1.LOC, COUNT(D1.DEPTNO),
sum((SELECT count(*) FROM EMP E, DEPT D
WHERE E.DEPTNO = D.DEPTNO AND D.LOC = D1.LOC ))
FROM DEPT D1 GROUP BY D1.LOC
Note also the two sets of parentheses used with the sum() function. (This is, in fact, what caused the immediate error you reported.) You are summing numbers, and the outer parentheses are part of the function call. But the numbers you are summing are the result of a "scalar subquery" (a subquery that returns a single row and single column, which happens to be a numeric value over which you can sum; in this case, that numeric value is a count, of employees by department). A subquery, scalar or otherwise, must always also be enclosed in its own set of parentheses, even if it appears within a function call, or other kinds of parentheses.
Now: the more natural way to do the same thing is with a join; not sure if you are far enough in your intro course to have studied this though.
select d.loc, count(distinct d.deptno) as departments, count(e.empno) as employees
from dept d left outer join emp e on d.deptno = e.deptno
group by d.loc
;
The outer join is needed so that departments are counted (for each location) even if they don't have any employees. In the SCOTT schema there is, in fact, a department with no employees - and it is the only department in its location. If you used an inner join above, that location wouldn't even appear in the output.
NOTE - the homework problem asks you to create a view, not a SELECT query. That part is trivial - I assume you can do it by yourself.

How to use GROUP BY clause with COUNT(*)

I have two tables on Oracle database, one is named departments_table and the other is locations_table. The departments.table has dep_id, dep_name, location_id, staff_id, employer_id. The locations table consists of location_id, city_id, streetname_id and postcode_id. How do I calculate the number of departments that each location has?
This is the code below is what I have tried to replicate but have been unsuccessful. The error message below that is what shows once the code has submitted.
SELECT dep_name, location_id,
COUNT(*)
FROM departments_table
WHERE location_id => 1
GROUP BY dep_name;
The results of this is an error, " not a single group function "
If you want to count how many departments are in each location, then you must group by location, not by department name, right? Let's start with that.
Then, you don't need ANYTHING about the individual departments in the output of the query, do you? You just need the location id and the count of departments.
select location_id, count(*) as cnt
from departments_table
group by location_id
;
This does most of the work. You may want to add the location name (city, address, etc.), which is/are stored elsewhere - in the locations_table. So you will need a join. And there may be locations in that table that are not, in fact, the location of any department (their id doesn't appear in the departments_table at all). If so, you would need an OUTER join. Also for those departments you probably want to show a count of 0 (rather than null) - you can "fix" that with the nvl() function. So you will end up with something like
select l.*, nvl(g.cnt, 0) as department_count
from locations_table l
left outer join
( select location_id, count(*) as cnt
from departments_table
group by location_id
) g
on l.location_id = g.location_id
;
SELECT l.location_id, l.city, COUNT(d.DEPARTMENT_ID)
FROM OEHR_LOCATIONS l, OEHR_DEPARTMENTS d WHERE l.location_id = d.location_id
GROUP BY l.location_id, l.city ORDER BY l.city;
This method works. I created aliases and made minor changes. OEHR stands for the table names so ignore that.

Why sub SQL in FROM area can't be null?

select col_1,
col_2
from tbl_a A,
tbl_b B,
( select col_1,col_2 from tbl_c where clo_c_1 <> '1' ) C
where A.col_1 = B.col_1
and A.col_2 = B.col_2
and (A.col_2 = B.col_2
or C.col_2 = A.col_2);
My environment is Oracle,when I run this SQL,if the sub SQL C hasn't got a result,then the entire SQL returns NULL.Whereas if C has a result(not null) which fits other condions,there could be a result.Would somebody explain why sub SQL at the from area need to be not NULL?Thanks very much.
You need to bring yourself into the 90s and start using standard joins:
select col_1,
col_2
from tbl_a A
inner join
tbl_b B
on A.col_1 = B.col_1
and A.col_2 = B.col_2
left join
( select col_1,col_2 from tbl_c where clo_c_1 <> '1' ) C
on
C.col_2 = A.col_2
As a guess. I'm not entirely sure what your join conditions should be but that's my first attempt.
This is expected behaviour. When you join two result sets, you only want to get results where the join criteria is satisfied. If the criteria are not satisfied, you should get no results.
If I run the query "get me all the employees older than 65, and get their departments", if there are no employees older than 65, you would not expect to get any results, even if there are some departments in the database.
SELECT emp.name, dept.dept_name
FROM emp
JOIN dept
ON (emp.dept_no = dept.dept_no)
WHERE emp.age > 65;
As the others said, if you actually want to get rows regardless of whether the subquery has any results, you need to use an outer join.

Oracle, insert multirows from subquery with more than one row

Im trying to copy some field from a table to other, I want to do iy by using insert with a subquery like this:
insert into sed_reporte_generico
(srg_usuario,
srg_nombres,
srg_ape_paterno,
srg_ape_materno,
srg_objetivo,
srg_peso_ob,
srg_calf_ob)
values
(
(select us.su_st_usuario, us.su_st_nombres, us.su_st_ap_paterno, us.su_st_ap_materno, ob.soc_st_descripcion, ob.soc_nr_peso,ob.soc_nr_calificacion
from sed_objetivo ob, sed_usuarios us, sed_evaluacion ev
where ob.se_evaluacion_pk = ev.se_evaluacion_pk and ev.su_colaborador_fk = us.su_usuarios_pk)
);
but I got this error:
01427. 00000 - "single-row subquery returns more than one row"
any idea how should I do this?
Thanks,
Think you got to choose between
insert into table (a, b, c) VALUES(1, 2, 3)
and
insert into table (a, b, c)
(SELECT x, y, z from table2)
You can have VALUES and SELECT mixed only (as pointed by an anomyous horse)
when your select query returns only one column and one row !
By the way, use JOIN... to Join your tables (use of the WHERE clauses to join tables is rather a bad habit) :
INSERT INTO sed_reporte_generico
(srg_usuario,
srg_nombres,
srg_ape_paterno,
srg_ape_materno,
srg_objetivo,
srg_peso_ob,
srg_calf_ob)
(select us.su_st_usuario, us.su_st_nombres, us.su_st_ap_paterno, us.su_st_ap_materno, ob.soc_st_descripcion, ob.soc_nr_peso,ob.soc_nr_calificacion
FROM sed_objetivo ob
JOIN sed_evaluacion ev ON ob.se_evaluacion_pk = ev.se_evaluacion_pk
JOIN sed_usuarios us on ev.su_colaborador_fk = us.su_usuarios_pk)
);

Find if a column in Oracle has a sequence

I am attempting to figure out if a column in Oracle is populated from a sequence. My impression of how Oracle handles sequencing is that the sequence and column are separate entities and one needs to either manually insert the next sequence value like:
insert into tbl1 values(someseq.nextval, 'test')
or put it into a table trigger. Meaning that it is non-trivial to tell if a column is populated from a sequence. Is that correct? Any ideas about how I might go about figuring out if a column is populated from a sequence?
You are correct; the sequence is separate from the table, and a single sequence can be used to populate any table, and the values in a column in some table may mostly come from a sequence (or set of sequences), except for the values manually generated.
In other words, there is no mandatory connection between a column and a sequence - and therefore no way to discover such a relationship from the schema.
Ultimately, the analysis will be of the source code of all applications that insert or update data in the table. Nothing else is guaranteed. You can reduce the scope of the search if there is a stored procedure that is the only way to make modifications to the table, or if there is a trigger that sets the value, or other such things. But the general solution is the 'non-solution' of 'analyze the source'.
If the sequence is used in a trigger, it is possible to find which tables it populates:
SQL> select t.table_name, d.referenced_name as sequence_name
2 from user_triggers t
3 join user_dependencies d
4 on d.name = t.trigger_name
5 where d.referenced_type = 'SEQUENCE'
6 and d.type = 'TRIGGER'
7 /
TABLE_NAME SEQUENCE_NAME
------------------------------ ------------------------------
EMP EMPNO_SEQ
SQL>
You can vary this query to find stored procedures, etc that make use of the sequence.
There are no direct metadata links between Oracle sequences and any use in the database. You could make an intelligent guess if a column's values are related to a sequence by querying the USER_SEQUENCES metadata and comparing the LAST_NUMBER column to the data for the column.
select t.table_name,
d.referenced_name as sequence_name,
d.REFERENCED_OWNER as "OWNER",
c.COLUMN_NAME
from user_trigger_cols t, user_dependencies d, user_tab_cols c
where d.name = t.trigger_name
and t.TABLE_NAME = c.TABLE_NAME
and t.COLUMN_NAME = c.COLUMN_NAME
and d.referenced_type = 'SEQUENCE'
and d.type = 'TRIGGER'
As Jonathan pointed out: there is no direct way to relate both objects. However, if you "keep a standard" for primary keys and sequences/triggers you could find out by finding the primary key and then associate the constraint to the table sequence.
I was in need of something similar since we are building a multi-db product and I tried to replicate some classes with properties found in a DataTable object from .Net which has AutoIncrement, IncrementSeed and IncrementStep which can only be found in the sequences.
So, as I said, if you, for your tables, use a PK and always have a sequence associated with a trigger for inserts on a table then this may come handy:
select tc.table_name,
case tc.nullable
when 'Y' then 1
else 0
end as is_nullable,
case ac.constraint_type
when 'P' then 1
else 0
end as is_identity,
ac.constraint_type,
seq.increment_by as auto_increment_seed,
seq.min_value as auto_increment_step,
com.comments as caption,
tc.column_name,
tc.data_type,
tc.data_default as default_value,
tc.data_length as max_length,
tc.column_id,
tc.data_precision as precision,
tc.data_scale as scale
from SYS.all_tab_columns tc
left outer join SYS.all_col_comments com
on (tc.column_name = com.column_name and tc.table_name = com.table_name)
LEFT OUTER JOIN SYS.ALL_CONS_COLUMNS CC
on (tc.table_name = cc.table_name and tc.column_name = cc.column_name and tc.owner = cc.owner)
LEFT OUTER JOIN SYS.ALL_CONSTRAINTS AC
ON (ac.constraint_name = cc.constraint_name and ac.owner = cc.owner)
LEFT outer join user_triggers trg
on (ac.table_name = trg.table_name and ac.owner = trg.table_owner)
LEFT outer join user_dependencies dep
on (trg.trigger_name = dep.name and dep.referenced_type='SEQUENCE' and dep.type='TRIGGER')
LEFT outer join user_sequences seq
on (seq.sequence_name = dep.referenced_name)
where tc.table_name = 'TABLE_NAME'
and tc.owner = 'SCHEMA_NAME'
AND AC.CONSTRAINT_TYPE = 'P'
union all
select tc.table_name,
case tc.nullable
when 'Y' then 1
else 0
end as is_nullable,
case ac.constraint_type
when 'P' then 1
else 0
end as is_identity,
ac.constraint_type,
seq.increment_by as auto_increment_seed,
seq.min_value as auto_increment_step,
com.comments as caption,
tc.column_name,
tc.data_type,
tc.data_default as default_value,
tc.data_length as max_length,
tc.column_id,
tc.data_precision as precision,
tc.data_scale as scale
from SYS.all_tab_columns tc
left outer join SYS.all_col_comments com
on (tc.column_name = com.column_name and tc.table_name = com.table_name)
LEFT OUTER JOIN SYS.ALL_CONS_COLUMNS CC
on (tc.table_name = cc.table_name and tc.column_name = cc.column_name and tc.owner = cc.owner)
LEFT OUTER JOIN SYS.ALL_CONSTRAINTS AC
ON (ac.constraint_name = cc.constraint_name and ac.owner = cc.owner)
LEFT outer join user_triggers trg
on (ac.table_name = trg.table_name and ac.owner = trg.table_owner)
LEFT outer join user_dependencies dep
on (trg.trigger_name = dep.name and dep.referenced_type='SEQUENCE' and dep.type='TRIGGER')
LEFT outer join user_sequences seq
on (seq.sequence_name = dep.referenced_name)
where tc.table_name = 'TABLE_NAME'
and tc.owner = 'SCHEMA_NAME'
AND AC.CONSTRAINT_TYPE is null;
That would give you the list of columns for a schema/table with:
Table name
If column is nullable
Constraint type (only for PK's)
Increment seed (from the sequence)
Increment step (from the sequence)
Column comments
Column name, of course :)
Data type
Default value, if any
Length of column
Index (column id)
Precision (for numbers)
Scale (for numbers)
I'm pretty sure that code can be optimized but it works for me, I use it to "load metadata" for tables and then represent that metadata as entities on my frontend.
Note that I'm filtering only primary keys and not retrieving compound key constraints since I don't care about those. If you do you'll have to modify the code to do so and make sure that you filter duplicates since you could get one column twice (one for the PK constraint, another for the compound key).

Resources