Oracle SUM calculated values - oracle

I'm trying to figure out how I can or if it's possible to sum the returned values from columns on the fly to have a total value returned for each record instead of having to calculate it again.
The query has multiple sub-queries which all calculate correctly and return an integer value, I'm trying to find a way to add these values on the fly into a Total column, is it even possible!?
SELECT (SELECT value FROM ....) AS NUM_1
, (SELECT value FROM ....) AS NUM_2
, (SELECT value FROM ....) AS NUM_3
, SUM(NUM_1 + NUM_2 + NUM_3) AS TOTAL
FROM DUAL;
This is the output I'm looking to achieve:
NUM_1 | NUM_2 | NUM_3 | Total
=============================
3 | 4 | 3 | 10

with numbers as (
SELECT (SELECT value FROM ....) AS NUM_1,
(SELECT value FROM ....) AS NUM_2,
(SELECT value FROM ....) AS NUM_3
FROM DUAL;
)
select num_1,
num_2,
num_3,
num_1 + num_2 + num_3 as total
from numbers;
You have to make sure that your (SELECT value FROM ....) queries return exactly one row otherwise you'll get an error like "Single row sub-query returns multiple rows".

Remove semi colon (;) after dual and then run as whole it will work
with numbers as (
SELECT (SELECT value FROM ....) AS NUM_1,
(SELECT value FROM ....) AS NUM_2,
(SELECT value FROM ....) AS NUM_3
FROM DUAL
)
select num_1,
num_2,
num_3,
num_1 + num_2 + num_3 as total
from numbers;

Related

How does the recursive WITH query work in oracle? When does it go into a cycle?

I have a scenario where I have to display a row 'n' number of times depending on the value in its quantity column.
Item Qty
abc 2
cde 1
Item Qty
abc 1
abc 1
cde 1
I am looking to convert the first table to the second.
I came across the site that I should be using the recursive WITH query.
My anchor member returns the original table.
SELECT ITEM, QTY
FROM lines
WHERE
JOB = TO_NUMBER ('1')
AND ITEM IN
(SELECT PART
FROM PICK
WHERE DELIVERY = '2')
My recursive member is as follows.
SELECT CTE.ITEM, (CTE.QTY - 1) QTY
FROM CTE
INNER JOIN
(SELECT ITEM, QTY
FROM LINES
WHERE JOB_ID = TO_NUMBER ('1')
AND ITEM IN
(SELECT PART
FROM PICK
WHERE DELIVERY = '2'
)) T
ON CTE.ITEM = T.ITEM
WHERE CTE.QTY > 1
My goal is to get all the parts and quantities first then and then for all parts with qty > 1 in the recursive step generate new rows to be added to the original result set and qty displayed in the new rows would be (original qty for that part - 1). The recursion would go on until qty becomes 1 for all the parts.
So this is what I had in the end.
WITH CTE (ITEM, QTY)
AS (
SELECT ITEM, QTY
FROM lines
WHERE
JOB = TO_NUMBER ('1')
AND ITEM IN
(SELECT PART
FROM PICK
WHERE DELIVERY = '2')
UNION ALL
SELECT CTE.ITEM, (CTE.QTY - 1) QTY
FROM CTE
INNER JOIN
(SELECT ITEM, QTY
FROM LINES
WHERE JOB_ID = TO_NUMBER ('1')
AND ITEM IN
(SELECT PART
FROM PICK
WHERE DELIVERY = '2'
)) T
ON CTE.ITEM = T.ITEM
WHERE CTE.QTY > 1)
SELECT ITEM, QTY
FROM CTE
ORDER BY 1, 2 DESC
I get the following error when I try the above
"ORA-32044: cycle detected while executing recursive WITH query"
How is it getting into a cycle? What did I miss in its working?
Also, Upon reading from another website If I used a "cycle clause". I was able to stop the cycle.
The clause I used was.
CYCLE
QUANTITY
SET
END TO '1'
DEFAULT '0'
If I used this before the select statement. I'm getting the desired output but I don't feel this is the right way of going about it. What exactly is the clause doing? What is the right way of using it?
Oracle Setup:
CREATE TABLE lines ( Item, Qty ) AS
SELECT 'abc', 2 FROM DUAL UNION ALL
SELECT 'cde', 1 FROM DUAL;
CREATE TABLE pick ( part, delivery ) AS
SELECT 'abc', 2 FROM DUAL UNION ALL
SELECT 'cde', 2 FROM DUAL;
Query 1: Using a hierarchical query:
SELECT Item,
COLUMN_VALUE AS qty
FROM lines l
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT 1
FROM DUAL
CONNECT BY LEVEL <= l.Qty
)
AS SYS.ODCINUMBERLIST
)
) t
WHERE item IN ( SELECT part FROM pick WHERE delivery = 2 )
Query 2: Using a recursive sub-query factoring clause:
WITH rsqfc ( item, qty ) AS (
SELECT item, qty
FROM lines l
WHERE item IN ( SELECT part FROM pick WHERE delivery = 2 )
UNION ALL
SELECT item, qty - 1
FROM rsqfc
WHERE qty > 1
)
SELECT item, 1 AS qty
FROM rsqfc;
Output:
ITEM | QTY
:--- | --:
abc | 1
abc | 1
cde | 1
db<>fiddle here

Removing column duplicate string values and extract the maximum string value for the coulm value

I want to populate temp2 with max tot_duration and count from temp1. I do it using the following code:
insert into analytics_db.charith_prepaid_bcell_temp2
(charged_party,cell_id,tot_duration,tband_count)
select A.*
FROM analytics_db.charith_prepaid_bcell_temp1 A,
(SELECT A.charged_party,B.B,MAX(A.tot_duration) C-- MAX COUNT FOR MAX TIMA BAND CELL
FROM analytics_db.charith_prepaid_bcell_temp1 A,
(SELECT charged_party,MAX(tband_count) B ---- MAX TIME BAND COUNT
FROM analytics_db.charith_prepaid_bcell_temp1
WHERE NVL(tot_duration,0) >0
GROUP BY charged_party) B
WHERE NVL(tot_duration,0) >0 AND A.charged_party = B.charged_party AND A.tband_count = B.B
GROUP BY A.charged_party,B.B) C
WHERE A.charged_party = C.charged_party AND A.tot_duration= C.C AND A.tband_count = C.B
But there are duplicate entries in the temp2 where there are different cell id with same tot_duration and tband_count for the same charged_party. How can I fix the code to only get the entry where there is only one entry with the maximum cell_id?
Thank you
This can be done with ranking functions like row_number.
select charged_party,cell_id,tot_duration,tband_count
from (select t.*
,row_number() over(partition by tot_duration,tband_count order by cell_id desc) as rnum
from tbl t
) t
where rnum = 1

PL/SQL: Need help creating calculated columns based on conditions. to be done in select query

I am trying to count all the distinct ids based on conditions. But I am unable to figure out where I am going wrong with the syntax. The logic is
COUNTD(IF ([column_name1] = 1) THEN [DATAPAGEID] END)
This is the formula I used in Tableau. However when writing it in a PL/SQL query as
Select FT.NAME, COUNT(DISTINCT FT.pageID IF FT."column_name" = 1 )
as total_expected
FROM
( Sub Query) FT
Group by FT.Name
Order by FT.Name
Needless to say its throwing errors. Now I can write separate queries which can give me each number using a where condition. For example, if I wanted a count of distinct pageid where column_name1 = 1, I would write something like this
Select FT.SITENAME, COUNT(DISTINCT DATAPAGEID) as Datapage
from
(sub query)
WHERE FT."column_name" = 1
but the problem with that is that I have other calculated columns in the query which will all need to be part of the same row. To illustrate here's what the table would look like
name Calculated_Column1 Calculated_Column2 Calculated_column3
abc 781 811 96.54%
pqr 600 800 75.00%
where calculated_column3 is the result of 781/811. Therefore I can't have a new query for each column. I thought using an if condition when calculating columns will solve this, but I can't get the syntax right somehow.
Therefore, I need to know how can I create conditional calculated columns within the select query. If I have not explained this well, please let me know and I will try to clarify further.
You can use a CASE block inside the count (DISTINCT ) as shown.
SELECT FT.NAME,
COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 1
THEN 1
ELSE 0
END ) Calculated_Column1,
COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 2
THEN 1
ELSE 0
END ) Calculated_Column2,
( COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 1
THEN 1
ELSE 0
END ) / COUNT(DISTINCT
CASE
WHEN DATAPAGEID = 2
THEN 1
ELSE 0
END ) ) * 100||'%' Calculated_Column3
FROM
( SELECT 'abc' name, 1 DATAPAGEID FROM dual
UNION ALL
SELECT 'abc' name, 1 DATAPAGEID FROM dual
UNION ALL
SELECT 'pqr' name, 2 DATAPAGEID FROM dual
UNION ALL
SELECT 'pqr' name, 2 DATAPAGEID FROM dual
UNION ALL
SELECT 'pqr' name, 3 DATAPAGEID FROM dual
) FT
GROUP BY FT.Name
ORDER BY FT.Name;
Output is
abc 1 1 100%
pqr 1 2 50%

How to compare items in an array to those in a database column using regular expressions?

I'm trying to take a list of elements in an array like this:
['GRADE', 'GRATE', 'GRAPE', /*About 1000 other entries here ...*/ ]
and match them to their occurrences in a column in an Oracle database full of entries like this:
1|'ANTERIOR'
2|'ANTEROGRADE'
3|'INGRATE'
4|'RETROGRADE'
5|'REIGN'
...|...
/*About 1,000,000 other entries here*/
For each entry in that array of G words, I'd like to loop through the word column of the Oracle database and try to find the right-sided matches for each entry in the array. In this example, entries 2, 3, and 4 in the database would all match.
In any other programming language, it would look something like this:
for entry in array:
for each in column:
if entry.right_match(each):
print entry
How do I do this in PL/SQL?
In PL/SQL it can be done in this way:
declare
SUBTYPE my_varchar2_t IS varchar2( 100 );
TYPE Roster IS TABLE OF my_varchar2_t;
names Roster := Roster( 'GRADE', 'GRATE', 'GRAPE');
begin
FOR c IN ( SELECT id, name FROM my_table )
LOOP
FOR i IN names.FIRST .. names.LAST LOOP
IF regexp_like( c.name, names( i ) ) THEN
DBMS_OUTPUT.PUT_LINE( c.id || ' ' || c.name );
END IF;
END LOOP;
END LOOP;
end;
/
but this is row by row processing, for large table it would be very slow.
I think it might be better to do it in a way shown below:
create table test123 as
select 1 id ,'ANTERIOR' name from dual union all
select 2,'ANTEROGRADE' from dual union all
select 3,'INGRATE' from dual union all
select 4,'RETROGRADE' from dual union all
select 5,'REIGN' from dual ;
create type my_table_typ is table of varchar2( 100 );
/
select *
from table( my_table_typ( 'GRADE', 'GRATE', 'GRAPE' )) x
join test123 y on regexp_like( y.name, x.column_value )
;
COLUMN_VALUE ID NAME
------------- ---------- -----------
GRADE 2 ANTEROGRADE
GRATE 3 INGRATE
GRADE 4 RETROGRADE

how to replace multiple strings together in Oracle

I have a string coming from a table like "can no pay{1},as your payment{2}due on {3}". I want to replace {1} with some value , {2} with some value and {3} with some value .
Is it Possible to replace all 3 in one replace function ? or is there any way I can directly write query and get replaced value ? I want to replace these strings in Oracle stored procedure the original string is coming from one of my table I am just doing select on that table
and then I want to replace {1},{2},{3} values from that string to the other value that I have from another table
Although it is not one call, you can nest the replace() calls:
SET mycol = replace( replace(mycol, '{1}', 'myoneval'), '{2}', mytwoval)
If there are many variables to replace and you have them in another table and if the number of variables is variable you can use a recursive CTE to replace them.
An example below. In table fg_rulez you put the strings with their replacement. In table fg_data you have your input strings.
set define off;
drop table fg_rulez
create table fg_rulez as
select 1 id,'<' symbol, 'less than' text from dual
union all select 2, '>', 'great than' from dual
union all select 3, '$', 'dollars' from dual
union all select 4, '&', 'and' from dual;
drop table fg_data;
create table fg_Data AS(
SELECT 'amount $ must be < 1 & > 2' str FROM dual
union all
SELECT 'John is > Peter & has many $' str FROM dual
union all
SELECT 'Eliana is < mary & do not has many $' str FROM dual
);
WITH q(str, id) as (
SELECT str, 0 id
FROM fg_Data
UNION ALL
SELECT replace(q.str,symbol,text), fg_rulez.id
FROM q
JOIN fg_rulez
ON q.id = fg_rulez.id - 1
)
SELECT str from q where id = (select max(id) from fg_rulez);
So, a single replace.
Result:
amount dollars must be less than 1 and great than 2
John is great than Peter and has many dollars
Eliana is less than mary and do not has many dollars
The terminology symbol instead of variable comes from this duplicated question.
Oracle 11gR2
Let's write the same sample as a CTE only:
with fg_rulez as (
select 1 id,'<' symbol, 'less than' text from dual
union all select 2, '>', 'greater than' from dual
union all select 3, '$', 'dollars' from dual
union all select 4, '+', 'and' from dual
), fg_Data AS (
SELECT 'amount $ must be < 1 + > 2' str FROM dual
union all
SELECT 'John is > Peter + has many $' str FROM dual
union all
SELECT 'Eliana is < mary + do not has many $' str FROM dual
), q(str, id) as (
SELECT str, 0 id
FROM fg_Data
UNION ALL
SELECT replace(q.str,symbol,text), fg_rulez.id
FROM q
JOIN fg_rulez
ON q.id = fg_rulez.id - 1
)
SELECT str from q where id = (select max(id) from fg_rulez);
If the number of values to replace is too big or you need to be able to easily maintain it, you could also split the string, use a dictionary table and finally aggregate the results
In the example below I'm assuming that the words in your string are separated with blankspaces and the wordcount in the string will not be bigger than 100 (pivot table cardinality)
with Dict as
(select '{1}' String, 'myfirstval' Repl from dual
union all
select '{2}' String, 'mysecondval' Repl from dual
union all
select '{3}' String, 'mythirdval' Repl from dual
union all
select '{Nth}' String, 'myNthval' Repl from dual
)
,MyStrings as
(select 'This is the first example {1} ' Str, 1 strnum from dual
union all
select 'In the Second example all values are shown {1} {2} {3} {Nth} ', 2 from dual
union all
select '{3} Is the value for the third', 3 from dual
union all
select '{Nth} Is the value for the Nth', 4 from dual
)
-- pivot is used to split the stings from MyStrings. We use a cartesian join for this
,pivot as (
Select Rownum Pnum
From dual
Connect By Rownum <= 100
)
-- StrtoRow is basically a cartesian join between MyStings and Pivot.
-- There as many rows as individual string elements in the Mystring Table
-- (Max = Numnber of rows Mystring table * 100).
,StrtoRow as
(
SELECT rownum rn
,ms.strnum
,REGEXP_SUBSTR (Str,'[^ ]+',1,pv.pnum) TXT
FROM MyStrings ms
,pivot pv
where REGEXP_SUBSTR (Str,'[^ ]+',1,pv.pnum) is not null
)
-- This is the main Select.
-- With the listagg function we group the string together in lines using the key strnum (group by)
-- The NVL gets the translations:
-- if there is a Repl (Replacement from the dict table) then provide it,
-- Otherwise TXT (string without translation)
Select Listagg(NVL(Repl,TXT),' ') within group (order by rn)
from
(
-- outher join between strings and the translations (not all strings have translations)
Select sr.TXT, d.Repl, sr.strnum, sr.rn
from StrtoRow sr
,dict d
where sr.TXT = d.String(+)
order by strnum, rn
) group by strnum
If you are doing this inside of a select, you can just piece it together, if your replacement values are columns, using string concatenation.

Resources