ORA-00947 not enough values with function returning table of records - oracle

So I'm trying to build a function that returns the records of items that are included in some client subscription.
So I've been building up the following:
2 types:
CREATE OR REPLACE TYPE PGM_ROW AS OBJECT
(
pID NUMBER(10),
pName VARCHAR2(300)
);
CREATE OR REPLACE TYPE PGM_TAB AS TABLE OF PGM_ROW;
1 function:
CREATE OR REPLACE FUNCTION FLOGIN (USER_ID NUMBER) RETURN PGM_TAB
AS
SELECTED_PGM PGM_TAB;
BEGIN
FOR RESTRICTION
IN ( SELECT (SELECT LISTAGG (ID_CHANNEL, ',')
WITHIN GROUP (ORDER BY ID_CHANNEL)
FROM (SELECT DISTINCT CHA2.ID_CHANNEL
FROM CHANNELS_ACCESSES CHA2
JOIN CHANNELS CH2
ON CH2.ID = CHA2.ID_CHANNEL
WHERE CHA2.ID_ACCESS = CMPA.ID_ACCESS
AND CH2.ID_CHANNELS_GROUP = CG.ID))
AS channels,
(SELECT LISTAGG (ID_SUBGENRE, ',')
WITHIN GROUP (ORDER BY ID_SUBGENRE)
FROM (SELECT DISTINCT SGA2.ID_SUBGENRE
FROM SUBGENRES_ACCESSES SGA2
JOIN CHANNELS_ACCESSES CHA2
ON CHA2.ID_ACCESS = SGA2.ID_ACCESS
JOIN CHANNELS CH2
ON CH2.ID = CHA2.ID_CHANNEL
WHERE SGA2.ID_ACCESS = CMPA.ID_ACCESS
AND CH2.ID_CHANNELS_GROUP = CG.ID))
AS subgenres,
CG.NAME,
A.BEGIN_DATE,
A.END_DATE,
CMP.PREVIEW_ACCESS
FROM USERS U
JOIN COMPANIES_ACCESSES CMPA
ON U.ID_COMPANY = CMPA.ID_COMPANY
JOIN COMPANIES CMP ON CMP.ID = CMPA.ID_COMPANY
JOIN ACCESSES A ON A.ID = CMPA.ID_ACCESS
JOIN CHANNELS_ACCESSES CHA
ON CHA.ID_ACCESS = CMPA.ID_ACCESS
JOIN SUBGENRES_ACCESSES SGA
ON SGA.ID_ACCESS = CMPA.ID_ACCESS
JOIN CHANNELS CH ON CH.ID = CHA.ID_CHANNEL
JOIN CHANNELS_GROUPS CG ON CG.ID = CH.ID_CHANNELS_GROUP
WHERE U.ID = USER_ID
GROUP BY CG.NAME,
A.BEGIN_DATE,
A.END_DATE,
CMPA.ID_ACCESS,
CG.ID,
CMP.PREVIEW_ACCESS)
LOOP
SELECT PFT.ID_PROGRAM, PFT.LOCAL_TITLE
BULK COLLECT INTO SELECTED_PGM
FROM PROGRAMS_FT PFT
WHERE PFT.ID_CHANNEL IN
( SELECT TO_NUMBER (
REGEXP_SUBSTR (RESTRICTION.CHANNELS,
'[^,]+',
1,
ROWNUM))
FROM DUAL
CONNECT BY LEVEL <=
TO_NUMBER (
REGEXP_COUNT (RESTRICTION.CHANNELS,
'[^,]+')))
AND PFT.ID_SUBGENRE IN
( SELECT TO_NUMBER (
REGEXP_SUBSTR (RESTRICTION.SUBGENRES,
'[^,]+',
1,
ROWNUM))
FROM DUAL
CONNECT BY LEVEL <=
TO_NUMBER (
REGEXP_COUNT (RESTRICTION.SUBGENRES,
'[^,]+')))
AND (PFT.LAUNCH_DATE BETWEEN RESTRICTION.BEGIN_DATE
AND RESTRICTION.END_DATE);
END LOOP;
RETURN SELECTED_PGM;
END FLOGIN;
I expect the function tu return a table with 2 columns containing all the records from table PROGRAMS_FT that are included in the user access.
For some reason, I'm getting compilation warning ORA-000947.
My understanding of the error code is that it occurs when the values inserted does not match the type of the object receiving the values, and I can't see how this can be the case here.

You're selecting two scalar values and trying to put them into an object. That doesn't happen automatically, you need to convert them to an object:
...
LOOP
SELECT PGM_ROW(PFT.ID_PROGRAM, PFT.LOCAL_TITLE)
BULK COLLECT INTO SELECTED_PGM
FROM PROGRAMS_FT PFT
...
(It's an unhelpful quirk of PL/SQL that it says 'not enough values' rather than 'too many values', as you might expect when you try to put two things into one; I'm sure I came up with a fairly convincing explanation/excuse for that once but it escapes me at the moment...)
I'm not sure your loop makes sense though. Assuming your cursor query returns multiple rows, each time around the loop you're replacing the contents of the SELECTED_PGM collection - you might think you are appending to it, but that's not how it works. So you will end up returning a collection based only on the final iteration of the loop.
Aggregating and then splitting the data seems like a lot of work too. You could maybe use collections for those; but you can probably get rid of the cursor and loop and combine the cursor query with the inner query, which would be more efficient and would allow you to do a single bulk-collect for all the combined data.

Related

ORA-01722: invalid number while passing value from inner select query to the top select query

FOR the ISBN['9780495809135'] if CATEGORY_EXISTS column return as 1234,3454 then query is throwing below error.if it returns single row then its not throwing error.
I want to write in the topmost query say if CATEGORY_EXISTS ='Category Not Found' then FILE_NAME column then should display as 'files not found' otherwise pass the CATEGORY_EXISTS values with comma separated to top most most query.
Please note that this is just pseduo query,in the actual query lot of other tables and joins are there,
ORA-01722: invalid number
01722. 00000 - "invalid number"
*Cause: The specified number was invalid.
*Action: Specify a valid number.
SELECT ISBN ,
(SELECT LISTAGG(ANP.FILE_NAME, ',') WITHIN GROUP (
ORDER BY ANP.FILE_NAME)
FROM TABLE1 T
WHERE T.NODE_ID IN( CATEGORY_EXISTS)
)FILE_NAME
FROM
(SELECT ISBN,
(SELECT (
CASE
WHEN COUNT(DISTINCT AN.ID) > 0
THEN LISTAGG(AN.ID, ',') WITHIN GROUP (
ORDER BY AN.ID)
ELSE 'Category Not Found'
END )
FROM TABLE1 aca
JOIN TABLE2 AN
ON ACA.CHILD_NODE_ID=AN.ID
WHERE PARENT_NODE_ID=GT_CHILD_NODE_ID
) CATEGORY_EXISTS
FROM
(SELECT ISBN,
(SELECT ID FROM TEMP_CHILD_ASSOC ac WHERE CHILD_NODE_NAME=GT.ISBN
) GT_CHILD_NODE_ID
FROM MAIN_TABLE GT
WHERE ISBN='9780495809135'
)
);
The listagg() function generates a string of comma-separated values (if there is more than one ID). The case expression gives you either that generated string, of the fixed text literal (if there are no IDs). You are then trying to compare that string to a number; effectively one of these:
WHERE T.NODE_ID IN ('4321')
WHERE T.NODE_ID IN ('1234,3454')
WHERE T.NODE_ID IN ('Category Not Found')
You are implicitly converting the string to a number to compare it with NODE_ID. The first one will work as the implicit conversion is valid. The second will give you ORA-01722 (unless you have exactly two values, and your NLS decimal separator is a comma; but still won't give a match), and the third will also give that error - because those strings cannot be converted to numbers.
It's possible you are expecting the second one to be magically treated as two numbers inside the IN() clause, but that isn't how it works; it's getting a single string literal, not an actual list of numbers it can understand.
The IN condition does accept a list of multiple comma-separated expressions, but you are passing in a single string. The fact that string happens to consist of comma-separated values is irrelevant: it is itself still just a single expression. And that cannot be converted implicitly to a number.
If you have, or can create, a schema-level table type like:
create type my_number_tab as table of number
/
then you could use the collect() function to convert the IDs into a collection instead of a string, and then use member of to find matches; something like (with a bit of interpretation of your pseudocode):
SELECT ISBN ,
(SELECT LISTAGG(ANP.FILE_NAME, ',') WITHIN GROUP (
ORDER BY ANP.FILE_NAME)
FROM TABLE3 ANP
WHERE ANP.NODE_ID MEMBER OF CATEGORIES -- use collection
)FILE_NAME
FROM
(SELECT ISBN,
(SELECT CAST(COLLECT(AN.ID) AS my_number_tab) -- create collection not string
FROM TABLE1 aca
JOIN TABLE2 AN
ON ACA.CHILD_NODE_ID=AN.ID
WHERE PARENT_NODE_ID=GT_CHILD_NODE_ID
) CATEGORIES
FROM
(SELECT ISBN,
(SELECT ID FROM TEMP_CHILD_ASSOC ac WHERE CHILD_NODE_NAME=GT.ISBN
) GT_CHILD_NODE_ID
FROM MAIN_TABLE GT
WHERE ISBN='9780495809135'
)
);
It looks like you could also join to anp inside the inner query instead, so in that you generate the string list of file names rather than (or as well as) the string list of IDs. It's hard to tell from the pseudocode though; but perhaps something like:
SELECT ISBN,
(SELECT (
CASE
WHEN COUNT(DISTINCT AN.ID) > 0
THEN LISTAGG(ANP.FILE_NAME, ',') WITHIN GROUP (
ORDER BY ANP.FILE_NAME)
ELSE 'Category Not Found'
END )
FROM TABLE1 aca
JOIN TABLE2 AN
ON ACA.CHILD_NODE_ID=AN.ID
JOIN TABLE3 ANP
ON ANP.NODE_ID=AN.ID
WHERE ACA.PARENT_NODE_ID=GT_CHILD_NODE_ID
) FILE_NAME
FROM
(SELECT ISBN,
(SELECT ID FROM TEMP_CHILD_ASSOC ac WHERE CHILD_NODE_NAME=GT.ISBN
) GT_CHILD_NODE_ID
FROM MAIN_TABLE GT
WHERE ISBN='9780495809135'
);
You could probably also do the same thing with left outer joins (though perhaps they don't all need to be), although your comment suggests you have a reason for using subqueries instead:
SELECT GT.ISBN,
CASE WHEN COUNT(AN.ID) = 0 THEN 'files not found'
ELSE LISTAGG(ANP.FILE_NAME, ',') WITHIN GROUP (ORDER BY ANP.FILE_NAME)
END AS file_name
FROM MAIN_TABLE GT
LEFT JOIN TEMP_CHILD_ASSOC ac ON CHILD_NODE_NAME=GT.ISBN
LEFT JOIN table1 aca ON aca.parent_node_id = ac.id
LEFT JOIN table2 an on an.id = ACA.CHILD_NODE_ID
LEFT JOIN table3 anp on anp.node_id = an.id
WHERE GT.ISBN = '9780495809135'
GROUP BY GT.ISBN;
or something like that; again hard to tell from the pseudocode...

Replacing DBMS_OUPUT in a PL/SQL test LOOP to return a collection

So I have this assignment where I have to create a stored procedure to search for movies in an Oracle DataBase.
The search string follow the following logic :
It looks for the year of the movie in parenthesis
Ex. (1992)
It looks for a range of year in brackets
Ex. [1992,2000]
It looks for a word contained in the title, country, realisator, genre, actors or scenarists.
Any of the above can be combined multiple times.
Ex. : The Lord Of the Ring Ian McKellen Christopher Lee [1992,2000]
The logic used to solve this problem was to make a giant query to group all the required data then use a cursor to loop through the result set with a cursor to check if every word of the search string is valid.
I managed to make a procedure that works as expected, but the only way I found to return the results was to use DBMS_OUTPUT. Now the problem is that when I plug this is Hibernate the DBMS_OUTPUT is not sent over to the client. I've read some way to force the output by setting DBMS_OUTPUT.enable, but I feel that this is not the proper way to do it.
So here are my questions :
Is my logic flawed? Is there a simpler way to archive this with a single select or something?
Is there a way to dynamically push data inside a cursor and return it?
Am I really supposed to trick the DBMS_OUTPUT so it is sent over to hibernate?
Here's my code :
CREATE OR REPLACE PROCEDURE p_SearchFilm(searchString IN VARCHAR2) IS
IsValid BOOLEAN;
y1 INTEGER;
y2 INTEGER;
subStrArray apex_application_global.vc_arr2;
term VARCHAR(100);
CURSOR films IS
Select FilmId, Titre, real.Prenom||' '||real.nom as Realisateur, anneeSortie, ListPays, ListGenres,
ListScenaristes, ListActeurs, langueOrigine
from Film
natural left join
(select FilmId, listagg(p.Nom, ',') within group (Order By p.nom) ListPays from Film
natural join Film_vs_pays
natural join Pays p
Group by FilmId)
natural left join
(select FilmId, listagg(g.Nom, ',') within group (Order By g.nom) ListGenres from Film
natural join Film_vs_Genre
natural join Genre g
Group by FilmId)
natural left join
(select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListScenaristes from Film
natural join Scenariste s
join Personne p on s.personneId = p.personneId
Group by FilmId)
natural left join
(select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListActeurs from Film
natural join Personnage perso
join Personne p on perso.personneId = p.personneId
Group by FilmId)
left join Personne real on real.personneId = realisateurId;
BEGIN
<<FILM_LOOP>>
FOR film IN films LOOP
subStrArray := apex_util.string_to_table(searchString, ' ');
FOR i in 1..subStrArray.count LOOP
IsValid:= FALSE;
term:= subStrArray(i);
IF REGEXP_LIKE(term, '\(\d{4}\)') THEN
IF film.anneeSortie = TO_NUMBER(regexp_substr(term, '\d{4}')) THEN
IsValid:= TRUE;
END IF;
ELSIF REGEXP_LIKE(term, '\[\d{4},\d{4}\]') THEN
y1:= regexp_substr(term, '\d{4}', 1, 1);
y2:= regexp_substr(term, '\d{4}', 1, 2);
IF film.anneeSortie BETWEEN y1 AND y2 THEN
IsValid:= TRUE;
END IF;
ELSE
IF UPPER(film.Titre||film.Realisateur||film.ListActeurs||film.ListScenaristes||film.ListGenres||film.ListPays||film.langueOrigine)
LIKE '%'||UPPER(term)||'%' THEN
IsValid:= TRUE;
END IF;
END IF;
IF NOT IsValid THEN
CONTINUE FILM_LOOP;
END IF;
END LOOP;
DBMS_OUTPUT.put_line(film.FilmId||'|'||film.Titre);
END LOOP;
END;
A small disclaimer here :
I saw some similar questions that addressed this issue, but the ones that used cursors were returning a complete select, not hand picked rows.
The question about DBMS_OUTPUT and Hibernate stated that it should be avoided.
The questions using piped rows seamed to work only with functions (Changing the procedure for a function called by the procedure could be a valid work around, I'd like to know if something else is possible before tho).
The use of the DBMS_OUTPUT package is pretty much bounded to developer executions of anonymous blocks, and hence not suited for your intended communication with the Hibernate framework.
If you already have a stored procedure to apply your filter and determine your positive results, a solution could be populate a temporary table with those positives, and then return an open cursor which will only have the data from that temporary table, something like:
create global temporary table movie_results( movie varchar2(200) ) on commit preserve rows;
Of course your temp table can have more columns, but let me leave it like this, just to illustrate my solution.
CREATE OR REPLACE PROCEDURE p_SearchFilm(searchString IN VARCHAR2, movies out SYS_REFCURSOR) IS
IsValid BOOLEAN;
y1 INTEGER;
y2 INTEGER;
subStrArray apex_application_global.vc_arr2;
term VARCHAR(100);
CURSOR films IS
Select FilmId, Titre, real.Prenom||' '||real.nom as Realisateur, anneeSortie, ListPays, ListGenres,
ListScenaristes, ListActeurs, langueOrigine
from Film
natural left join
(select FilmId, listagg(p.Nom, ',') within group (Order By p.nom) ListPays from Film
natural join Film_vs_pays
natural join Pays p
Group by FilmId)
natural left join
(select FilmId, listagg(g.Nom, ',') within group (Order By g.nom) ListGenres from Film
natural join Film_vs_Genre
natural join Genre g
Group by FilmId)
natural left join
(select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListScenaristes from Film
natural join Scenariste s
join Personne p on s.personneId = p.personneId
Group by FilmId)
natural left join
(select FilmId, listagg(p.Prenom||' '||p.Nom, ',') within group (Order By p.nom) ListActeurs from Film
natural join Personnage perso
join Personne p on perso.personneId = p.personneId
Group by FilmId)
left join Personne real on real.personneId = realisateurId;
BEGIN
<<FILM_LOOP>>
FOR film IN films LOOP
subStrArray := apex_util.string_to_table(searchString, ' ');
FOR i in 1..subStrArray.count LOOP
IsValid:= FALSE;
term:= subStrArray(i);
IF REGEXP_LIKE(term, '\(\d{4}\)') THEN
IF film.anneeSortie = TO_NUMBER(regexp_substr(term, '\d{4}')) THEN
IsValid:= TRUE;
END IF;
ELSIF REGEXP_LIKE(term, '\[\d{4},\d{4}\]') THEN
y1:= regexp_substr(term, '\d{4}', 1, 1);
y2:= regexp_substr(term, '\d{4}', 1, 2);
IF film.anneeSortie BETWEEN y1 AND y2 THEN
IsValid:= TRUE;
END IF;
ELSE
IF UPPER(film.Titre||film.Realisateur||film.ListActeurs||film.ListScenaristes||film.ListGenres||film.ListPays||film.langueOrigine)
LIKE '%'||UPPER(term)||'%' THEN
IsValid:= TRUE;
END IF;
END IF;
IF NOT IsValid THEN
CONTINUE FILM_LOOP;
END IF;
END LOOP;
--DBMS_OUTPUT.put_line(film.FilmId||'|'||film.Titre);
insert into movie_results( movie )
values film.FilmId||'|'||film.Titre;
commit;
END LOOP;
open movies for
select *
from movie_results;
END;
Now your parameter 'movies' has all the positive results derived from your procedure, and all you'll have to do is read the cursor in Hibernate.
Note that, once you close your connection, the temporary table looses all the data and will be ready to use again when another session begins (always remember to close your Cursors/Connections).
The first part of this can probably be accomplished with just a query. You can define your search terms like this: How to declare variable and use it in the same SQL script? (Oracle SQL)
I'll leave the basic query writing to you (e.g. joining appropriate data), but your script will in effect look something like this:
DEFINE var_year1 = 1992;
DEFINE var_year2 = 1994;
DEFINE var_country = null;
DEFINE var_title = 'Lord Of The Rings';
DEFINE var_realisator = null;
DEFINE var_genre = null;
DEFINE var_actors = 'Ian';
DEFINE var_scenarists = 'Lee';
SELECT film_id, title, year
FROM ...
WHERE year BETWEEN &var_year1 AND &var_year2
AND UPPER(title) LIKE UPPER('%&var_title%')
AND UPPER(country) LIKE UPPER('%&var_country%')
AND UPPER(realisator) LIKE UPPER('%&var_realisator%')
AND UPPER(genre) LIKE UPPER('%&var_genre%')
AND UPPER(actors) LIKE UPPER('%&var_actors%')
AND UPPER(scenarists) LIKE UPPER('%&var_scenarists%');
/
The reason I only select film_id, title, and year is because the title and year will be distinct values to the film_id. There are a couple of ways to skin this cat, but it all revolves around using a LIKE statement with variables that you have defined. If you do not want to explicitly define the values in the script, you can use the ACCEPT command. Or, you can interface it with something else that applies those values into the variables.
With this approach, it was kind of a shot in the dark on how you really have your data laid out. But keep in mind good relational database management practices, and you should be able to turn this into a useable query.
Additional Method:
You can also use a series of subqueries to find the same data above using the like statements, then INNER JOIN them together to figure out which ones are in common. Example:
WITH sub_title AS (SELECT film_id FROM Film WHERE UPPER(title) LIKE UPPER('%&var_title%')),
...
sub_actor AS (SELECT film_id FROM (...) WHERE UPPER(actor) LIKE UPPER('%&var_actor%'))
SELECT film_id
FROM sub_title t
INNER JOIN sub_actor a ON a.film_id = t.film_id;
/
If you had searched for 'star war' in that, you would have returned for example
1 Star Wars A New Hope
2 Star Wars The Empire Strikes Back
3 Star Wars Return of the Jedi
Then in the second subquery if you had searched 'Donald Glover' you would have gotten
2 Star Wars The Empire Strikes Back
3 Star Wars Return of the Jedi
Then, the INNER JOIN would have given you ID numbers 2 and 3 because those were the only ones where all of the criteria were met. Repeat the process for your other search terms. I hope this helps.

Oracle Table Variables

Using Oracle PL/SQL is there a simple equivalent of the following set of T-SQL statements? It seems that everything I am finding is either hopelessly outdated or populates a table data type with no explanation on how to use the result other than writing values to stdout.
declare #tempSites table (siteid int)
insert into #tempSites select siteid from site where state = 'TX'
if 10 > (select COUNT(*) from #tempSites)
begin
insert into #tempSites select siteid from site where state = 'OK'
end
select * from #tempSites ts inner join site on site.siteId = ts.siteId
As #AlexPoole points out in his comment, this is a fairly contrived example.
What I am attempting to do is get all sites that meet a certain set of criteria, and if there are not enough matches, then I am looking to use a different set of criteria.
Oracle doesn't have local temporary tables, and global temporary tables don't look appropriate here.
You could use a common table expression (subquery factoring):
with tempSites (siteId) as (
select siteid
from site
where state = 'TX'
union all
select siteid
from site
where state = 'OK'
and (select count(*) from site where state = 'TX') < 10
)
select s.*
from tempSites ts
join site s on s.siteid = ts.siteid;
That isn't quite the same thing, but gets all the TX IDs, and only includes the OK ones if the count of TX ones - which has to be repeated - is less than 10. The CTE is then joined back to the original table, which all seems a bit wasteful; you're hitting the same table three times.
You could use a subquery directly in a filter instead:
select *
from site
where state = 'TX'
or (state = 'OK'
and (select count(*) from site where state = 'TX') < 10);
but again the TX sites have to be retrieved (or at least counted) a second time.
You can do this with a single hit of the table using an inline view (or CTE if you prefer) with an analytic count - which add the count of TX rows to the columns in the actual table, so you'd probably want to exclude that dummy column from the final result set (but using * is bad practice anyway):
select * -- but list columns, excluding tx_count
from (
select s.*,
count(case when state = 'TX' then state end) over (partition by null) as tx_count
from site s
where s.state in ('TX', 'OK')
)
where state = 'TX'
or (state = 'OK' and tx_count < 10);
From your description of your research it sounds like what you've been looking at involved PL/SQL code populating a collection, which you could still do, but it's probably overkill unless your real situation is much more complicated.

Does Oracle implicit conversion depend on joined tables or views

I've faced with a weird problem now. The query itself is huge so I'm not going to post it here (I could post however in case someone needs to see). Now I have a table ,TABLE1, with a CHAR(1) column, COL1. This table column is queried as part of my query. When I filter the recordset for this column I say:
WHERE TAB1.COL1=1
This way the query runs and returns a very big resultset. I've recently updated one of the subqueries to speed up the query. But after this when I write WHERE TAB1.COL1=1 it does not return anything, but if I change it to WHERE TAB1.COL1='1' it gives me the records I need. Notice the WHERE clause with quotes and w/o them. So to make it more clear, before updating one of the sub-queries I did not have to put quotes to check against COL1 value, but after updating I have to. What feature of Oracle is it that I'm not aware of?
EDIT: I'm posting the tw versions of the query in case someone might find it useful
Version 1:
SELECT p.ssn,
pss.pin,
pd.doc_number,
p.surname,
p.name,
p.patronymic,
to_number(p.sex, '9') as sex,
citiz_c.short_name citizenship,
p.birth_place,
p.birth_day as birth_date,
coun_c.short_name as country,
di.name as leg_city,
trim( pa.settlement
|| ' '
|| pa.street) AS leg_street,
pd.issue_date,
pd.issuing_body,
irs.irn,
irs.tpn,
irs.reg_office,
to_number(irs.insurer_type, '9') as insurer_type,
TO_CHAR(sa.REG_CODE)
||CONVERT_INT_TO_DOUBLE_LETTER(TO_NUMBER(SUBSTR(TO_CHAR(sa.DOSSIER_NR, '0999999'), 2, 3)))
||SUBSTR(TO_CHAR(sa.DOSSIER_NR, '0999999'), 5, 4) CONVERTED_SSN_DOSSIER_NR,
fa.snr
FROM
(SELECT pss_t.pin,
pss_t.ssn
FROM EHDIS_INSURANCE.pin_ssn_status pss_t
WHERE pss_t.difference_status < 5
) pss
INNER JOIN SSPF_CENTRE.file_archive fa
ON fa.ssn = pss.ssn
INNER JOIN SSPF_CENTRE.persons p
ON p.ssn = fa.ssn
INNER JOIN
(SELECT pd_2.ssn,
pd_2.type,
pd_2.series,
pd_2.doc_number,
pd_2.issue_date,
pd_2.issuing_body
FROM
--The changed subquery starts here
(SELECT ssn,
MIN(type) AS type
FROM SSPF_CENTRE.person_documents
GROUP BY ssn
) pd_1
INNER JOIN SSPF_CENTRE.person_documents pd_2
ON pd_2.type = pd_1.type
AND pd_2.ssn = pd_1.ssn
) pd
--The changed subquery ends here
ON pd.ssn = p.ssn
INNER JOIN SSPF_CENTRE.ssn_archive sa
ON p.ssn = sa.ssn
INNER JOIN SSPF_CENTRE.person_addresses pa
ON p.ssn = pa.ssn
INNER JOIN
(SELECT i_t.irn,
irs_t.ssn,
i_t.tpn,
i_t.reg_office,
(
CASE i_t.insurer_type
WHEN '4'
THEN '1'
ELSE i_t.insurer_type
END) AS insurer_type
FROM sspf_centre.irn_registered_ssn irs_t
INNER JOIN SSPF_CENTRE.insurers i_t
ON i_t.irn = irs_t.new_irn
OR i_t.old_irn = irs_t.old_irn
WHERE irs_t.is_registration IS NOT NULL
AND i_t.is_real IS NOT NULL
) irs ON irs.ssn = p.ssn
LEFT OUTER JOIN SSPF_CENTRE.districts di
ON di.code = pa.city
LEFT OUTER JOIN SSPF_CENTRE.countries citiz_c
ON p.citizenship = citiz_c.numeric_code
LEFT OUTER JOIN SSPF_CENTRE.countries coun_c
ON pa.country_code = coun_c.numeric_code
WHERE pa.address_flag = '1'--Here's the column value with quotes
AND fa.form_type = 'Q3';
And Version 2:
SELECT p.ssn,
pss.pin,
pd.doc_number,
p.surname,
p.name,
p.patronymic,
to_number(p.sex, '9') as sex,
citiz_c.short_name citizenship,
p.birth_place,
p.birth_day as birth_date,
coun_c.short_name as country,
di.name as leg_city,
trim( pa.settlement
|| ' '
|| pa.street) AS leg_street,
pd.issue_date,
pd.issuing_body,
irs.irn,
irs.tpn,
irs.reg_office,
to_number(irs.insurer_type, '9') as insurer_type,
TO_CHAR(sa.REG_CODE)
||CONVERT_INT_TO_DOUBLE_LETTER(TO_NUMBER(SUBSTR(TO_CHAR(sa.DOSSIER_NR, '0999999'), 2, 3)))
||SUBSTR(TO_CHAR(sa.DOSSIER_NR, '0999999'), 5, 4) CONVERTED_SSN_DOSSIER_NR,
fa.snr
FROM
(SELECT pss_t.pin,
pss_t.ssn
FROM EHDIS_INSURANCE.pin_ssn_status pss_t
WHERE pss_t.difference_status < 5
) pss
INNER JOIN SSPF_CENTRE.file_archive fa
ON fa.ssn = pss.ssn
INNER JOIN SSPF_CENTRE.persons p
ON p.ssn = fa.ssn
INNER JOIN
--The changed subquery starts here
(SELECT ssn,
type,
series,
doc_number,
issue_date,
issuing_body
FROM
(SELECT ssn,
type,
series,
doc_number,
issue_date,
issuing_body,
ROW_NUMBER() OVER (partition BY ssn order by type) rn
FROM SSPF_CENTRE.person_documents
)
WHERE rn = 1
) pd --
--The changed subquery ends here
ON pd.ssn = p.ssn
INNER JOIN SSPF_CENTRE.ssn_archive sa
ON p.ssn = sa.ssn
INNER JOIN SSPF_CENTRE.person_addresses pa
ON p.ssn = pa.ssn
INNER JOIN
(SELECT i_t.irn,
irs_t.ssn,
i_t.tpn,
i_t.reg_office,
(
CASE i_t.insurer_type
WHEN '4'
THEN '1'
ELSE i_t.insurer_type
END) AS insurer_type
FROM sspf_centre.irn_registered_ssn irs_t
INNER JOIN SSPF_CENTRE.insurers i_t
ON i_t.irn = irs_t.new_irn
OR i_t.old_irn = irs_t.old_irn
WHERE irs_t.is_registration IS NOT NULL
AND i_t.is_real IS NOT NULL
) irs ON irs.ssn = p.ssn
LEFT OUTER JOIN SSPF_CENTRE.districts di
ON di.code = pa.city
LEFT OUTER JOIN SSPF_CENTRE.countries citiz_c
ON p.citizenship = citiz_c.numeric_code
LEFT OUTER JOIN SSPF_CENTRE.countries coun_c
ON pa.country_code = coun_c.numeric_code
WHERE pa.address_flag = 1--Here's the column value without quotes
AND fa.form_type = 'Q3';
I've put separating comments for the changed subqueries and the WHERE clause in both queries. Both versions of the subqueries return the same result, one of them is just slower, which is why I decided to update it.
With the most simplistic example I can't reproduce your problem on 11.2.0.3.0 or 11.2.0.1.0.
SQL> create table tmp_test ( a char(1) );
Table created.
SQL> insert into tmp_test values ('1');
1 row created.
SQL> select *
2 from tmp_test
3 where a = 1;
A
-
1
If I then insert a non-numeric value into the table I can confirm Chris' comment "that Oracle will rewrite tab1.col1 = 1 to to_number(tab1.col1) = 1", which implies that you only have numeric characters in the column.
SQL> insert into tmp_test values ('a');
1 row created.
SQL> select *
2 from tmp_test
3 where a = 1;
ERROR:
ORA-01722: invalid number
no rows selected
If you're interested in tracking this down you should gradually reduce the complexity of the query until you have found a minimal, reproducible, example. Oracle can pre-compute a conversion to be used in a JOIN, which as your query is complex seems like a possible explanation of what's happening.
Oracle explicitly recommends against using implicit conversion so it's wiser not to use it at all; as you're finding out. For a start there's no guarantees that your indexes will be used correctly.
Oracle recommends that you specify explicit conversions, rather than rely on implicit or automatic conversions, for these reasons:
SQL statements are easier to understand when you use explicit data type conversion functions.
Implicit data type conversion can have a negative impact on performance, especially if the data type of a column value is converted to that of a constant rather than the other way around.
Implicit conversion depends on the context in which it occurs and may not work the same way in every case. For example, implicit conversion from a datetime value to a VARCHAR2 value may return an unexpected year depending on the value of the NLS_DATE_FORMAT
parameter.
Algorithms for implicit conversion are subject to change across software releases and among Oracle products. Behavior of explicit conversions is more predictable.
If you do only have numeric characters in the column I would highly recommend changing this to a NUMBER(1) column and I would always recommend explicit conversion to avoid a lot of pain in the longer run.
It's hard to tell without the actual query. What I would expect is that TAB1.COL1 is in some way different before and after the refactoring.
Candidates differences are Number vs. CHAR(1) vs. CHAR(x>1) vs VARCHAR2
It is easy to introduce differences like this with subqueries where you join two tables which have different types in the join column and you return different columns in your subquery.
To hunt that issue down you might want to check the exact datatypes of your query. Not sure how to do that right now .. but an idea would be to put it in a view and use sqlplus desc on it.

MERGE output cursor of SP into table?

i have a Stored Procedure which returns output as a ref cursor. I would like to store the output in another table using the MERGE statement. I'm having problems however mixing all the statements together (WITH, USING, MERGE etc..).
Can somebody assist? Thanks!
This is the table i want the output in (STOP_TIME is left out on purpose):
TABLE: **USER_ALLOCATION**
START_TIME date NOT NULL Primary Key
USER_ID number NOT NULL Primary Key
TASK_ID number NULL
This is the SP:
create or replace
PROCEDURE REPORT_PLAN_AV_USER
(
from_dt IN date,
to_dt IN date,
sysur_key IN number,
v_reservations OUT INTRANET_PKG.CURSOR_TYPE
)
IS
BEGIN
OPEN v_reservations FOR
with
MONTHS as (select FROM_DT + ((ROWNUM-1) / (24*2)) as DT from DUAL connect by ROWNUM <= ((TO_DT - FROM_DT) * 24*2) + 1),
TIMES as (select DT as START_TIME,(DT + 1/48) as STOP_TIME from MONTHS where TO_NUMBER(TO_CHAR(DT,'HH24')) between 8 and 15 and TO_NUMBER(TO_CHAR(DT,'D')) not in (1,7))
select
TIMES.START_TIME,
TIMES.STOP_TIME,
T.TASK_ID,
sysur_key USER_ID,
from
TIMES
left outer join (ALLOCATED_USER u INNER JOIN REQUIRED_RESOURCE r ON u.AU_ID = r.RR_ID INNER JOIN TASK t ON r.TASK_ID = t.TASK_ID)
ON u.USER_ID = sysur_key AND t.PLAN_TYPE = 3 AND TIMES.start_time >= TRUNC30(t.START_DATE) AND TIMES.start_time < TRUNC30(t.FINISH_DATE)
where u.USER_ID is null OR u.USER_ID = sysur_key
order by START_TIME ASC;
END;
I don't think you can resuse the cursor unless you write a lot of procedural code.
Can't you write a single merge statement and drop procedure REPORT_PLAN_AV_USER?
If you still need procedure REPORT_PLAN_AV_USE you can create a view that you use in procedure REPORT_PLAN_AV_USER and in your merge statement (to prevent code duplication).

Resources