0

I have a large entity-attribute-value like a table. I try to select some rows from this table with a subquery and then filtrate with rows. How can I prevent merging subquery and main query in this situation?

For example:

EMP:
EMPNO | ENAME  | SAL
---------------------
1000  | KING   | 10000
1001  | BLAKE  | 7500

CREATE VIEW EAV(ID,ATTR,VALUE) AS
select empno, 'name'||ename, ename from emp -- subquery 1
union
select empno, 'sal'||ename, ename from emp -- subquery 2
union
select empno, 'mgr'||ename, ename from emp -- subquery 3

NOTE: ||ename was added just to prevent Oracle to optimize next queries by adding filter "(null is not null)" to subquery 1 and 3

In subquery I select all rows with attribute 'sal%' and then filtrate it in the main query:

select *
FROM (select id,value from EAV where attr like 'sal%')
WHERE to_number(value) > 5000;

This query fall cause optimizer merge subquery with outer query. After merging DB try to apply to_number to all values in column "value", but some of it has a string value. Witch HINT prevent this optimization?

p.s. I want to get same result as

WITH t as (
   select /*+ materialize */ id,value
   from eav
   where attr like 'sal%') 
select * from t where to_number(value) > 5000;

but, without CTE.

Patrick Bacon
  • 4,490
  • 1
  • 26
  • 33
potapuff
  • 1,839
  • 4
  • 18
  • 36

2 Answers2

1

ROWNUM is the safest way to prevent optimizer transformations and ensure type safety. Using ROWNUM makes Oracle think the row order matters, and prevents things like predicate pushing and view mergning.

select *
from
(
   select id, value, rownum --Add ROWNUM for type safety.
   from eav
   where attr like 'sal%' 
)
where to_number(value) > 5000;

There are other ways to do this but none of them are reliable. Don't bother with simple inline views, common table expressions, CASE, predicate ordering, or hints. Those common methods are not reliable and I have seen them all fail.


The best long-term solution is to alter the EAV table to have a different column for each type, as I describe in this answer. Fix this now or future developers will curse your name when they have to write complex queries to avoid type errors.

Community
  • 1
  • 1
Jon Heller
  • 34,999
  • 6
  • 74
  • 132
  • ", rownum --Add ROWNUM for type safety." -amazing! it's like a magic – potapuff May 25 '16 at 06:41
  • it is no real solution, but just a part of a task for student. In this task students try to transform plain table to EAV and similar meta-model with Object-Object_types-Attributes-Params and try to write queries upon this data-views. – potapuff May 25 '16 at 06:48
  • In other hand I have similar problem in the real PostgreSQL-based project with plain tables. In one of table I have filed where user can place link to nodes. Some links can be casted to number and linked to internal table, but other links is URL so I can't cast it. – potapuff May 25 '16 at 06:54
0

I doubt your problem really has anything to do with the optimizer. At least in your example, VALUE is set to ENAME for all three atttributes. That's fine for the "name" attribute, but for "sal" it should probably be SAL. For "mgr", I have no clue as your example doesn't provide enough information.

I'd also recommend removing the "||ename" part, again on the assumption that the optimizer is not the problem.

Lastly, change the UNIONs to UNION ALLs, if EMPNO is your primary key on EMP. UNION attempts to reduce the results to unique rows, which is unnecessary processing if they're already unique on ID, ATTR.

Rework the view, and then "select * from EAV where ATTR = 'sal'" and confirm that what you're seeing is actually salaries. That should allow you to do to_number(ATTR) for sal with no issues.

Matthew G
  • 176
  • 1
  • 4
  • I don't think the `CREATE VIEW ...` is the real problem, that's just the code used to create a test. The last query is the important one. – Jon Heller May 24 '16 at 23:06
  • "At least in your example, VALUE is set to ENAME for all three atttributes." - It is added just for this sample. ENAME added to attribute name just for preventing other Oracle optimization, where DB skip same of subquery by adding "(null is not null)" filter. – potapuff May 25 '16 at 06:58
  • Executing of "select * from EAV where ATTR = 'sal' and to_number(value) > 5000" - is a initial issue for this question. By design all DB when apply filter (in our case ATTR = 'sal' and to_number(value) > 5000 ) do it for all parts of filter and all rows, even executing a part of filter doesn't make any change to result. For example with filter "( 1=1 or very_heavy_function())" very_heavy_function() will be executed for each rows. In our case to_number will be executed to all rows, but must be executed only to ATTR = 'sal'. – potapuff May 25 '16 at 11:42