Find factorial in Oracle - oracle

I have a table which has a column call numbers
Numbers
------
3
5
I am trying to get the factorial of those. I am using the below logic but not with proper result
Select
Numbers
,EXP(SUM(LN(Numbers)) OVER (ORDER BY Numbers)) Factorial
FROM testTbl
*Output
*
Numbers Factorial
------ ---------
3 3.00000000000000000000000000000000000001
5 15.0000000000000000000000000000000000002
What is wrong? Please help
Expected
--------
Numbers Factorial
------ ---------
3 6
5 120
Thanks in advance

I've had a go at this from another angle, trying to do it all in a SQL statement (using your table testTbl and the column numbers).
This is what I've come up with, see if it suits you:
SELECT testtbl.numbers,
ROUND( EXP( SUM( LN( t1.n ) ) ) ) AS factorial
FROM ( SELECT UNIQUE LEVEL n
FROM testtbl
CONNECT BY LEVEL <= numbers) t1,
( SELECT UNIQUE LEVEL n
FROM testtbl
CONNECT BY LEVEL <= numbers) t2,
testTbl
WHERE t1.n <= t2.n
AND t2.n = testTbl.numbers
GROUP BY testtbl.numbers
ORDER BY testtbl.numbers;
Gives the output:
Numbers Factorial
3 6
5 120
Hope it helps...

Were it me, I'd create a factorial function and call that user-defined function in my query. Something like
SQL> create function factorial( p_n in number )
2 return number
3 is
4 begin
5 if( p_n = 1 )
6 then
7 return p_n;
8 else
9 return p_n * factorial( p_n - 1 );
10 end if;
11 end;
12 /
Function created.
SQL> with t as (
2 select 3 num from dual
3 union all
4 select 5 from dual
5 )
6 select num,
7 factorial(num)
8 from t;
NUM FACTORIAL(NUM)
---------- --------------
3 6
5 120
If for some reason you cannot define a new function and you really want to do it in SQL, you'll can generate all the numbers less than the number in your table and then aggregate those generated numbers.
SQL> ed
Wrote file afiedt.buf
1 with t as (
2 select 3 num from dual
3 union all
4 select 5 from dual
5 )
6 select t.num,
7 exp( sum(ln(gen.num))) factorial
8 from (select level num
9 from dual
10 connect by level <= (select max(t.num) from t)) gen,
11 t
12 where gen.num <= t.num
13* group by t.num
SQL> /
NUM FACTORIAL
---------- ----------
5 120
3 6

Related

how to perform oracle ceil with decimal points

In round function we can use (4,512,1) but the same is not true for ceil and floor. I want to ceil and my floor the value 4,512 as 4,5 and 4,6 but the decimal point 1 is not always constant. it may vary. For Floor with decimal points the trunc function can be used. Is there a way to perform ceil with decimal points ?
Adapting how the round function is defined (for positive numbers):
ROUND(n, integer) = FLOOR(n * POWER(10, integer) + 0.5) * POWER(10, -integer)
... for a general case of a variable ceiling you might want something like:
ceil(n * power(10, integer)) * power(10, -integer)
So you could define your own functions:
create or replace function my_ceil(p_number number, p_decimals pls_integer)
return number as
begin
return ceil(p_number * power(10, p_decimals)) * power(10, -p_decimals);
end;
/
create or replace function my_floor(p_number number, p_decimals pls_integer)
return number as
begin
return floor(p_number * power(10, p_decimals)) * power(10, -p_decimals);
end;
/
Then with some sample data:
with t (n) as (
select 4.512 from dual union all
select 5.12345 from dual union all
select 6 from dual union all
select 0 from dual union all
select -1.23 from dual
)
select n, 0 as d, my_ceil(n, 0) as my_ceil, my_floor(n, 0) as my_floor from t
union all
select n, 1 as d, my_ceil(n, 1), my_floor(n, 1) from t
union all
select n, 2 as d, my_ceil(n, 2), my_floor(n, 2) from t
union all
select n, 3 as d, my_ceil(n, 3), my_floor(n, 3) from t
union all
select n, 4 as d, my_ceil(n, 4), my_floor(n, 4) from t
order by n, d
you get:
N
D
MY_CEIL
MY_FLOOR
-1.23
0
-1
-2
-1.23
1
-1.2
-1.3
-1.23
2
-1.23
-1.23
-1.23
3
-1.23
-1.23
-1.23
4
-1.23
-1.23
0
0
0
0
0
1
0
0
0
2
0
0
0
3
0
0
0
4
0
0
4.512
0
5
4
4.512
1
4.6
4.5
4.512
2
4.52
4.51
4.512
3
4.512
4.512
4.512
4
4.512
4.512
5.12345
0
6
5
5.12345
1
5.2
5.1
5.12345
2
5.13
5.12
5.12345
3
5.124
5.123
5.12345
4
5.1235
5.1234
6
0
6
6
6
1
6
6
6
2
6
6
6
3
6
6
6
4
6
6
db<>fiddle
You might need to look at negative values to check they behave as you expect/want, and adjust the functions to mimic round if necessary. You also said the decimal might be zero, 1 or more; if that could be negative then it will need more work...
CEIL and FLOOR return the closest integer and don't accept any arguments (but the number itself).
So, if you want to do that up to the 1st decimal, first multiply the number by 10, CEIL/FLOOR that value, and then divide it by 10.
Something like this:
SQL> with test (col) as (select 4.521 from dual)
2 select col,
3 --
4 ceil(col * 10) / 10 c_ceil,
5 floor(col * 10) / 10 c_floor
6 from test;
COL C_CEIL C_FLOOR
---------- ---------- ----------
4,521 4,6 4,5
SQL>

Updating row, according to Rownum in Oracle

I have two tables in Oracle
TableProducts
Product_Code, and 20 others fields
BGU
LSO
MPA
MPA4
MPA5
TPA
UGU
For this example, now I have 7 values, but maybe 9 values later.
CREATE TABLE TableContacts AS SELECT *
FROM Contacts
WHERE Rownum <= (4*(SELECT Count(Distinct Product_Code) FROM TableProducts));
Now I have 28 Rows in my TableContacts.
Now I need To UPDATE the rows in order to create combinations test.
TableContacts
Product_Code, Email, PDF, and 17 others fields.
Email and PDF has two possible values 'N' or 'Y'.
I need to fill the TableContacts with the combinations of Product_Code, Email and PDF fields, according to Rownum position.
Rownum = 1 -> Product_Code='BGU', Email='N', PDF='N'
Rownum = 2 -> Product_Code='BGU', Email='N', PDF='Y'
Rownum = 3 -> Product_Code='BGU', Email='Y', PDF='N'
Rownum = 4 -> Product_Code='BGU', Email='Y', PDF='Y'
Rownum = 5 -> Product_Code='LSO', Email='N', PDF='N'
If I have 7 values for Product_Code, 2 by Email and 2 by PDF, then I will need to fill (7 * 2 *2) = 28 Rows.
How to create and SQL for this situation updating TableContacts?
Partially, requirement doesn't make sense. Rows in a table within the relational databases aren't sorted in any way, so - saying that you want to refer to a rownum is ... strange. That's why I modified the contacts table and added yet another column - rn - which shows that rownum of yours.
Also, this example shows only 3 products (didn't feel like typing all of them). Code that follows doesn't care about number of those products and will work the same regardless.
Products:
SQL> select * From products;
CODE
-----
BGU
LSO
MPA
Insert into Contacts:
SQL> insert into contacts (rn, code, email, pdf)
2 with temp as
3 (select p.code, x.lvl
4 from products p cross join (Select level lvl from dual connect by level <= 4) x
5 )
6 select t.lvl,
7 t.code,
8 case when t.lvl in (1, 2) then 'N'
9 when t.lvl in (3, 4) then 'Y'
10 end email,
11 --
12 case when t.lvl in (1, 3) then 'N'
13 when t.lvl in (2, 4) then 'Y'
14 end pdf
15 from temp t;
12 rows created.
Result:
SQL> select * From contacts
2 order by code, rn;
RN CODE E P
---------- ----- - -
1 BGU N N
2 BGU N Y
3 BGU Y N
4 BGU Y Y
1 LSO N N
2 LSO N Y
3 LSO Y N
4 LSO Y Y
1 MPA N N
2 MPA N Y
3 MPA Y N
4 MPA Y Y
12 rows selected.
SQL>
[EDIT: how to update table that contains rows?]
If I understood you correctly, this is what you initially have in the CONTACTS table:
SQL> select code, rownum from contacts;
CODE ROWNUM
----- ----------
BGU 1
LSO 2
MPA 3
BGU 4
LSO 5
MPA 6
BGU 7
LSO 8
MPA 9
BGU 10
LSO 11
MPA 12
12 rows selected.
SQL>
As I previously said: rownum is irrelevant here, it can change, you can't tell which rownum belongs to which code.
Anyway, such an update (merge, actually) does the job:
SQL> merge into contacts a
2 using (select c.code,
3 c.rowid,
4 row_number() over (partition by c.code order by null) rn
5 from contacts c
6 ) x
7 on (a.rowid = x.rowid)
8 when matched then update set
9 a.email = case when x.rn in (1, 2) then 'N'
10 when x.rn in (3, 4) then 'Y'
11 end,
12 a.pdf = case when x.rn in (1, 3) then 'N'
13 when x.rn in (2, 4) then 'Y'
14 end;
12 rows merged.
SQL> select * From contacts order by code, email, pdf;
CODE EMAIL PDF
----- ----- -----
BGU N N
BGU N Y
BGU Y N
BGU Y Y
LSO N N
LSO N Y
LSO Y N
LSO Y Y
MPA N N
MPA N Y
MPA Y N
MPA Y Y
12 rows selected.
SQL>

How can I query to get the rows according to the certain column's value in oracle? [duplicate]

This question already has an answer here:
how to duplicate my sql results? [duplicate]
(1 answer)
Closed 2 years ago.
Table A is:
--------------
C1 C2
--------------
A 3
B 2
--------------
select * from
(
select 'A' as C1, 3 as C2 from dual
union all
select 'B' as C1, 2 as C2 from dual
)
I want to get the following result view with one query statement:
--------------
C1 N1
--------------
A 1
A 2
A 3
B 1
B 2
--------------
I need to generate rows as many as C2 value
Is this possible?
Thank you.
We can handle this via the use of a calendar/sequence table. Consider:
WITH nums AS (
SELECT 1 AS val FROM dual UNION ALL
SELECT 2 FROM dual UNION ALL
SELECT 3 FROM dual
)
SELECT
a.C1,
n.val AS N1
FROM TableA a
INNER JOIN nums n
ON n.val <= a.C2
ORDER BY
a.C1,
n.val;
Demo
Note that in practice, you might use a dedicated table containing a sequence of numbers to cover all possible values in your table. Or, you might use an Oracle sequence.
Alternatively:
SQL> with test as
2 (select 'A' as C1, 3 as C2 from dual
3 union all
4 select 'B' as C1, 2 as C2 from dual
5 )
6 select c1, column_value n1
7 from test cross join table(cast(multiset(select level from dual
8 connect by level <= c2
9 ) as sys.odcinumberlist))
10 order by c1, column_value;
C N1
- ----------
A 1
A 2
A 3
B 1
B 2
SQL>

custom round logic off in SQL

Iwant to round of the value upto 2 decimal point when third decimal digit is greater then 5:
39.956 should be round off to 39.96,
35.665 should be round off to 35.66 ,
39.997 should be round off to 40.00 ,
56.684 should be round off to 56.68.
I am trying to do below
SELECT CAST(FLOOR(VALUE) AS VARCHAR2(30))
+ CASE
WHEN CAST(SUBSTR(SUBSTR(VALUE, INSTR(VALUE, '.')), 4) AS INT) > 5
THEN
CONCAT(
'.',
( SUBSTR(
SUBSTR(VALUE, INSTR(VALUE, '.')),
2,
2
)
+ 1)
)
ELSE
CONCAT(
'.',
SUBSTR(
SUBSTR(VALUE, INSTR(VALUE, '.')),
2,
2
)
)
END
FROM DUAL;
but for the border cases, for example 39.897 and 39.997 it is not working.
Maybe you simply need this:
SQL> with test(num) as (
2 select 39.956 from dual union all
3 select 35.665 from dual union all
4 select 39.997 from dual union all
5 select 56.684 from dual
6 )
7 select num, round(num -0.001, 2)
8 from test;
NUM ROUND(NUM-0.001,2)
---------- ------------------
39,956 39,96
35,665 35,66
39,997 40
56,684 56,68
Aleksej's solution will work fine and is probably the most efficient if it is known beforehand that the input numbers have at most three decimal places.
The problem can be generalized though, like so: round 38.445 down to 38.44; however, round 38.44503 to 38.45. (That is, if there are non-zero digits after the "5" in the third decimal position, then round up.)
Something like the query below can be used in the general case. The only time the result is different from "usual" rounding is when the input number has exactly three non-zero decimal places, and the third decimal place is 5. This is exactly how the solution reads.
with inp (n) as (select 38.445 from dual union all select 38.44503 from dual)
select n,
round(n,2) - case when n = round(n, 3) and mod(1000*n, 10) = 5
then 0.01
else 0 end as custom_rounded
from inp;
N CUSTOM_ROUNDED
---------- --------------
38.445 38.44
38.44503 38.45

Select the sum of occurances of first alphabetical character

Hi what i need to do is create a select statement which outputs the sum of the first character in a field within the table so the output would look something like
A,12
B,0
C,20
D,14
E,0
ect...
The table is called contacts, in the above there was 12 occurrences of people whose names begin with the letter A
I hope i have explained this correctly
Let's understand this with EMP table example.
SQL> with
2 letters
3 as
4 (select chr( ascii('A')+level-1 ) letter
5 from dual
6 connect by level <= 26
7 )
8 SELECT substr(ename, 1, 1) AS init_name,
9 count(*) cnt
10 FROM emp
11 WHERE substr(ename, 1, 1) IN (SELECT letter from letters)
12 GROUP BY substr(ename, 1, 1)
13 UNION
14 SELECT l.letter AS init_name,
15 0 cnt
16 FROM letters l
17 WHERE l.letter NOT IN (SELECT substr(ename, 1, 1) FROM emp)
18 ORDER BY init_name
19 /
I CNT
- ----------
A 2
B 1
C 1
D 0
E 0
F 1
G 0
H 0
I 0
J 2
K 1
L 0
M 2
N 0
O 0
P 0
Q 0
R 0
S 2
T 1
U 0
V 0
W 1
X 0
Y 0
Z 0
26 rows selected.
SQL>
So, it gives the count of each letter of first name, and for the other letters which does not exist in the first name, the count is 0.
Generate the 26 letters using connect then left join to the first letter of the name and count them:
select letter, count(name) count
from (select chr(ascii('A')+level-1) letter from dual connect by level < 27) l
left join emp on substr(name, 1, 1) = letter
group by letter order by 1
See SQLFiddle
Attribution: My technique of generating letters uses elements of Lalit's answer.

Resources