0

I have an attribute-value table av that looks like this:

| attribute | value |
~~~~~~~~~~~~~~~~~~~~~
| a1        | A1    |
| b1        | BB1   |
| b2        | BB2   |

For simplicity, assume varchar(255) on both attribute and value columns, unique index on attribute.

I need to use the values of specific attributes in a query, which looks like this:

SELECT *
FROM   t1
      ,t2
WHERE  t1.a1 = "A1"  -- Value of "a1" attribute
 AND   t1.id = t2.id
 AND   t2.b1 = "BB1"  -- Value of "b1" attribute
 AND   t2.b2 = "BB2"  -- Value of "b2" attribute

Is there an elegant way of doing this in Sybase ASE (12 or 15) which scales well as we increase the # of tables and attributes?

By "scale" I mean ~10-20 attributes needed across 4-5 joined tables

I can think of the following solutions, all of which seem to suck:


SOLUTION 1: Obvious: Join AV table, once per attribute

SELECT *
FROM   t1
      ,t2
      ,av AS 'av_a1'
      ,av AS 'av_b1'
      ,av AS 'av_b2'
WHERE  t1.a1 = av_a1.value
 AND   t1.id = t2.id
 AND   t2.b1 = av_b1.value
 AND   t2.b2 = av_b2.value
 AND   av_a1.attribute = "a1"
 AND   av_b1.attribute = "b1"
 AND   av_b2.attribute = "b2"

Pros: Obvious.

Cons: Scales VERY poorly as far as code quality, and probably performance as well.


SOLUTION 2: Avoid the headache of multiple joins with variables

declare @a1 varchar(255)
select  @a1 = value FROM av WHERE attribute = "a1"
declare @b1 varchar(255)
select  @b1 = value FROM av WHERE attribute = "b1"
declare @b2 varchar(255)
select  @b2 = value FROM av WHERE attribute = "b2"

SELECT *
FROM   t1
      ,t2
WHERE  t1.a1 = @a1
 AND   t1.id = t2.id
 AND   t2.b1 = @b1
 AND   t2.b2 = @b2

Pros: No more extra joins making the query both ugly and poorly performing.

Cons: Scales somewhat poorly as far as code quality (need to add new variables with new attributes).


Any better solutions?

Danny Beckett
  • 20,529
  • 24
  • 107
  • 134
DVK
  • 126,886
  • 32
  • 213
  • 327

2 Answers2

1

I'm not sure what the additional clauses in the where statement are for (comparing values in one table to the attributes in the other). The following flattens the attributes before the join:

SELECT *
FROM   t1 join
       t2
       on t1.id = t2.id join
       (select av.id,
               MAX(case when av.attribute = 'a1' then av.value end) as a1,
               MAX(case when av.attribute = 'b1' then av.value end) as b1,
               MAX(case when av.attribute = 'b2' then av.value end) as b2
        from av
        group by av.id
       ) attr
       on attr.id = t1.id

This works, assuming there are no duplicates in the attributes -- which there generally are not when using an attribute table. You can add back in the where conditions, if you like, I just didn't understand why they were there.

Also, you should switch to ANSI standard join syntax.

If you don't have an id, you can do essentially the same thing:

SELECT *
FROM   t1 join
       t2
       on t1.id = t2.id cross join
       (select MAX(case when av.attribute = 'a1' then av.value end) as a1,
               MAX(case when av.attribute = 'b1' then av.value end) as b1,
               MAX(case when av.attribute = 'b2' then av.value end) as b2
        from av
       ) attr
       on attr.id = t1.id
where <whatever you want>
Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
  • Hmm... so in essence you seem to be self-pivoting AV table inplace? – DVK Jan 22 '13 at 22:15
  • @DVK . . . Yes. That is a good way of describing it. I supposed I could have used the actual `pivot` statement, but this version works in any database. – Gordon Linoff Jan 22 '13 at 22:16
  • But the query is wrong - AB has no ID to match to t1/t2 table. Simply 10 rows for 10 distinct attrinutes. – DVK Jan 22 '13 at 22:16
  • @DVK . . . The structure of the data seems weird. You have the values as columns in other tables, and yet you keep a separate list of them row-wise. Are these some sort of global options that you are trying to compare to other tables? – Gordon Linoff Jan 22 '13 at 22:25
  • Pretty much. They aren't quite "global", just a set of configuration attributes that was retrieved from a bigger table with multiple configurations. E.g. think of table 1 being shipping prices per shipping type, and "a1" is a type of shipping a specific account is set up to use. table 2 is tax rates per location, and "b1" is location of the account. etc.... (it's kind of a poor example as far as business picture but I prefer not to give our actual business purpose - suffice it to say that the query DOES make perfect sense) – DVK Jan 22 '13 at 22:33
0

Using the Entity-Attribute-Value design is fundamentally non-relational, so it's bound to be awkward and inefficient to query it in SQL as if the rows describe attributes of one logical entity.

To ease the cost of doing this in SQL, I frequently recommend to fetch all the rows as they are stored in the database, and then apply attributes to entity instances in application code, one row at a time.

Here's another SO question with example PHP code demonstrating what I mean:
Create a summary result with one query


Re your comment and downvote:

You're shooting the messenger here.

You asked:

Is there an elegant way of doing this in Sybase ASE (12 or 15) which scales well as we increase the # of tables and attributes?

Every method of querying multiple rows of EAV data into one row of result set, whether by joins or by pivoting, requires that you know the set of attributes, and these are fixed at the time you prepare the query. Because SQL is based on the relational model, the columns of a result-set cannot expand dynamically as the query executes.

Therefore, if you have a data model where the number of tables and attributes expands from time to time, you'll find yourself changing your pivot-query code every time you add an attribute.

You can generate the pivot-query dynamically based on the number of distinct attributes, but that also requires application code, because you need to query the current attributes and then build a query.

The alternative is to fetch all the data, one attribute per row of result set, and write code to "reassemble" them into logical entities on the application side.

Dynamic pivot queries always require application code -- either before or after the query.

I'm sorry you don't like that answer.

Community
  • 1
  • 1
Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • Sorry, but this wasn't a question on how to correctly design my database tables. I'm writing queries against existing ones, so this answer doesn't help **at all**. Neither does "do it in app layer" - if I would have such an option, I wouldn't be asking the question, with all due respect. – DVK Jan 22 '13 at 23:10