Can not use ORDER BY when creating materialized view with SDO geometry in Oracle 11g
Asked Answered
G

1

6

I am using Oracle 11g 2.0.1.0 with Spatial and Oracle SQL Developer on the client. I have a table Places with primary key ID and a view Coordinates with tw columns: ID referencing a post in Places, and the SDO geometry Point.

I want to create a materialized view with the following SQL:

CREATE MATERIALIZED VIEW PlaceCoordinates
NOCACHE NOPARALLEL BUILD IMMEDIATE
USING INDEX
REFRESH ON DEMAND COMPLETE 
DISABLE QUERY REWRITE  AS
SELECT Places.ID, Coordinates.Point
FROM Places
LEFT OUTER JOIN Coordinates ON Places.ID = Coordinates.ID
ORDER BY Places.ID

This gives me this error:

ORA-30373: object data types are not supported in this context

No matter what I sort on (even if it is just something silly like 1) I get the same error. However, if I remove the ORDER BY statement it works fine. It also works fine with sorting if I just do an ordinary SELECT without creating a materialized view.

Why wouldn't I be able to sort? Is there anyway around this problem?

Glassco answered 14/7, 2015 at 14:50 Comment(6)
When you order by 1 you're still referencing places.ID, just by position. Does it let you order by ID without the table prefix?Sanctimonious
Also see MOS document 1939208.1, which is for 12c but this might be something related; or 1067173.1. Are you ordering for physical clustering or to avoid having to have an order by when you query the MV - which wouldn't be guaranteed to work?Sanctimonious
Thank you for your comment. Do you know why I can not reference Places.ID? I will test without table prefix when I get back to work tomorrow.Glassco
I doubt it will work as just ID if the positional reference doesn't, but perhaps worth trying. Aside from those two docs I mentioned, no, I'm afraid not.Sanctimonious
I posted my comment before I had seen your new one. Thank you for the links - I will check them out. I am ordering to not have to order by when querying the MV. I must admit that I did not know that it was not guarantedd to work, so I guess that means I no longer need to get this to work. Again, thank you for your help.Glassco
Obligatory link to Tom Kytes blog. Even if it seems to work today without an order by when you query the MV, it might not one day.Sanctimonious
A
9

The key thing is that an ORDER BY in a materialized view makes no sense.

Under the covers, a materialized view is really just a table that gets automatically updated when the tables it is based on get updated. But being a table means that no ordering can ever be guaranteed. Even though the initial MV gets stored in the desired order, there is no guarantee it will remain so after updates are applied. The only way to make sure you get the results in the proper order is to use an explicit ORDER BY when you select from the MV.

You can include an ORDER BY into a view (not a materialized view), and that will be applied when you do use that view: selects from the view then do not need any ORDER BY. But this is a VERY BAD practice. It means that applications may unknowingly depend on some assumed order provided by a view - until someone decides to remove the ORDER BY from the view and all hell breaks loose.

The key is: if an application requires a result in a specific order, then it must say so in the SELECTs it issues, by including an ORDER BY.

That said, looking at your MV definition, it looks like it will never be updated as changes take place on the base tables (PLACES and COORDINATES): you say that it is "REFRESH ON DEMAND COMPLETE". In other words you (or some automatic process) triggers a full refresh at regular intervals. This is exactly the same as creating a new table. You might as well do this:

CREATE TABLE PlaceCoordinates AS
SELECT Places.ID, Coordinates.Point
FROM Places
LEFT OUTER JOIN Coordinates ON Places.ID = Coordinates.ID;

and run this every time you want to refresh your PLACECOORDINATES table (after dropping the old table). It will be simpler and more efficient than the MV machinery. Another approach is to create the table once, then truncate and fill it when necessary:

CREATE TABLE PlaceCoordinates (
  ID NUMBER PRIMARY KEY,
  Point SDO_GEOMETRY
);

and

TRUNCATE TABLE PlaceCoordinates;
INSERT INTO PlaceCoordinates (ID, Point)
SELECT Places.ID, Coordinates.Point
FROM Places
LEFT OUTER JOIN Coordinates ON Places.ID = Coordinates.ID;

That lets you specify that ID is a primary key - always a good idea. And of course, don't forget to define the proper spatial index on the POINT column (assuming you want to show the points on a map or query them). Good practice then is to first drop that index before you refresh the content and recreate it afterwards (you need that also for the MV approach).

Whatever the chosen approach (MV as you specify it, or table), PLACECOORDINATES will not reflect the real-time state of the PLACES and COORDINATES tables. It will only reflect the state at the last time you manually fully refreshed the MV or re-loaded the table. If this is acceptable, then you are all set.

If you want that PLACECOORDINATES is closer to the state of the other two tables, without having to fully refresh/reload it, say every minute, then you need to define the MV so it is refreshed only from the changes in the source tables. That means that you need a MATERIALIZED VIEW LOG on those tables where changes will be recorded in order to be applied on the MV. That however will only happen at the intervals you specify or you when manually request a refresh. But reasonably not more than every minute. Certainly not every second.

If PLACECOORDINATES must reflect all changes in PLACES and COORDINATES as they happen (= in "real time"), then the only way to guarantee that is to make it a table and have triggers on PLACES and COORDINATES automatically apply changes on those tables on PLACECOORDINATES as they happen.

Maybe in that case you are better off just reading directly from the base tables.

Algometer answered 15/7, 2015 at 17:44 Comment(4)
Thank you for a very thourough response. You are right in that the MV will be updated on a schedule and not always reflect the current state of the tables, but that is alright for my purposes. I think I will simply take the performance hit of sorting every time I query the MV instead of when I create it - that seem to be the right thing to do.Glassco
"This is exactly the same as creating a new table" - except that dropping and recreating the table invalidates anything that refers to it, you have to recreate grants and indexes, and anything that tries to look at it while it's being rebuilt will error. The docs also say you can order by during the initial creation but it's ignored on refresh, so not quite sure what theoint of that is. (And is guaranteed that an order by in a view is alwasy going to be reflected whenyou query it?)Sanctimonious
What I meant to say is that having a MV always fully refreshing on demand is no different than having a plain table. There is no operational benefit whatsoever. And as for recreating indexes after a drop/create, that is likely to be faster than updating the indexes by doing a delete/insert (which is what a full refresh essentially does). Then again, a truncate+insert retains all indexes (and all other dependencies).Algometer
The order by in a view will always be performed for simple selects (SELECT ... FROM ... WHERE). Surprisingly, it is also reflected for simple joins. But then again: DON'T DO THAT. Do NOT include an ORDER BY in a view. Always use ORDER BY explicitly in the queries.Algometer

© 2022 - 2024 — McMap. All rights reserved.