PL/SQL select returns more than one line - oracle

I'm embarrassed to admit this is a totally noob question - but I take shelter in the fact that I come from a T-SQL world and this is a totally new territory for me
This is a simple table I have with 4 records only
ContractorID ProjectID Cost
1 100 1000
2 100 800
3 200 1005
4 300 2000
This is my PL SQL function which should take a contractor and a project id and return number of hours ( 10 in this case )
create or replace FUNCTION GetCost(contractor_ID IN NUMBER,
project_ID in NUMBER)
RETURN NUMBER
IS
ContractorCost NUMBER;
BEGIN
Select Cost INTO ContractorCost
from Contractor_Project_Table
where ContractorID= contractor_ID and ProjectID =project_ID ;
return ContractorCost;
END;
But then using
select GetCost(1,100) from Contractor_Project_Table;
This returns same row 4 times
1000
1000
1000
1000
What is wrong here? WHy is this returning 4 rows instead of 1
Thank you for

As #a_horse_with_no_name points out, the problem is that Contractor_Project_Table has (presumably) 4 rows so any SELECT against Contractor_Project_Table with no WHERE clause will always return 4 rows. Your function is getting called 4 times, one for each row in the table.
If you want to call the function with a single set of parameters and return a single row of data, you probably want to select from the dual table
SELECT GetCost( 1, 100 )
FROM dual

Because you have 4 rows in Contractor_Project_Table table. Use this query to get one record.
select GetCost(1,100) from dual;

Related

How to return rows based on the database user and the table's contents?

I have a following table:
id name score
1 SYS 4
2 RHWTT 5
3 LEO 4
4 MOD3_ADMIN 5
5 VPD674 4
6 SCOTT 5
7 HR 4
8 OE 5
9 PM 4
10 IX 5
11 SH 4
12 BI 5
13 IXSNEAKY 4
14 DVF 5
I want to create a policy function in Oracle SQL that makes sure of the following things:
If a user(Leo) is executing a select statement on this table, it only gets 3 LEO 4.
sys_dba gets all the results no matter what.
I have given select permissions to Leo on this table created by Scott.
I am getting stuck at writing this complex PL/SQL function. I tried the following and it states compilation errors. Also, I think it does not do what I intend to do:
CREATE FUNCTION no_show_all (
p_schema IN NUMBER(5),
p_object IN VARCHAR2
)
RETURN
AS
BEGIN
RETURN 'select avg(score) from scott.rating';
END;
/
Based on your previous question and info you posted, here's how I understood the question: if you granted select on the whole table to any user, then it is able to fetch all rows from it. You have to further restrict values.
One option - as we're talking about the function - is to use case in where clause.
Here's an example.
Sample data:
SQL> create table rating as
2 select 1 id, 'sys' name, 4 score from dual union all
3 select 3, 'leo' , 3 from dual union all
4 select 6, 'scott' , 5 from dual union all
5 select 7, 'hr' , 2 from dual;
Table created.
Function:
it accepts username as a parameter (mind letter case! In my example, everything is lowercase. In your, perhaps you'll have to use upper function or something like that)
case says: if par_user is equal to sys, let it fetch all rows. Otherwise, fetch only rows whose name column's value is equal to par_user
return the result
So:
SQL> create or replace function f_rating (par_user in varchar2)
2 return number
3 is
4 retval number;
5 begin
6 select avg(score)
7 into retval
8 from rating
9 where name = case when par_user = 'sys' then name
10 else par_user
11 end;
12 return retval;
13 end;
14 /
Function created.
Let's try it:
SQL> select f_rating('sys') rating_sys,
2 f_rating('hr') rating_hr
3 from dual;
RATING_SYS RATING_HR
---------- ----------
3,5 2
SQL>
I suggest creating a view for each user, like so
create view THE_VIEW as select * from TABLE where NAME = user
Then grant access to the view only.
Now it doesn't matter what kind of query a user tries to perform on your table, she will only get one row back.
Of-course the DBA user can access all the table data.

Number of trailing zeros in a number

I have a column MONTHLY_SPEND in the table with data type of NUMBER. I am trying to write a query which will return number of zeros in the column.
e.g..
1000 will return 3
14322 will return 0
1230 will return 1
1254000.65 will return 0
I tried using mod operator and 10 but without the expected result. Any help is appreciated. Please note that database is Oracle and we can't create procedure/function.
select nvl(length(regexp_substr(column, '0+$')), 0) from table;
Here is one way to find
create table spend
(Monthly_spend NUMBER);
Begin
insert into spend values (1000)
insert into spend values (14322)
insert into spend values (1230)
insert into spend values (1254000.65)
End;
This query will for this data :
select Monthly_spend,REGEXP_COUNT(Monthly_spend,0)
from spend
where Monthly_spend not like '%.%' ;
if have one more data like 102 and if it should be zero , then try below query:
select Monthly_spend,case when substr(Monthly_spend,-1,1)=0 THEN REGEXP_COUNT(Monthly_spend,0) ELSE 0 END from spend;
Here is final query for value like 2300120 or 230012000
select Monthly_spend,
case when substr(Monthly_spend,-1,1)=0 and REGEXP_COUNT(trim (0 from Monthly_spend),0)<=0 THEN REGEXP_COUNT(Monthly_spend,0)
when REGEXP_COUNT(trim (0 from Monthly_spend),0)>0 THEN LENGTH(Monthly_spend) - LENGTH(trim (0 from Monthly_spend))
ELSE 0 END from spend;
Output :
1000 3
1254000.65 0
14322 0
1230 1
102 0
2300120 1
230012000 3
You can try this, a simple solution.
select length(to_char(col1))-length(rtrim(to_char(col1), '0')) no_of_trailing_zeros from dual;
select length(to_char('123.120'))-length(rtrim(to_char('123.120'), '0')) no_of_trailing_zeros from dual;

simple random sampling while pulling data from warehouse(oracle engine) using proc sql in sas

I need to pull humongous amount of data, say 600-700 variables from different tables in a data warehouse...now the dataset in its raw form will easily touch 150 gigs - 79 MM rows and for my analysis purpose I need only a million rows...how can I pull data using proc sql directly from warehouse by doing simple random sampling on the rows.
Below code wont work as ranuni is not supported by oracle
proc sql outobs =1000000;
select * from connection to oracle(
select * from tbl1 order by ranuni(12345);
quit;
How do you propose I do it
Use the DBMS_RANDOM Package to Sort Records and Then Use A Row Limiting Clause to Restrict to the Desired Sample Size
The dbms_random.value function obtains a random number between 0 and 1 for all rows in the table and we sort in ascending order of the random value.
Here is how to produce the sample set you identified:
SELECT
*
FROM
(
SELECT
*
FROM
tbl1
ORDER BY dbms_random.value
)
FETCH FIRST 1000000 ROWS ONLY;
To demonstrate with the sample schema table, emp, we sample 4 records:
SCOTT#DEV> SELECT
2 empno,
3 rnd_val
4 FROM
5 (
6 SELECT
7 empno,
8 dbms_random.value rnd_val
9 FROM
10 emp
11 ORDER BY rnd_val
12 )
13 FETCH FIRST 4 ROWS ONLY;
EMPNO RND_VAL
7698 0.06857749035643605682648168347885993709
7934 0.07529612360785920635181751566833986766
7902 0.13618520865865754766175030040204331697
7654 0.14056380246495282237607922497308953768
SCOTT#DEV> SELECT
2 empno,
3 rnd_val
4 FROM
5 (
6 SELECT
7 empno,
8 dbms_random.value rnd_val
9 FROM
10 emp
11 ORDER BY rnd_val
12 )
13 FETCH FIRST 4 ROWS ONLY;
EMPNO RND_VAL
7839 0.00430658806761508024693197916281775492
7499 0.02188116061148367312927392115186317884
7782 0.10606515700372416131060633064729870016
7788 0.27865276349549877512032787966777990909
With the example above, notice that the empno changes significantly during the execution of the SQL*Plus command.
The performance might be an issue with the row counts you are describing.
EDIT:
With table sizes in the order of 150 gigs - 79 MM, any sorting would be painful.
If the table had a surrogate key based on a sequence incremented by 1, we could take the approach of selecting every nth record based on the key.
e.g.
--scenario n = 3000
FROM
tbl1
WHERE
mod(table_id, 3000) = 0;
This approach would not use an index (unless a function based index is created), but at least we are not performing a sort on a data set of this size.
I performed an explain plan with a table that has close to 80 million records and it does perform a full table scan (the condition forces this without a function based index) but this looks tenable.
None of the answers posted or comments helped my cause, it could but we have 87 MM rows
Now I wanted the answer with the help of sas: here is what I did: and it works. Thanks all!
libname dwh path username pwd;
proc sql;
create table sample as
(select
<all the variables>, ranuni(any arbitrary seed)
from dwh.<all the tables>
<bunch of where conditions goes here>);
quit);

Split amount into multiple rows if amount>=$10M or <=$-10B

I have a table in oracle database which may contain amounts >=$10M or <=$-10B.
99999999.99 chunks and also include remainder.
If the value is less than or equal to $-10B, I need to break into one or more 999999999.99 chunks and also include remainder.
Your question is somewhat unreadable, but unless you did not provide examples here is something for start, which may help you or someone with similar problem.
Let's say you have this data and you want to divide amounts into chunks not greater than 999:
id amount
-- ------
1 1500
2 800
3 2500
This query:
select id, amount,
case when level=floor(amount/999)+1 then mod(amount, 999) else 999 end chunk
from data
connect by level<=floor(amount/999)+1
and prior id = id and prior dbms_random.value is not null
...divides amounts, last row contains remainder. Output is:
ID AMOUNT CHUNK
------ ---------- ----------
1 1500 999
1 1500 501
2 800 800
3 2500 999
3 2500 999
3 2500 502
SQLFiddle demo
Edit: full query according to additional explanations:
select id, amount,
case
when amount>=0 and level=floor(amount/9999999.99)+1 then mod(amount, 9999999.99)
when amount>=0 then 9999999.99
when level=floor(-amount/999999999.99)+1 then -mod(-amount, 999999999.99)
else -999999999.99
end chunk
from data
connect by ((amount>=0 and level<=floor(amount/9999999.99)+1)
or (amount<0 and level<=floor(-amount/999999999.99)+1))
and prior id = id and prior dbms_random.value is not null
SQLFiddle
Please adjust numbers for positive and negative borders (9999999.99 and 999999999.99) according to your needs.
There are more possible solutions (recursive CTE query, PLSQL procedure, maybe others), this hierarchical query is one of them.

Oracle aggregate function to return a random value for a group?

The standard SQL aggregate function max() will return the highest value in a group; min() will return the lowest.
Is there an aggregate function in Oracle to return a random value from a group? Or some technique to achieve this?
E.g., given the table foo:
group_id value
1 1
1 5
1 9
2 2
2 4
2 8
The SQL query
select group_id, max(value), min(value), some_aggregate_random_func(value)
from foo
group by group_id;
might produce:
group_id max(value), min(value), some_aggregate_random_func(value)
1 9 1 1
2 8 2 4
with, obviously, the last column being any random value in that group.
You can try something like the following
select deptno,max(sal),min(sal),max(rand_sal)
from(
select deptno,sal,first_value(sal)
over(partition by deptno order by dbms_random.value) rand_sal
from emp)
group by deptno
/
The idea is to sort the values within group in random order and pick the first.I can think of other ways but none so efficient.
You might prepend a random string to the column you want to extract the random element from, and then select the min() element of the column and take out the prepended string.
select group_id, max(value), min(value), substr(min(random_value),11)
from (select dbms_random.string('A', 10)||value random_value,foo.* from foo)
In this way you cand avoid using the aggregate function and specifying twice the group by, which might be useful in a scenario where your query is very complicated / or you are just exploring the data and are entering manually queries with a lengthy and changing list of group by columns.

Resources