Stored procedure - sql

I have 4 tables to be used in the procedure
business(abnnumber,name)
business_industry(abnnumber,industryid)
industry(industryid,unionid)
trade_union(unionid)
I was assigned to get trade union title in one line and all the businesses ABNNUMBER and business name in different lines using stored procedure.
What I tried is:
CREATE [OR REPLACE] PROCEDURE INDUSTRY_INFORMATION
[enter image description here][1](P_INDUSTRYID in integer,
P_UNIONTITLE OUT VARCHAR2,
P_BUSINESSNAME OUT VARCHAR2) AS
BEGIN
SELECT TRADE_UNION.UNIONTITLE, BUSINESS.BUSINESSNAME INTO
P_UNIONTITLE,P_BUSINESSNAME
FROM BUSINESS inner join BUSINESS_INDUSTRY ON
BUSINESS.ABNNUMBER=BUSINESS_INDUSTRY.ABNNUMBER
INNER JOIN INDUSTRY ON BUSINESS_INDUSTRY.INDUSTRYID=INDUSTRY.INDUSTRYID
INNER JOIN TRADE_UNION ON INDUSTRY.UNIONID=TRADE_UNION.UNIONID;
END;
Sample data is in the link http://www.mediafire.com/file/8c4dwn4n88n8a42/strd_procedure.txt
Required output should be
UNIONTITLE (one line)
ABNNUMBER BUSINESS NAME (next line)
`` [1]: https://i.stack.imgur.com/sGuwe.jpg

I suspect that You need something like this:
create or replace procedure industry_info is
begin
for r in (
select tu.uniontitle ut,
listagg('['||b.abnnumber||'] '||b.businessname, ', ')
within group (order by b.businessname) blist
from business b
join business_industry bi on b.abnnumber = bi.abnnumber
join industry i on bi.industryid = i.industryid
join trade_union tu on i.unionid = tu.unionid
group by tu.uniontitle )
loop
dbms_output.put_line(r.ut);
dbms_output.put_line(r.blist);
dbms_output.put_line('-----');
end loop;
end;
Function listagg is available in Oracle 11g or later.
Output:
Cleaners' Union
[12345678912] Consolidated Proerty Services, [12345678929] Gold Cleaning Services, [12345678926] Home Cleaning Services, [12345678924] Shine Cleaning
-----
Construction Workers' Union
[12345678920] Build a House, [12345678919] Construction Solutions, [12345678922] Joe's Rubbish Removal, [12345678918] Leak and Roof Repair, [12345678928] Muscle Rubbish Removals
-----
Electricians' Union
[12345678916] Change the Fuse Electricals, [12345678921] Hire a Wire, [12345678917] Vicky Electricals
-----
Movers' Union
[12345678913] Kohlan Movers, [12345678925] Moveit
-----
Mowers' Union
[12345678923] Do it Right Mowers, [12345678911] James Mowers and Landscape
-----
Plumbers' Union
[12345678927] 24X7 Plumbing Service, [12345678915] Anytime Plumbers, [12345678914] Pumbers Delivered
-----

Related

Oracle SQL where clause subquery not working properly

I have a car rent management database and I created a query which should output car maintainer supervisors that have more than 1 subordinates (maintainers). In my case only one supervisor has 2 subordinates.
When I try to use this select statement, it still outputs all supervisors, when in reality I only need to output the first row. Would really appreciate your help, I am quite new in SQL and this is for my school project.
1 select s.name || ' ' || s.surname as "Supervisor", s.mobile_nr as "Telephone", count(w.id_worker) as "Total subworkers"
2 from supervisor s, worker w, maintainer m
3 where s.id_supervisor=w.id_supervisor and w.id_maintainer=m.id_maintainer and (select count(id_worker) from worker, maintainer where maintainer.id_maintainer=worker.id_maintainer)>1
4* group by s.name, s.surname, s.mobile_nr
SQL> /
Supervisor Telephone Total subworkers
------------------------------------------------------------- -------------------- ----------------
Marcis Janeks +37122176223 2
Ilona Katlina +37128632447 1
Skaidris Kalnins +37123389601 1
Zane Masta +37126874141 1
Karlis Maiznieks +37128610671 1
Modris Upmalis +37125012112 1
Ilgonis Smaidins +37129636642 1
Janis Kurpnieks +37126178482 1
Vladimir Kurcavenko +79061177915 1
Miraldas Hakomas +37022892633 1
10 rows selected.
Use a HAVING clause rather than an (uncorrelated) sub-query:
SELECT MAX(s.name || ' ' || s.surname) as "Supervisor",
MAX(s.mobile_nr) as "Telephone",
COUNT(w.id_worker) as "Total subworkers"
FROM supervisor s
INNER JOIN worker w
ON s.id_supervisor=w.id_supervisor
INNER JOIN maintainer m
ON w.id_maintainer=m.id_maintainer
GROUP BY s.id_supervisor
HAVING COUNT(w.id_worker) > 1
Note: you probably want to GROUP BY the primary key for the supervisor rather than using the name as there could be two people with the same name (and the same phone number if they shared an office).
Note 2: Use the ISO/ANSI standard join syntax rather than the legacy comma-join syntax from 30 years ago that is difficult to read.

Find string match to Oracle table using regex

I have an Oracle stored procedure on an Oracle 12c database that receives a company_name input. From that company_name, I need to find and flag Federal institutions. To accomplish that, I have a table (TBL_FED_KEY) with one column (KEY_1) of keywords. The table contains nearly 50 values like:
ARMY
FEDERAL
AIR FORCE
VETERANS
HOMELAND SECURITY
INDIAN HOSPITAL
WILL ROGERS
To give you an idea of the company_name string that could be passed through to the procedure, here are examples:
US Army - Munson Health Center
Federal Bureau of Prisons,BOP/DOJ-
Hickam Air Force Base Pharmacy
Minnesota Veterans Home Pharmacy
P.H.S. Indian Hospital
Will Rogers Health Center
What Oracle SQL can be used to match the incoming company_name against TBL_FED_KEY.KEY_1? I've tried multiple variations of REGEXP_INSTR but I can't seem to get anything to work 100%. Is REGEXP_INSTR even the best tool to accomplish this?
Thanks!
You could just use like:
select f.*
from TBL_FED_KEY f
where lower(i.name) like '%' || lower(KEY_1) || '%'
Seems you want case-insensitive match among those string. So, use REGEXP_LIKE() function with case-insensitive(i) option :
SELECT *
FROM TBL_FED_KEY
WHERE REGEXP_LIKE(company_name,key_1,'i')
I am not sure what the procedure is supposed to do after it "flags" the company as federal vs. not. I would instead write it as a function as shown below (but you can easily reuse most of the code in a procedure, if needed).
Then I illustrate how the function can be used directly in SQL. You can also use it in PL/SQL if needed, but in most cases you don't. Note - the same idea can be implemented exclusively in SQL, resulting in faster execution, since you don't need PL/SQL at all. Important - even in plain SQL, this should be implemented via a semi join, as I demonstrated, for faster execution.
Setup:
create table tbl_fed_key (key_1 varchar2(200));
insert into tbl_fed_key
select 'ARMY' from dual union all
select 'FEDERAL' from dual union all
select 'AIR FORCE' from dual union all
select 'VETERANS' from dual union all
select 'HOMELAND SECURITY' from dual union all
select 'INDIAN HOSPITAL' from dual union all
select 'WILL ROGERS' from dual
;
commit;
Function code:
create or replace function is_federal_institution(company_name varchar2)
return varchar
deterministic
as
is_fed varchar2(1);
begin
select case when exists ( select key_1
from tbl_fed_key
where instr(upper(company_name), upper(key_1)) > 0
)
then 'Y' else 'N' end
into is_fed
from dual;
return is_fed;
end;
/
SQL test:
with
inputs (str) as (
select 'Joe and Bob Army Supply Store' from dual union all
select 'Mary Poppins Indian Hospital' from dual union all
select 'Bridge Association of NYC' from dual union all
select 'Will Rogers Garden' from dual union all
select 'First Federal Bank NA' from dual
)
select str, is_federal_institution(str) as is_federal
from inputs
;
STR IS_FEDERAL
------------------------------ ----------
Joe and Bob Army Supply Store Y
Mary Poppins Indian Hospital Y
Bridge Association of NYC N
Will Rogers Garden Y
First Federal Bank NA Y
As you can see, I threw in a few false positives - to illustrate the important fact that this "technological" solution is only partial. A human will still need to review the individual hits, if accuracy is important.

Split Text into Table Rows with Read-Only Permissions

I am a read-only user for a database with he following problem:
Scenario:
Call center employees for a company submit tickets to me through our database on behalf of our clients. The call center includes alphanumeric lot numbers of an exact length in their message for me to troubleshoot. Depending on how many times a ticket is updated, there could be several messages for one ticket, each of them having zero or more of these alphanumeric lot numbers embedded in the message. I can access all of these messages with Oracle SQL and SQL Tools.
How can I extract just the lot numbers to make a single-column table of all the given lot numbers?
Example Data:
-- Accessing Ticket 1234 --
SELECT *
FROM communications_detail
WHERE ticket_num = 1234;
-- Results --
TICKET_NUM | MESSAGE_NUM | MESSAGE
------------------------------------------------------------------------------
1234 | 1 | A customer recently purchased some products with
| | a lot number of vwxyz12345 and wants to know if
| | they have been recalled.
------------------------------------------------------------------------------
1234 | 2 | Same customer found lots vwxyz23456 and zyxwv12345
| | in their storage as well and would like those checked.
------------------------------------------------------------------------------
1234 | 3 | These lots have not been recalled. Please inform
| | the client.
So-Far:
I am able to isolate the lot numbers of a constant string with the following code, but it gets put into standard output and not a table format.
DECLARE
msg VARCHAR2(200) := 'Same customer found lots xyz23456 and zyx12345 in their storage as well and would like those checked.';
cnt NUMBER := regexp_count(msg, '[[:alnum:]]{10}');
BEGIN
IF cnt > 0 THEN
FOR i IN 1..cnt LOOP
Dbms_Output.put_line(regexp_substr(msg, '[[:alnum:]]{10}', 1, i));
END LOOP;
END IF;
END;
/
Goals:
Output results into a table that can itself be used as a table in a larger query statement.
Somehow be able to apply this to all of the messages associated with the original ticket.
Update: Changed the example lot numbers from 8 to 10 characters long to avoid confusion with real words in the messages. The real-world scenario has much longer codes and very specific formatting, so a more complex regular expression will be used.
Update 2: Tried using a table variable instead of standard output. It didn't error, but it didn't populate my query tab... This may just be user error...!
DECLARE
TYPE lot_type IS TABLE OF VARCHAR2(10);
lots lot_type := lot_type();
msg VARCHAR2(200) := 'Same customer found lots xyz23456 and zyx12345 in their storage as well and would like those checked.';
cnt NUMBER := regexp_count(msg, '[[:alnum:]]{10}');
BEGIN
IF cnt > 0 THEN
FOR i IN 1..cnt LOOP
lots.extend();
lots(i) := regexp_substr(msg, '[[:alnum:]]{10}', 1, i);
END LOOP;
END IF;
END;
/
This is a regex format which matches the LOT mask you provided: '[a-z]{3}[0-9]{5}' . Using something like this will help you avoid the false positives you mention in your question.
Now here is a read-only, pure SQL solution for you.
with cte as (
select 'Same customer found lots xyz23456 and zyx12345 in their storage as well and would like those checked.' msg
from dual)
select regexp_substr(msg, '[a-z]{3}[0-9]{5}', 1, level) as lotno
from cte
connect by level <= regexp_count(msg, '[a-z]{3}[0-9]{5}')
;
I'm using the WITH clause just to generate the data. The important thing is the the use of the CONNECT BY operator which is part of Oracle's hierarchical data syntax but here generates a table from one row. The pseudo-column LEVEL allows us to traverse the string and pick out the different occurrences of the regex pattern.
Here's the output:
SQL> r
1 with cte as ( select 'Same customer found lots xyz23456 and zyx12345 in their storage as well and would like those checked.' msg from dual)
2 select regexp_substr(msg, '[a-z]{3}[0-9]{5}', 1, level) as lotno
3 from cte
4 connect by level <= regexp_count(msg, '[a-z]{3}[0-9]{5}')
5*
LOTNO
----------
xyz23456
zyx12345
SQL>

Using a Table-Valued Function to Turn a Single Row into Many Within Select

Simple enough question I think.
I have a dataset, quite large with a bit of free-text name data. I need to to link this to our employee table.
There's a whole set of different ways people have entered the 'owner' in to this fields (John Smith, J.Smith, John Smith (JSMITH), Company:John Smith/Client: John Smith, ect.)
Most of these are fine, but the problem I have is with the ones where multiple names have been entered. For example; "John Smith / Joe Bloggs".
I have a pre-created Table-Valued function which takes in a string and a delimiter, then returns a table with the results of the split.
dbo.Split('John Smith / Joe Bloggs')
id val
1 John Smith
2 Joe Bloggs
The issue I have is that I need these results to come back for each row within an existing dataset. So for example, my query selecting the Owner, RefNumber and OSProjectCode fro my 'ProjectActions' table containing the following data:
RefNumber OSProjectCode Owner
1 1234 Bill Baggins
2 1234 John Smith / Joe Bloggs
would come out looking like this:
RefNumber OSProjectCode Owner
1 1234 Bill Baggins
2 1234 John Smith
2 1234 Joe Bloggs
What I've tried to far is attempt to join on the results of the function - but unsurprisingly it wont let me send in the column from ProjectsActions into the function like that.
SELECT a.val AS [Owner], pa.[RefNumber], pa.[OSProjectCode]
FROM dbo.ProjectsActions pa
INNER JOIN dbo.Split(pa.[Owner], '/') a
Msg 4104, Level 16, State 1, Line 1
The multi-part identifier "pa.Owner" could not be bound.
The only way I can think of doing this, which seems a little too bulky and messy, is the below:
;with base as(
SELECT
pa.RefNumber
, pa.OSProjectCode
, (SELECT val FROM dbo.Eval(pa.Owner) WHERE id = 1) AS [First]
, (SELECT val FROM dbo.Eval(pa.Owner) WHERE id = 2) AS [Second]
FROM ProjectsActions pa
)
SELECT
a.RefNumber
, a.OSProjectCode
, a.First AS [Owner]
FROM base a WHERE a.First IS NOT NULL
UNION ALL
SELECT
b.RefNumber
, b.OSProjectCode
, b.Second AS [Owner]
FROM base b WHERE a.First IS NOT NULL
Surely there's a better way? Something more similar to my first attempt - joining to the results within each row?
Any feedback or ideas would be much appreciated.
Cheers,
Scott.
EDIT:
FYI if anyone comes accross this with a similar issue, but are missing the 'split' part - I use a function found elsewhere on stackoverflow. https://stackoverflow.com/a/14600765/1700309
You need to use an APPLY as your join.
SELECT
a.val AS [Owner],
pa.[RefNumber],
pa.[OSProjectCode]
FROM dbo.ProjectsActions pa
CROSS APPLY dbo.Split(pa.[Owner], '/') a
The CROSS APPLY acts like an INNER JOIN passing the row-level value into your table-valued function. If you expect split function returns NULL if it can't split the value (NULL, empty, etc), you can use OUTER APPLY so that the NULL won't drop that row out of your result set. You can also add a COALESCE to fall back to the [owner].
SELECT
COALESCE(a.val, pa.[Owner]) AS [Owner],
pa.[RefNumber],
pa.[OSProjectCode]
FROM dbo.ProjectsActions pa
OUTER APPLY dbo.Split(pa.[Owner], '/') a

Oracle Data warehouse - Handling data that there ins't in any dimension

We are implementing a data warehouse and we are stuck in a problem. We want to load this data to fact table(num_class is not a FK/PK);
STUDENT NUM_CLASS COURSE CLASS SHIFT YEAR DATE
---------- --------- ------------ ----- ------ ----- ----------
1100882 1 EID SAD OT 13/14 28-05-2014
1100882 1 EID SAD PL2 13/14 28-05-2014
This is the procedure we use to load the data to fact table:
PROCEDURE load_fact_table_presencas IS
v_source_lines INTEGER;
BEGIN
-- JUST FOR STATISTICS
SELECT COUNT(*)
INTO v_source_lines
FROM T_CLEAN_presencas;
INSERT INTO t_fact_presencas(aluno_id, uc_id,curso_id,turno_id,id_tempo,numero_aula)
SELECT
pre.aluno_id,
uc.uc_id,
curso.curso_id,
t.turno_id,
ano.id_tempo,
pre.numero_aula
FROM
t_dim_turnos t,
T_DIM_ANO_LETIVO ano,
T_DIM_UCS uc,
T_DIM_CURSOS curso,
T_CLEAN_PRESENCAS pre
WHERE
-- joins to get dimension keys using sources' natural keys
ano.data_completa = TO_CHAR( pre."data", 'dd-mm-yyyy') AND
NVL(pre.TURNO_ID, pck_error_codes.c_load_invalid_dim_record_Nkey) = t.turno_id_natural AND
curso.curso_id_natural = pre.curso_id AND
uc.uc_id_natural = pre.uc_id AND
--t.turno_id_natural = pre.turno_id AND
-- excludes EXPIRED VERSIONS of curso and us dimensions
uc.is_expired_version = 'NO' AND
curso.IS_EXPIRED_VERSION = 'NO';
pck_log.write_log('Info: '||SQL%ROWCOUNT ||' fact(s) (presencas) loaded');
pck_log.write_log('Done!');
EXCEPTION
WHEN NO_DATA_FOUND THEN
pck_log.write_log('Info: No facts generated from '||v_source_lines||' source clean presencas');
WHEN OTHERS THEN
pck_log.write_log('Error: Could not load fact table (presencas) ['||sqlerrm||']');
RAISE e_load;
END;
The problem is that some of the data that we want to load isn't at any dimension and when we load the data we give to each row a "special registry". This returns a primary key restriction error because it is possible that all the other keys are the same.
PK is STUDENT_id + COURSE_id + CLASS_id + SHIFT + DATE_id. My dimensions are STUDENT, COURSE, CLASS, SHIFT, DATE.