1

I've searched the forums and while I see similar posts, they only address pieces of the full query I need to formulate (array_aggr, where exists, joins, etc.). If the question I'm posting has been answered, I will gladly accept references to those threads.

I did find this thread ...which is very similar to what I need, except it is for MySQL, and I kept running into errors trying to get it into psql syntax. Hoping someone can help me get everything together. Here's the scenario:

Attribute

attrib_id | attrib_name

UserAttribute

user_id | attrib_id | value

Here's a small example of what the data looks like:

Attribute

attrib_id | attrib_name
-----------------------
1         | attrib1
2         | attrib2
3         | attrib3
4         | attrib4
5         | attrib5

UserAttribute -- there can be up to 15 attrib_id's/value's per user_id

user_id | attrib_id | value
----------------------------
101     | 1         | valueA
101     | 2         | valueB
102     | 1         | valueC
102     | 2         | valueD
103     | 1         | valueA
103     | 2         | valueB
104     | 1         | valueC
104     | 2         | valueD
105     | 1         | valueA
105     | 2         | valueB

Here's what I'm looking for

Result

user_id    | attrib1_value | attrib2_value
--------------------------------------------------------
101        | valueA        | valueB
102        | valueC        | valueD
103        | valueA        | valueB
104        | valueC        | valueD
105        | valueA        | valueB

As shown, I'm looking for single rows that contain: - user_id from the UserAttribute table - attribute values from the UserAttribute table

Note: I only need attribute values from the UserAttribute table for two specific attribute names in the Attribute table

Again, any help or reference to an existing solution would be greatly appreciated.


UPDATE:

@ronin provided a query that gets the results desired:

SELECT ua.user_id
      ,MAX(CASE WHEN a.attrib_name = 'attrib1' THEN ua.value ELSE NULL END) AS attrib_1_val
      ,MAX(CASE WHEN a.attrib_name = 'attrib2' THEN ua.value ELSE NULL END) AS attrib_2_val
  FROM UserAttribute ua
  JOIN Attribute a ON (a.attrib_id = ua.attrib_id)
  WHERE a.attrib_name IN ('attrib1', 'attrib2')
  GROUP BY ua.user_id;

To build on that, I tried to add some 'LIKE' pattern matching within the 'WHEN' condition (against the ua.value), but everything ends up as the 'FALSE' value. Will start a new question to see if that can be incorporated if I cannot figure it out. Thanks all for the help!!

Community
  • 1
  • 1
vmoralito
  • 43
  • 10
  • What errors did you get? What was the query you tried? Please edit your question to show these things – Bohemian Feb 11 '15 at 00:01
  • Sounds like you want some sort of pivot table. – Andreas Feb 11 '15 at 00:03
  • I'm not sure I understand.. if there are 15 attributes to a customer, do you need 15 columns? – Joe Love Feb 11 '15 at 04:16
  • @Bohemian - I was getting group by and invalid reference errors. I was going the nested subqueries/joins route without luck. – vmoralito Feb 11 '15 at 04:30
  • @JoeLove - I didn't need all 15, just 2 of the attributes. – vmoralito Feb 11 '15 at 04:31
  • Sorry folks, I thought I'd get emails when someone posted comments/answers. @ronin 's answer has got me to a pretty good starting point. I'd like to be able to filter the results within the sql query, but I could just do it in the rest of the code. – vmoralito Feb 11 '15 at 04:31
  • Well, I think that a join to the table twice would be a ton easier. – Joe Love Feb 11 '15 at 04:52
  • Since your original question has been answered properly, it might be best to start a *new* question. Otherwise you might void the honest effort of answerers and make it confusing for everyone. effort You can always link to this one for context. – Erwin Brandstetter Feb 12 '15 at 01:15
  • @vmoralito Yeah, probably better off posting another question. You can use `LIKE` in the `CASE` statement: `CASE WHEN a.attrib_name LIKE 'attrib%1' THEN ua.value ELSE NULL END`, but on the condition, not the value. – Glenn Feb 12 '15 at 03:18

3 Answers3

3

If each attribute only has a single value for a user, you can start by making a sparse matrix:

SELECT user_id
      ,CASE WHEN attrib_id = 1 THEN value ELSE NULL END AS attrib_1_val
      ,CASE WHEN attrib_id = 2 THEN value ELSE NULL END AS attrib_2_val
  FROM UserAttribute;

Then compress the matrix using an aggregate function:

SELECT user_id
      ,MAX(CASE WHEN attrib_id = 1 THEN value ELSE NULL END) AS attrib_1_val
      ,MAX(CASE WHEN attrib_id = 2 THEN value ELSE NULL END) AS attrib_2_val
  FROM UserAttribute
  GROUP BY user_id;

In response to the comment, searching by attribute name rather than id:

SELECT ua.user_id
      ,MAX(CASE WHEN a.attrib_name = 'attrib1' THEN ua.value ELSE NULL END) AS attrib_1_val
      ,MAX(CASE WHEN a.attrib_name = 'attrib2' THEN ua.value ELSE NULL END) AS attrib_2_val
  FROM UserAttribute ua
  JOIN Attribute a ON (a.attrib_id = ua.attrib_id)
  WHERE a.attrib_name IN ('attrib1', 'attrib2')
  GROUP BY ua.user_id;
Glenn
  • 8,932
  • 2
  • 41
  • 54
  • This is a great starting point! Not sure I would've gotten here on my own. How much more complex would it be to filter based on the attrib_name in the Attribute table (as the ID may differ between the db instances I'm querying)? Also, to add an "if value contains 'string', return this value instead"? – vmoralito Feb 11 '15 at 04:40
  • I tried to add some 'LIKE' pattern matching within the 'WHEN' condition (against the ua.value), but everything ends up as the 'FALSE' value. Is it possible to even do that in this case? If not, I'll deal with the regex within the PHP code. – vmoralito Feb 11 '15 at 15:39
  • @vmoralito: Almost *anything* is possible. But please don't ask questions in *comments*. Update your question or just start a new one. – Erwin Brandstetter Feb 11 '15 at 20:33
1

Starting with Postgres 9.4 you can use the simpler aggregate FILTER clause:

SELECT user_id
      ,MAX(value) FILTER (WHERE attrib_id = 1) AS attrib_1_val
      ,MAX(value) FILTER (WHERE attrib_id = 2) AS attrib_2_val
FROM   UserAttribute
WHERE  attrib_id IN (1,2)
GROUP  BY 1;

For more than a few attributes or for top performance, look to crosstab() from the additional module tablefunc (Postgres 8.3+). Details here:

Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • I'm locked in on 9.1.3 (vendor supported) for now, but thanks for the comment. I'll keep the references marked. – vmoralito Feb 11 '15 at 15:40
0

What about something like this:

select ua.user_id, a.attrib_name attrib_value1, a2.attrib_name attrib_value2
from user_attribute ua
left join attribute a on a.atribute_id=ua.attribute_id and a.attribute_id in (1,2)
left join user_attribute ua2 on ua2.user_id=ua.user_id and ua2.attribute_id > ua.attribute_id
left join attribute a2 on a2.attribute_id=ua2.attribute_id and a2.attribute_id in (1,2)
Joe Love
  • 5,594
  • 2
  • 20
  • 32
  • Gave this a shot after tweaking to match actual table names, but I get this error: ERROR: column a.value does not exist LINE 1: select ua.user_id, a.value attrib_value1, a2.attrib_value_2 – vmoralito Feb 11 '15 at 05:09
  • Had to add some commas in the SELECT, and re-name tables again, but now I get this: ERROR: column a.atribute_id does not exist LINE 3: left join ccs_attribute a on a.atribute_id=ua.attribute_id a... – vmoralito Feb 11 '15 at 15:06
  • I had to make a few more edits trying to get this to work. This query executes, but I do not get expected results: `select ua.user_id, a.name, ua.value, a2.name, ua2.value from ccs_user_attribute ua left join ccs_attribute a on a.attribute_id=ua.attribute_id and a.attribute_id in (1,2) left join ccs_user_attribute ua2 on ua2.user_id=ua.user_id and ua2.attribute_id > ua.attribute_id left join ccs_attribute a2 on a2.attribute_id=ua2.attribute_id and a2.attribute_id in (1,2);` This also locks me into knowing the attrib_id, which does vary between instances. – vmoralito Feb 11 '15 at 16:01
  • Trying to figure out what you're doing here `select ua.user_id, a.attrib_name attrib_value1, a2.attrib_name a2.attrib_value_2 ` as I get a syntax error. Note that "attrib_value1" does not exist as a column anywhere, that's just what I wanted the results table to show. – vmoralito Feb 11 '15 at 16:11
  • I had a typo-- fixed. Perhaps if we had an SQLfiddle to play with it'd be easier. Hopefully this one works out, I'm just doing a couple of joins to the same table to get each attribute. Sorry for the long delay in response. – Joe Love Feb 12 '15 at 15:05