Views is one part of the answer. It still doesn't allow you to "write once, use everywhere".
You want a generic "program" (query, or function, or procedure) to which you can feed two queries (names for stored queries) and which will spit out the information you need.
I show below how you can do this in a procedure. You call the procedure with two view names, and it prints a message showing you the counts. In a serious implementation you wouldn't use DBMS_OUTPUT.PUT_LINE to get your output, I am not advocating that; I am just showing how you can implement your concept with a procedure, you can modify its behavior to write to a file, or you can change it to a function that will produce a table with the counts, etc.
Note that - since the procedure uses dynamic SQL, and table/view names can't be passed in as bind variables, you must consider SQL injection (a bad thing). To combat it, I wrapped the view names within DBMS_ASSERT.SIMPLE_SQL_NAME. So, make sure your views have simple names (in the technical sense).
Generic procedure:
create or replace procedure
comp_queries ( view_name_1 in varchar2, view_name_2 in varchar2 )
is
cnt_1 number;
cnt_2 number;
cnt_diff number;
begin
execute immediate 'select count(*) from ' || dbms_assert.simple_sql_name(view_name_1)
into cnt_1;
execute immediate 'select count(*) from ' || dbms_assert.simple_sql_name(view_name_2)
into cnt_2;
execute immediate 'select count(*) from (
select * from ' || view_name_1 || ' minus
select * from ' || view_name_2 || ')'
into cnt_diff;
dbms_output.put_line('Count from ' || upper(view_name_1) || ': ' || cnt_1);
dbms_output.put_line('Count from ' || upper(view_name_2) || ': ' || cnt_2);
dbms_output.put_line('Count from ' || upper(view_name_1) || ' MINUS '
|| upper(view_name_2) || ': ' || cnt_diff);
end;
/
Compile it and confirm it works without problems.
Then test it by creating two views. Here I write two queries that should give the same output:
create view v_deptno_1 as
select distinct deptno from emp;
create view v_deptno_2 as
select deptno from emp group by deptno;
So, let's check they do indeed produce the same output. (Note that this is WEAKER THAN a complete formal proof that the queries are equivalent; it's just what you were doing already, checking that they are equivalent on the currently present data in the underlying tables.)
First, since we use DBMS_OUTPUT, we need to turn server output on. Then I call the procedure twice - once with the two view names we just created, and once in a way that simulates a SQL injection attack (always test that too!)
SQL> set serveroutput on
SQL> exec comp_queries('v_deptno_1', 'v_deptno_2')
Count from V_DEPTNO_1: 3
Count from V_DEPTNO_2: 3
Count from V_DEPTNO_1 MINUS V_DEPTNO_2: 0
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.00
-- SIMULATE SQL INJECTION ATTACK:
SQL> exec comp_queries('v_deptno_1', 'v_deptno_2;delete * from emp')
BEGIN comp_queries('v_deptno_1', 'v_deptno_2;delete * from emp'); END;
*
ERROR at line 1:
ORA-44003: invalid SQL name
ORA-06512: at "SYS.DBMS_ASSERT", line 206
ORA-06512: at "INTRO.COMP_QUERIES", line 10
ORA-06512: at line 1
Elapsed: 00:00:00.01