Oracle SQL - select from view more rows than running select in the view - sql

When I run this SQL, I get 116,463 rows.
select * from appsdisc.appsdisc_phones_gen_v
When I run the select that is in the view definition script, I get 11,702 rows.
I can't figure out why the result set is different.
The view script is as follows.
CREATE OR REPLACE FORCE VIEW APPSDISC.APPSDISC_PHONES_GEN_V
(PARTY_ID, CUSTOMER_ID, CUSTOMER_NUMBER, PHONE_NUMBER, PHONE_TYPE)
AS
SELECT party_id,
customer_id,
customer_number,
phone_number,
phone_type
FROM appsdisc_phones_v pv1
WHERE pv1.phone_type LIKE
DECODE (TRIM (SUBSTR (pv1.attribute14, 1, 4)),
'FR', 'FR T%',
'PHONE')
AND pv1.contact_point_id =
(SELECT MIN (pv2.contact_point_id)
FROM appsdisc_phones_v pv2
WHERE pv2.customer_id = pv1.customer_id
AND pv2.phone_type LIKE
DECODE (
TRIM (SUBSTR (pv1.attribute14, 1, 4)),
'FR', 'FR T%',
'PHONE'));

If you're running the view query exactly as it is, and you are not logged in as APPSDISC, you might be querying your own table (or view), since appsdisc_phones_v isn't prefixed by the schema in the view script. Hopefully this is a development environment and you have an old copy for a valid reason.
Here's a demo of the effect I think you're seeing. As one user (SOUSER1) I can create and populate a table with a view on top of it, and grant access to that view to a different user. Notice I don't need to grant access to the underlying table directly.
create table my_table (id number);
insert into my_table
select level as id from dual connect by level <= 1000;
commit;
create view souser1.my_view as select * from my_table;
grant select on souser1.my_view to souser2;
select count(*) from my_view;
COUNT(*)
----------
1000
select count(*) from my_table;
COUNT(*)
----------
1000
I didn't specify the schema in the select inside the view statement, so it's going to be the same as the view owner, which is SOUSER1 in this case.
Then as a second user (SOUSER2) I can create my own version of the table, with fewer rows. Querying the view still shows the row count from the SOUSER1 table, not my own.
create table my_table (id number);
insert into my_table
select level as id from dual connect by level <= 100;
commit;
select count(*) from souser1.my_view;
COUNT(*)
----------
1000
If I run the query from the original view I'm seeing my own copy of the table, which is smaller, because the table name isn't qualified with the schema name - hence it defaults to my own:
select count(*) from my_table;
COUNT(*)
----------
100
So seeing a different number of rows makes sense as long as there are two versions of the table and you haven't specified which you want to query.
And in my case, if I try to query the other schema's table directly I get an error, since I didn't grant any privileges on that:
select count(*) from souser1.my_table;
SQL Error: ORA-00942: table or view does not exist
00942. 00000 - "table or view does not exist"
But you'd see the same error querying my_table if you don't have your own copy, you don't set your current schema on login, and you don't have a synonym pointing to a table in some schema.

Gordon is right, the select should return the same results as the view. Issues such as running the query in different schema, databases, or over database links could explain what you are observing. You can see this by running the two SQL commands in the same database and schema below and comparing the values returned by both.
First, confirm the number of rows the view returns with the SQL:
SELECT COUNT(*) FROM APPSDISC.APPSDISC_PHONES_GEN_V;
Then confirm the number of rows the query for the view returns with the SQL:
WITH RESULTS AS (
SELECT party_id,
customer_id,
customer_number,
phone_number,
phone_type
FROM appsdisc_phones_v pv1
WHERE pv1.phone_type LIKE
DECODE (TRIM (SUBSTR (pv1.attribute14, 1, 4)),
'FR', 'FR T%',
'PHONE')
AND pv1.contact_point_id =
(SELECT MIN (pv2.contact_point_id)
FROM appsdisc_phones_v pv2
WHERE pv2.customer_id = pv1.customer_id
AND pv2.phone_type LIKE
DECODE (
TRIM (SUBSTR (pv1.attribute14, 1, 4)),
'FR', 'FR T%',
'PHONE')))
SELECT COUNT(*)
FROM RESULTS
/
Both queries should be returning the same values. If not then there is more to this issue than a query returning a different number of rows.

Related

How to get a list of tables used for each query in the query history in snowflake

I am doing analysis on table usage. I need to use the query history table and take out all tables that were used in a query. The query itself could use 10 separate tables, or even use the same table multiple times. I need to be able to parse through the query (using a query itself, not python or any other language, just SQL) and take out a list of all tables that the query hit.
Is there any way to do this?
An example of where I am getting the histories would be this query:
select query_text
from table(information_schema.query_history())
An alternative approach using rlike and information_schema.tables.
You could extend this further by looking at the # rows per table (high = fact, low = dimension) and the number of times accessed.
select query_text, array_agg(DISTINCT TABLE_NAME::string)
from
(select top 100 query_text
from
table(information_schema.query_history())
where
EXECUTION_STATUS = 'SUCCESS' ) a
left outer join
(select TABLE_NAME from INFORMATION_SCHEMA.TABLES group by TABLE_NAME) b
on
upper(a.query_text) rlike '.*('||upper(b.table_name)||').*'
group by
query_text
Extended Version:
I noticed there's some issues with the above answer. Firstly that it does not allow you to run the explain plan any more than one query at a time. Secondly if the query_id uses a cache it fails to return any objects.
So be extending my initial answer as follows.
Create a couple views that read all the databases and provide central authority on all tables/views/objects/query_histories.
Run the generated SQL which creates a couple views. It again uses rlike but substitutes database and schema names from the query_history when not present.
I've added credits, time elapsed to the two views for more extensions.
You can validate for yourself by checking the explain plan as above and if you don't see identical tables check the SQL and you'll most likely see the cache has been used.
Would be great to hear if anyone finds this useful.
Step 1 Create a couple views :
show databases;
select RTRIM( 'create or replace view bob as ( '||listagg('select CONCAT_WS(\'.\',TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME) table_name_3,CONCAT_WS(\'.\',TABLE_SCHEMA, TABLE_NAME) table_name_2,TABLE_NAME, ROW_COUNT, BYTES from ' ||"name" ||'.INFORMATION_SCHEMA.TABLES union all ') ,' union all')||')' tabs,
RTRIM( 'create or replace view bobby as ( '||listagg('select QUERY_ID, query_text ,DATABASE_NAME, SCHEMA_NAME, CREDITS_USED_CLOUD_SERVICES , TOTAL_ELAPSED_TIME from table( '||"name" ||'.information_schema.query_history()) where EXECUTION_STATUS = \'SUCCESS\' union all ') ,' union all')||')' tabs2
from table(result_scan( LAST_QUERY_ID()));
Step 2 Run this SQL:
select
QUERY_TEXT,
query_id,
CREDITS_USED,
TOTAL_ELAPSED,
array_agg(TABLE_NAME_3) tables_used
from
(
select
QUERY_TEXT
,query_id
,TABLE_NAME
, rlike( (a.query_text) , '.*(\\s.|\\.){1}('||(bob.TABLE_NAME)||'(\\s.*|$))','is') aa
, rlike( (a.query_text) , '.*(\\s.|\\.){1}('||(bob.TABLE_NAME_2)||'(\\s.*|$))','is') bb
, rlike( (a.query_text) , '.*(\\s.){1}('||(bob.TABLE_NAME_3)||'(\\s.*|$))','is') cc,
bob.TABLE_NAME_3,
count(1) cnt,
max(CREDITS_USED_CLOUD_SERVICES) CREDITS_USED,
max(TOTAL_ELAPSED_TIME) TOTAL_ELAPSED
from
BOBBY a
left outer join
BOB
on
rlike( (a.query_text) , '.*(\\s.|\\.){1}('||(bob.TABLE_NAME)||'(\\s.*|$))','is')
or rlike( (a.query_text) , '.*(\\s.|\\.){1}('||(bob.TABLE_NAME_2)||'(\\s.*|$))','is')
or rlike( (a.query_text) , '.*(\\s.|\\.){1}('||(bob.TABLE_NAME_3)||'(\\s.*|$))','is')
where
TABLE_NAME is not null
and ( cc
or iff(bb, upper( DATABASE_NAME||'.'||TABLE_NAME) = bob.TABLE_NAME_3, false)
or iff(aa, upper (DATABASE_NAME||'.'||SCHEMA_NAME||'.'||TABLE_NAME) = bob.TABLE_NAME_3, false)
)
group by
1,2,3,4,5,6,7)
group by
1,2,3,4;
The ACCESS_HISTORY view will tell you what tables are used in a query:
https://docs.snowflake.com/en/sql-reference/account-usage/access_history.html
That's an enterprise level feature. You could also run EXPLAIN on any query:
SELECT *, "objects"
FROM TABLE(EXPLAIN_JSON(SYSTEM$EXPLAIN_PLAN_JSON('SELECT * FROM a.b.any_table_or_view')))
WHERE "operation"='TableScan'
See more at https://stackoverflow.com/a/64343564/132438

How to dynamically SELECT from manually partitioned table

Suppose I have table of tenants like so;
CREATE TABLE tenants (
name varchar(50)
)
And for each tenant, I have a corresponding table called {tenants.name}_entities, so for example for tenant_a I would have the following table.
CREATE TABLE tenant_a_entities {
id uuid,
last_updated timestamp
}
Is there a way I can create a query with the following structure? (using create table syntax to show what I'm looking for)
CREATE TABLE all_tenant_entities {
tenant_name varchar(50),
id uuid,
last_updated timestamp
}
--
I do understand this is a strange DB layout, I'm playing around with foreign data in Postgres to federate foreign databases.
Did you consider declarative partitioning for your relational design? List partitioning for your case, with PARTITION BY LIST ...
To answer the question at hand:
You don't need the table tenants for the query at all, just the detail tables. And one way or another you'll end up with UNION ALL to stitch them together.
SELECT 'a' AS tenant_name, id, last_updated FROM tenant_a_entities
UNION ALL SELECT 'b', id, last_updated FROM tenant_b_entities
...
You can add the name dynamically, like:
SELECT tableoid::regclass::text, id, last_updated FROM tenant_a_entities
UNION ALL SELECT tableoid::regclass::text, id, last_updated FROM tenant_a_entities
...
See:
Get the name of a row's source table when querying the parent it inherits from
But it's cheaper to add a constant name while building the query dynamically in your case (the first code example) - like this, for example:
SELECT string_agg(format('SELECT %L AS tenant_name, id, last_updated FROM %I'
, split_part(tablename, '_', 2)
, tablename)
, E'\nUNION ALL '
ORDER BY tablename) -- optional order
FROM pg_catalog.pg_tables
WHERE schemaname = 'public' -- actual schema name
AND tablename LIKE 'tenant\_%\_entities';
Tenant names cannot contain _, or you have to do more.
Related:
Table name as a PostgreSQL function parameter
How to check if a table exists in a given schema
You can wrap it in a custom function to make it completely dynamic:
CREATE OR REPLACE FUNCTION public.f_all_tenant_entities()
RETURNS TABLE(tenant_name text, id uuid, last_updated timestamp)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE
(
SELECT string_agg(format('SELECT %L AS tn, id, last_updated FROM %I'
, split_part(tablename, '_', 2)
, tablename)
, E'\nUNION ALL '
ORDER BY tablename) -- optional order
FROM pg_tables
WHERE schemaname = 'public' -- your schema name here
AND tablename LIKE 'tenant\_%\_entities'
);
END
$func$;
Call:
SELECT * FROM public.f_all_tenant_entities();
You can use this set-returning function (a.k.a "table-function") just like a table in most contexts in SQL.
Related:
How to UNION a list of tables retrieved from another table with a single query?
Simulate CREATE DATABASE IF NOT EXISTS for PostgreSQL?
Function to loop through and select data from multiple tables
Note that RETIRN QUERY does not allow parallel queriies before Postgres 14. The release notes:
Allow plpgsql's RETURN QUERY to execute its query using parallelism (Tom Lane)

How can we enter many values in a SQL parameter - SQL Developer?

In SQL Developer, we can use parameters in order to test our query with different values - for example:
I have a table called Fruits (code, name). I want to retrieve code's apples.
SELECT *
FROM fruits
WHERE name IN (:PM_NAME)
It works correctly when I fill out one value (in this case :PM_NAME equal apple)
But when I want to fill out many values it doesn't work! I've tried these forms and these separators but still..
apple;orange
'apple';'orange'
('apple','orange')
['apple','orange']
"apple","orange"
In a nutshell what's the correct format to fill out multiple values in a SQL parameter in SQL Developer ?
I can't take credit for this 'trick' but the solution lies in a regular expression.
I want to search on multiple types of objects, fed to a :bind, and used in a WHERE clause.
SELECT owner,
object_name,
object_type
FROM all_objects
WHERE object_name LIKE :SEARCH
AND owner NOT IN (
'SYS',
'MDSYS',
'DBSNMP',
'SYSTEM',
'DVSYS',
'APEX_050100',
'PUBLIC',
'ORDS_METADATA',
'APEX_LISTENER'
)
AND object_type IN (
SELECT regexp_substr(:bind_ename_comma_sep_list,'[^,]+',1,level)
FROM dual CONNECT BY
regexp_substr(:bind_ename_comma_sep_list,'[^,]+',1,level) IS NOT NULL
)
ORDER BY owner,
object_name,
object_type;
I first learned of this 'trick' or technique from here.
So your query would look like this
SELECT *
FROM fruits
WHERE name IN (
SELECT regexp_substr(:PM_NAME,'[^,]+',1,level)
FROM dual CONNECT BY
regexp_substr(:PM_NAME,'[^,]+',1,level) IS NOT NULL
)
When you're prompted for values by SQL Developer, don't quote the strings, just comma separate them. Also, no spaces.
So in the input box, enter
apple,orange
And I suppose if you want ; vs , then update the regex call as needed.

Trigger keeps failing to compile. Claims table not found when table exists

All the tables that are called definitely exist. I double checked the spelling as well.
The errors sqldeveloper gives are not very helpful and I can't seem to figure out why this is failing. I am attempting to make a trigger which updates the top5restaurants table when a new review comes in. I have a procedure which parses the review and adds them to the rating table. The top5restaurant table maintains the restaurants with the 5 highest ratings.
drop view bestratings_rest;
create view bestratings_rest(rid, rate) as
(select distinct rid, max(stars) from rating
group by rid);
drop table top5restaurants;
create table top5restaurants(rid int);
insert into top5restaurants rid
select rid from
(select rid, rate from bestratings_rest r
order by r.rate asc)
where rownum <=5;
drop view top5rest;
create view top5rest as
select rid, best from(
select t.rid, best from top5restaurants t
inner join (select distinct rid, max(stars) as best from rating
group by rid) b
on t.rid = b.rid);
create or replace trigger top5_trigger
after insert on rating
for each row
declare
minrow top5rest%rowtype;
restrid restaurant.rid%type;
restname restaurant.name%type;
begin
insert into minrow
select rid, min(best) from top5rest;
insert into restname
select rid from restaurant
where :new.rid = restaurant.rid;
if :new.stars > minrow.best then
DELETE FROM top5restaurants
where top5restaurants.rid = :new.rid;
insert into top5restaurants values(rid);
end if;
end;
/
--
--
begin
update_reviews('Jade Court','Sarah M.', 4, '08/17/2017');
update_reviews('Shanghai Terrace','Cameron J.', 5, '08/17/2017');
update_reviews('Rangoli','Vivek T.',5,'09/17/2017');
update_reviews('Shanghai Inn','Audrey M.',2,'07/08/2017');
update_reviews('Cumin','Cameron J.', 2, '09/17/2017');
end;
/
select * from top5restaurants;
sqldeveloper outputs:
View BESTRATINGS_REST dropped.
View BESTRATINGS_REST created.
Table TOP5RESTAURANTS dropped.
Table TOP5RESTAURANTS created.
5 rows inserted.
View TOP5REST dropped.
View TOP5REST created.
Trigger TOP5_TRIGGER compiled
LINE/COL ERROR
7/5 PL/SQL: SQL Statement ignored
7/17 PL/SQL: ORA-00942: table or view does not exist
11/5 PL/SQL: SQL Statement ignored
11/17 PL/SQL: ORA-00942: table or view does not exist
18/5 PL/SQL: SQL Statement ignored
18/40 PL/SQL: ORA-00984: column not allowed here
Errors: check compiler log
You don't insert into a scalar variable. You can do a select into. The statement
select rid
into restname
from restaurant
where :new.rid = restaurant.rid;
is syntactically valid. But it is almost certainly incorrect. Presumably, you want to select the restaurant name not the rid
select name
into restname
from restaurant
where :new.rid = restaurant.rid;
If you declare a %rowtype variable, then you want to do a select * into that variable. Otherwise you'll get a compilation error in the future if anyone adds or removes a column. You'd have to add a predicate that specified which row you wanted to select. My guess is that you want something like this to give the row with the lowest best score (or the row with the lowest rid if there are two with the same low score
select *
into minrow
from top5rest low
where not exists( select 1
from top5rest high
where low.best > high.best )
and not exists( select 1
from top5rest same
where low.best = same.best
and low.rid > same.rid )
More fundamentally, though, even if you get your syntax errors corrected, you're going to get a runtime mutating table error if you try to query the rating table (which your view does) from inside a row-level trigger on the rating table. That's not allowed. It almost guarantees that you have a problem in your logic. In this case, there seems to be no need for a separate table to store the top 5 restaurants. Realistically, your view should just do that calculation if you need the results in real-time or you should create a materialized view that refreshes periodically if you don't need to see a restaurant's overall rating change instantly when new ratings are added. So you probably don't want a trigger at all.

Oracle: Query identical table in multiple schema in single line

In Oracle, is there a way to query the same, structurally identical table out of multiple schema within the same database in single line? Obviously assuming the user has permissions to access all schema, I could build a query like:
select * from schema1.SomeTable
union all
select * from schema2.SomeTable
But is it possible, given the right permissions to say something like:
select * from allSchema.SomeTable
...and bring back all rows for all the schema? And related to this, is it possible to pick which schema, such as:
select * from allSchema.SomeTable where schemaName in ('schema1','schema2')
The simplest option, as far as I can tell, is to create a VIEW (as UNION of all tables across all those users), and then SELECT FROM VIEW.
For example:
create or replace view my_view as
select 'schema_1' source_schema, id, name from schema_1.table union
select 'schema_2' source_schema, id, name from schema_2.table union
...
-- select all
select * from my_view;
-- select all that belongs to one of schemas
select * from my_view where source_schema = 'schema_1';