View Expansion in Oracle - sql

So we have some developers who went a little view happy. So now we have views that reference views that reference views, ad nauseum.
So what I want, in order to assist me in Tuning, is to expand these views.
I want a function that takes a string and returns a string. The input string is the query, the output string is the same query without views.
CREATE OR REPLACE VIEW myView AS
SELECT * FROM emp
Using function/stored procedure "F":
F('SELECT * FROM myView')
...would return:
SELECT * FROM ( SELECT * FROM emp)
Is there an Oracle package for this?
Does someone have code in:
either SQL or PL/SQL
In something else

Short answer:
Not at this time
Not that I'm aware of
UPDATE
It looks like Oracle 12c has exactly what you need: DBMS_UTILITY.expand_sql_text
http://docs.oracle.com/cd/E16655_01/appdev.121/e17602/d_util.htm#ARPLS73973

One problem with what you are proposing is that there are usually multiple ways that a query involving views can be rewritten, so simply expanding the text of the views won't necessarily tell you a lot about how the query is being executed.
Since your purpose is tuning, I would suggest that the execution plans for the queries will probably give you the information you really need. This won't show the rewritten query, but it will show you all the actual tables and how they are referenced in executing the query.
The best way I know of to view the actual execution plan is:
SELECT /*+ gather_plan_statistics */ * FROM myView
select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'))

There are some methods to expand views but the output is so ugly it may not be useful.
12c Has a new procedure named DBMS_UTILITY.EXPAND_SQL_TEXT.
11g Has an undocumented procedure dbms_sql2.expand_sql_text.
Below is a simple example. The output may be helpful for the optimizer but it is probably not much use if you need a human-readable SQL statement.
create or replace view view1 as select 1 a, 2 b from dual;
create or replace view view2 as select a from view1;
declare
v_output clob;
begin
dbms_utility.expand_sql_text('select * from view2', v_output);
dbms_output.put_line(v_output);
end;
/
Output:
SELECT "A1"."A" "A" FROM (SELECT "A2"."A" "A" FROM (SELECT 1 "A",2 "B" FROM "SYS"."DUAL" "A3") "A2") "A1"

Related

FOR loop in Oracle SQL or Apply SQL to multiple Oracle tables

My SQL is a bit rusty, so I don't know whether the following is even possible:
I have multiple tables t_a, t_b, t_c with the same column layout and I want to apply the same operation to them, namely output some aggregation into another table. For a table t_x this would look like this:
CREATE TABLE t_x_aggregate (
<here the col definitions which are the same for all new tables t_[abc]_aggregate>
);
INSERT INTO t_x_aggregate(id, ...)
SELECT id, SUM(factor*amount)
FROM t_x
WHERE some fixed condition
GROUP BY id;
I now want to execute something like a FOR loop around this:
for t_x in t_a, t_b, t_c
CREATE TABLE ...
INSERT INTO ...
end for
Is this possible in SQL? Or would I need to build a wrapper in another language for this?
So, the result of that operation would be 3 new tables? T_A_AGGREGATE, T_B_AGGREGATE and T_C_AGGREGATE?
I think that the fastest way is to write 3 separate CREATE TABLE statements, e.g.
create table t_a_aggregate as
select id, sum(factor * amount) suma
from t_a
where some_condition
group by id;
create table t_b_aggregate as
select id, sum(factor * amount) suma
from t_b
where some_condition
group by id;
create table t_c_aggregate as
select id, sum(factor * amount) suma
from t_c
where some_condition
group by id;
OK; I understand that queries aren't that simple, but nothing much changes - only table names in CREATE and FROM (perhaps somewhere else, but that's more or less "it"). Any decent text editor's search/replace capabilities should be able to do it quickly.
If you want to do it dynamically in a loop (read: PL/SQL), you can - but dynamic SQL doesn't scale, is difficult to maintain, is painful to debug. Therefore, if you're doing it only once, consider running 3 separate statements.
How to do it dynamically?
You'd have to create a string (we usually put them into a locally declared variable) which contains the whole DDL statement. Why? Because you can't execute DDL from a PL/SQL otherwise.
If there are multiple tables and/or columns involved, you'll have to combine "fixed" parts of the statement (like create table, select, from, order by) concatenated with "dynamic" parts - such as column names. Note that in between you have to concatenate commas as separators. Pay attention to usage of multiple single quotes as you have to escape them (or use the q-quoting mechanism).
Also, for multiple columns you'll probably have to do it in a loop, concatenating each new column to previously composed string.
It (the statement stored into the varirable) is the executed by EXECUTE IMMEDIATE. If it is correctly written, it'll succeed. Otherwise, it'll fail, but it won't tell you why it failed (that's why I said difficult debugging").
So, instead of executing it, we usually display that string (using dbms_output.put_line) so that we see how it looks like and - using copy/paste - try to execute it.
Basically, it can be quite complex and - as I said - difficult to maintain and debug.
For the FOR loop you need to use PL/SQL like this:(*)
declare
type array_t is table of varchar2(10);
array array_t := array_t('a', 'b', 'c');
lo_stmt varchar2(2000);
begin
lo_stmt :=
'CREATE TABLE t_'||array(i)||'_aggregate ('||
' <here the col definitions which are the same for all new tables t_[abc]_aggregate>'||
');'||
''||
'INSERT INTO t_'||array(i)||'_aggregate(id, ...)'||
'SELECT id, SUM(factor*amount)'||
'FROM t_'||array(i)||
'WHERE some fixed condition'||
'GROUP BY id;'||
execute immediate lo_stmt;
end loop;
end;
/
Look also at this SO question: How to use Oracle PL/SQL to create...
(*) #Littlefoot describes in the 2nd part of his answer valuable background to this program.

Execute Subqueries of a View

I have a rather large view constructed using the "WITH" statement. I've build the view's logic using small, understandable sub queries which are built on top of each other.
The result is a clearly structured SQL which (I believe ) is good to follow even if you're not the creator.
My problem comes with debugging in the future. If at some stage a collegue wants to understand how the result of the view is computed, it's a good approach to to execute some of the sub queries.
The usual approach would be to copy the view's SQL to an SQL-editor (e.g. SQL-Developer) and to replace the main statement with the subquery you're interested in.
Example:
WITH
all_orders AS (
SELECT order, price ... FROM ...
),
all_customers AS (
SELECT id,
last_name,
first_name,
first_order_date ...
FROM...
),
new_customers AS (
SELECT id,
last_name,
first_name
FROM all_customers
WHERE first_order_date > ...
)
-- main SQL
SELECT ... FROM all_orders a
INNER JOIN new_customers ON (...)
If I have the feeling that something is wrong with "new_customers" I would comment out the main SQL and replace it with:
...
-- main SQL
-- SELECT ... FROM all_orders a
-- INNER JOIN new_customers ON (...)
SELECT * FROM new_customers;
If I see that new_customers contains wrong data and I want to check if at least it's source "all_customers" is correct, I replace my main SQL again with
...
-- main SQL
-- SELECT ... FROM all_orders a
-- INNER JOIN new_customers ON (...)
SELECT * FROM all_customers;
That works really well but as soon as the SQL is inside a view I only have access to the result of the main SQL as the normal output of the view.
However, for simple debugging (meaning without going to the SQL editor, looking up the views definition and coping the SQL to the SQL editor) it would be really helpful to have some kind of database function where I could say:
SELECT * FROM RUN_SUBQUERY('my_view_name', 'new_customers');
My question: Is there such a database function or some similar approach which would allow me to quickly execute sub queries of a database view without splitting the logic up into sub views?
See below for my experience with sub views.
Alternatives
Split the big view in separate smaller views:
I tried this. The execution speed of the SQL dropped by factor 10. Since this is way too slow I'm also looking at some possible optimization here - however, I've seen it running really well when it was all in one statement/ view so it's hard to justify the extra effort here. Again, I only need the sub query results for debugging.
Keep the big view and additionally split it into smaller views which are just used for debugging:
This might be a way to go but we all know that having logic defined in two places is never a good idea (DRY).
You could use a function that returns a table of records.
In this function you can according to the input parameters construct your sql differently and return the result with a cursor.
So for example:
select * from table(cast(run_subquery('my_view_name','new_customers' as a_table_of_records_type));
and this calls the function run_subquery:
function RUN_SUBQUERY (VNAME IN VARCHAR2,QNAME IN VARCHAR2)
return a_table_of_records_type is
query_string varchar2(4000);
begin
if VNAME = '...' then
query_string := query_string || '....';
end if;
if VNAME = '...' then
query_string := query_string || '....';
end if;
-- Execute string through refcursor and put the output in a_table_of_records_type
return a_table_of_records_type;
end;

View parameterized queries ran on oracle

I would like to view parameterized update, select, and delete statements executed on an oracle database.
I can see the query by running the following:
select * from v$sqlarea where parsing_schema_name = 'SCHEMA_NAME' order by last_active_time desc
But I want to also view the parameters that go with the SQL in the SQL_TEXT column. Is there a way to do this?
If by parameterized you mean bind variables, you need V$SQL_BIND_CAPTURE. Search it by the SQL_ID you found in V$SQLAREA.
Each row represents a variable captured by position, so you'll have to match it up with your names from your query.

Optimize pass parameter to view

I have quite complicated view in mysql, like
select filter.id as filter_id, person.id, person.name
from person, filter
inner join
...
left join
...
where person_match_filter_condition ...
group by filter.filter_id, person.id, person.name
Query filters person which corresponds domain specific conditions.
Typical use of view is:
select * from where filter_id = some_value
Problem is that mysql cannot optimize query. It applies confition by filter_id AFTER get data for all filters - very ineffective.
Idea to get filter_id from other tables is not good for my case.
How can I transform my query to make it more effective?
Wrap the long query in a procedure, and pass the filters to the procedure call as parameters. Then instead of using views you call the procedure, the procedure will build you the entire query and will run optimized query.
Better yet, you can pass parameters to your views in a simple manner by creating a Function to GET your values from Session Variables. See https://www.stackoverflow.com/questions/14511760 for the technique. This is a copy of my create function you may wish to pattern after.
DELIMITER //
CREATE FUNCTION fn_getcase_id()
RETURNS MEDIUMINT(11)
DETERMINISTIC NO SQL
BEGIN
see stackoverflow.com/questions/14511760 and read ALL the info TWICE or MORE. wh 04/13/2017
RETURN #sv_case_id;
END//
DELIMITER ;
You will need to create a similar FN (one for each variable).

How to transform an Oracle SQL into a Stored Procedure that should iterate through some tables fetching a certain data field?

I need to transform an Oracle SQL statement into a Stored Procedure therefore users with less privileges can access certain data field:
SELECT
info_field, data_field
FROM
table_one
WHERE
some_id = '<id>' -- I need this <id> to be the procedure's parameter
UNION ALL
SELECT
info_field, data_field
FROM
table_two
WHERE
some_id = '<id>'
UNION ALL
SELECT
info_field, data_field
FROM
table_three
WHERE
some_id = '<id>'
UNION ALL
...
Given that I'm no SP expert I've been unable to figure out a good solution to loop through all the involved tables (12 aprox.).
Any ideas would be helpful. Thanks much!
If you just want to restrict users' access you could create a view and grant them select on the view but not the tables:
CREATE VIEW info_and_data AS
SELECT info_field, data_field
FROM table_one
UNION ALL
SELECT info_field, data_field
FROM table_two
UNION ALL
SELECT info_field, data_field
FROM table_three
...
The users could then type:
SELECT info_field, data_field
FROM info_and_data
WHERE some_id = <id>
There are other ways to achieve your goal besides my suggestions below, but I would warn against splitting up data that really belongs in one table just to implement a data access policy that may change in the future.
The simplest solution to limit which table columns a user sees is through views on those tables. Use different views that show or hide specific columns and grant access to those views to different users/roles.
If you don't know in advance which combination of columns a user may be allowed to see, then you could use dynamic sql: You assemble the SQL statment in the stored procedure based on the access privileges of your user (look up from some other table you create to hold this info), meaning that you only include the proper columns in the SELECT portion of your statement. See this document from Orace
for more info.
If you are using Oracle 10g, then you may find this Oracle article interesting. It introduces the topic of the Virtual Private Database, or VPD for short, where you can hide certain rows, or columns or even individual column values depending on who is accessing a table.
Is the expectation that, among all these tables, only one will have a match for a given ID?
If no: You need to explain what you want to do when there are multiple matches.
If yes: You simply do the same SQL query, selecting the result into a variable that you then return.
It would look something like this:
PROCEDURE get_fields( the_id NUMBER,
info_field_out OUT table_one.info_field%TYPE,
data_field_out OUT table_one.data_field%TYPE
)
IS
BEGIN
SELECT info_field, data_field
INTO info_field_out, data_field_out
FROM (
... put your full SQL query here, using 'the_id' as the value to match against ..
);
EXCEPTION
WHEN no_data_found THEN
-- What do you want to do here? Set the outputs to NULL? Raise an error?
WHEN too_many_rows THEN
-- Is this an invalid condition?
END;