How to auto-redefine view when underlying table changes (new column)? - sql

We've got a view that's defined like this
CREATE VIEW aView as
SELECT * from aTable Where <bunch of conditions>;
The "value" of the view is in the where-condition, so it is okay to use a Select * in this case.
When a new column is added to the underlying table, we have to redefine the view with a
CREATE OR REPLACE FORCE VIEW aView as
SELECT * from aTable Where <bunch of conditions>;
as the Select * seems to get "translated" into all the columns present at the time the view is (re-)defined.
My question: How can we avoid this extra step?
(If the answer is dependent on the RDBMS, we're using Oracle.)

I know you specified Oracle, but the behavior is the same in SQL Server.
One way to update the view with the new column is to use:
exec sp_refreshview MyViewName
go
Of course, I also agree with the other comments about not using a SELECT * in a view definition.

This extra step is mandatory in Oracle: you will have to recompile your view manually.
As you have noticed, the "*" is lost once you create a view:
SQL> create table t (id number);
Table created
SQL> create view v as select * from t;
View created
SQL> select text from user_views where view_name = 'V';
TEXT
-------------------------------------------------------
select "ID" from t

You should not be using * in your views. Specify the columns explicitly.
That way you are only retrieving the data you need, and thus avoid potential issues down the road where someone adds a column to a table that you do not want that view to return (e.g., a large binary column that would adversely impact performance).
Yes, you need to recompile the view to add another column, but this is the correct process. That way you avoid other compilation issues, such as if the view reference two tables, and someone adds a duplicate column name in one of the tables. The compiler would then have issues determining which of the columns was being referred to if you did not prefix a reference to the column with a table alias, or it might complain if there are duplicate column names in the results.

The problem with automatically updating views to add columns comes when you extend your model, for example to
SELECT a.*, std_name_format(a.first_name, a.middle_names, a.last_name) long_name
or even
SELECT a.*, b.* from table_a a join table_b b....
If you have a view of just SELECT * FROM table, then you probably should be using a synonym or addressing the table directly.
If the view is hiding rows (SELECT * FROM table WHERE...), then you can look at the feature variously known as Fine Grained Access Control (FGAC), Row Level Security (RLS) or Virtual Private Database (VPD).
You might be able to do something with a DDL trigger but that would get complicated.

Related

Snowflake SQL Compilation Error: View Definition Declared but view Query Produced

I've just gotten a new query error that I haven't changed anything to. Any advice on what to do? Thanks
SQL compilation error:
View definition for '**********' declared 115 column(s), but view query produces 117 column(s).
This is speculation, but it sounds like your view is using select x.*, where * means to get all the columns from some table.
Then, the underlying table changes . . . and voila, you might have a problem.
I've just gotten a new query error that I haven't changed anything to. Any advice on what to do?
If the query started to produce errors it means that the defintion of view is no longer "valid/up-to-date". Most likely the underlying table has been altered.
CREATE VIEW
View definitions are not dynamic. A view is not automatically updated if the underlying sources are modified such that they no longer match the view definition, particularly when columns are dropped. For example:
A view is created referencing a specific column in a source table and the column is subsequently dropped from the table.
A view is created using SELECT * from a table and any column is subsequently dropped from the table.
In either of these scenarios, querying the view returns a column mismatch error.
Steps to reproduce the scenario:
CREATE OR REPLACE TABLE t(col1 INT, col2 INT);
INSERT INTO t(col1, col2) VALUES (1,1);
CREATE OR REPLACE VIEW v_t AS SELECT * FROM t;
SELECT * FROM v_t;
--COL1 COL2
--1 1
So far so good. Now altering the underlying table by adding new column:
ALTER TABLE t ADD COLUMN col3 INT DEFAULT 3;
SELECT * FROM v_t;
SQL compilation error: View definition for 'V_T' declared 2 column(s), but view query produces 3 column(s).
Recreation of the view and keeping its definition on par with underlying tables should resolve it:
CREATE OR REPLACE VIEW v_t
COPY GRANTS
AS
SELECT * FROM t;
-- using * will work to refresh it but I would not recommend it
-- and explicitly describe columns instead
CREATE OR REPLACE VIEW v_t
COPY GRANTS -- to preserve already granted permissions
AS
SELECT col1, col2, col3 FROM t;
SELECT * FROM v_t;
-- COL1 COL2 COL3
-- 1 1 3
I found myself with a similar issue this morning. I had copied my query from a txt file I had and pasted it into a worksheet and tired to run it and got the same error. I had used the Table with the definition issue for a join and only for 1 column so I didn't see why it was giving me such an error.
All a look of check tables I commented on the Worksheet the error I was seeing. But I decided to run it again and it worked.
This tells me that Snowflake was using what it had cached but after editing the query it saw it as a new query and re-ran it instead of erroring out when what is in the cache doesn't match the definition.

Oracle : 2 column names for a single column

There is a requirement to rename the DB tables and column names,
so all the tools/application taking data from the source will have to change their queries. The solution we are planning to implement is that for every table name change we will create a VIEW with the original table name. Easy and simple to implement. No query change required, but there are cases where a table name remains the same but a column name changes within the table, so we can't create another view (any object with the same object name).
Is there a Column Synonym kind of thing which we can propose here?
Any solutions/ideas are welcome. Requirement is to have queries containing original column names referring to the new columns in the same tables.
For example:
Table Name: DATA_TABLE
Existing Column Name: PM_DATE_TIME
New Column Name: PM_DATETIME
Existing Query select pm_Date_time from Data_Table; should refer to new column pm_Datetime
You could consider renaming your original table, and then create a View in its place providing both the old and the new column-names:
CREATE TABLE Data_Table ( pm_Date_time DATE );
ALTER TABLE Data_Table RENAME TO Data_Table_;
CREATE VIEW Data_Table AS
(
SELECT pm_Date_time,
pm_Date_time AS pm_Datetime -- Alias to provide the new column name
FROM Data_table_
);
-- You can use both the old columnn-name...
INSERT INTO Data_Table( pm_Date_time ) VALUES ( SYSDATE );
-- ... or the new one
UPDATE Data_Table SET pm_Datetime = SYSDATE;
There are things that won't work the same way as before:
-- INSERT without stating column-names will fail.
INSERT INTO Data_Table VALUES ( SYSDATE );
-- SELECT * will return both columns (should not do this anyway)
SELECT * FROM Data_Table
Once you are done with your changes drop the view and rename the table and the columns.
You'll want to add virtual columns:
ALTER TABLE Data_Table ADD pm_Date_time as (pm_Datetime);
UPDATE: Oracle (11g at least) doesn't accept this and raises "ORA-54016: Invalid column expression was specified". Please use Peter Lang's solution, where he pseudo-adds zero days:
ALTER TABLE Data_Table ADD (pm_Datetime + 0) AS pm_Date_time;
This works like a view; when accessing pm_Date_time you are really accessing pm_Datetime.
Rextester demo: http://rextester.com/NPWFEW17776
And Peter is also right in this point that you can use it in queries, but not in INSERT/columns or UPDATE/SET clauses.
This was basically touched on in the answer by Thorsten Kettner, but what your looking for is a pseudocolumn.
This solution looks a little hacky because the syntax for a pseudocolumn requires an expression. The simplest expression I can think of is the case statement below. Let me know if you can make it more simple.
ALTER TABLE <<tablename>> ADD (
<<new_column_name>> AS (
CASE
WHEN 1=1 THEN <<tablename>>.<<old_column_name>>
END)
);
This strategy basically creates a new column on the fly by evaluating the case statement and copying the value of <old_column_name> to <new_column_name>. Because you are dynamically interpolating this column there is a performance penalty vs just selecting the original column.
One gotcha here is that this will only work if you are duplicating a column once. Multiple pseudocolumns cannot contain duplicate expressions in Oracle.
we cant create a another view (any object with the same object name).
That's true within a schema. Another somewhat messy approach is to create a new user/schema with appropriate privileges and create all your views in that, with those querying the modified tables in the original schema. You could include instead-of triggers if you need to do more than query. They would only need the old columns names (as aliases), not the new ones, so inserts that don't specify the columns (which is bad, of course) would still work too.
You could also create synonyms to packages etc. in the original schema if the applications/tools call any and their specifications haven't changed. And if they have changed you can create wrapper packages in your new schema.
Then your legacy tools/applications can connect to that new schema and if it's all set up right will see things apparently as they were before. That could potentially be done by setting current_schema, perhaps through a login trigger, if the way they connect or the account they connect to can't be modified.
As the tools and applications are upgraded to work with the new table/column names they can switch back to the original schema.

Refresh view in HSQL

I would like to update a view in HSQL without writing the same statement again and again.
I have a table CONTACTS with ID, First_NAME and LAST_NAME. I also have a VIEW for this table, which I created with
CREATE VIEW IDGREATERTHREE AS SELECT * FROM CONTACTS WHERE ID > 3;
How can I update my VIEW after I added a new column to my table. I want to update my table without anything like this:
ALTER VIEW IDGREATERTHREE AS SELECT * FROM CONTACTS WHERE ID > 3;
I would like to find a way to refresh my invalid view in a similar way like in Oracle:
ALTER VIEW IDGREATERTHREE COMPILE;
I am also looking for a way to select just the invalid views. WithSELECT * FROM INFORMATION_SCHEMA.VIEWS I am not able to see any difference between an invalid and a non-invalid view.
A solution for this would be to write an ON DDL trigger.
In this ON DDL trigger , you check if your modifying your table.
If this is the case, then you use Dynamic SQL to recreate your view. This is doable with plsql (you tagged with oracle). There is ample documentation on creating triggers and dynamic SQL on the Internet.
HSQLDB cannot have invalid views. When you create a view, the SELECT * FROM CONTACTS is expanded to the actual column names. When you add a column to the table the view is recompiled with the original column names and the new column is not included.

SQL.Working with views

I have a table 'Goods' with different information about goods (name, price, etc). I need to create a view at the same scheme as table 'Goods' has. But the view must be empty. And when user adds new good to the view it is saved in table 'Goods', but the view remains empty when user opens it next time. So main idea is not to show existing data to the user which has access to the view.
Assuming your on a database system that supports a concept like SQL Server's CHECK OPTION, and you're allowed to create a view that doesn't have that option set, you should be fine:
create table T (ID int not null)
go
create view V
as
select * from T where 1=0
go
select * from V
go
insert into V(ID) values (10)
go
select * from V
go
select * from T
The two selects from V return 0 rows. The select from T returns one row:
ID
----
10
CHECK OPTION:
Forces all data modification statements executed against the view to follow the criteria set within select_statement. When a row is modified through a view, the WITH CHECK OPTION makes sure the data remains visible through the view after the modification is committed.
And you want the opposite - you want to allow data modifications performed through the view to create rows which are invisible through the view.
Create table Goods1 with "insert trigger" on it which make insert into Goods and delete from Goods1
As far as I know this isn't possible. The whole point of a view is that it is a view to a table or grouping of tables, ie. it must show the data that matches the view.
http://www.w3schools.com/sql/sql_view.asp
What you could do is create another table called GoodsView and add a trigger to it to INSERT into Goods table and DELETE from GoodsView afterwards.
http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Fsqlp%2Frbafysqltrig.htm

Delete a column from a table without changing the environment

I'm working on Oracle SQL database, quite big database. One of (among 150 tables) this table has to be changed because it's redundant (it can be generated through a join). I have been asked to delete a column from this table, to get rid of the redundancy. The problem is that now I have to change code everywhere someone made a insert/update/etc on this table (and don't forget the constraint!). I thought "I can make a view that do the right join" so the problem it's solved for all the select, but it's not working for the insert, because I'm updating 2 tables... Is there a way to solve this problem?
My goal is to rename my original table original_table in original_table_smaller (with one less column) and create a view (or something like a view) called original_table that work like the original table.
Is this possible?
As your view will contain one column that is not present in the real table, you will need to use an instead of trigger to make the view updateable.
Something like this:
create table smaller_table
(
id integer not null primary key,
some_column varchar(20)
);
create view real_table
as
select id,
some_column,
null as old_column
from smaller_table;
Now your old code would run something like this:
insert into real_table
(id, some_column, old_column)
values
(1, 'foo', 'bar');
which results in:
ORA-01733: virtual column not allowed here
To get around this, you need an INSTEAD OF trigger:
create or replace trigger comp_trigger
instead of insert on smaller_table
begin
insert into old_table
(id, some_column)
values
(:new.id, :new.some_column);
end;
/
Now the value for the "old_column" will be ignored. You need something similar for updates as well.
If your view contains a join, then you can handle that situation as well in the trigger. Simply do an update/insert according to the data to two different tables
For more details and examples, see the manual
http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#i1006376
It is possible to insert/update on views.
You might want to check with USER_UPDATABLE_COLUMNS which columns you can insert to the view.
Check with this query:
select * from user_updatable_columns where table_name = 'VIEW_NAME';
Oracle has two different ways of making views updatable:-
The view is "key preserved" with respect to what you are trying to update. This means the primary key of the underlying table is in the view and the row appears only once in the view. This means Oracle can figure out exactly which underlying table row to update OR
You write an instead of trigger.