问题
Basically I have users with a dynamic attributes table. Simplified:
SELECT * FROM users;
id | email
-----+------------------------------------------
1 | example@example.com
SELECT * FROM user_attributes;
id | name
----+----------------
1 | Salutation
2 | Given Name
3 | Surname
4 | Alias
5 | Address
6 | Address 2
7 | Address 3
8 | City
9 | Region
....
SELECT * FROM user_attribute_values;
client_id | attribute_id | value
-----------+--------------+-------
What I'm looking to do is a SELECT that would return columns user_id, city, region where city & region are not empty.
The reason for the user_attributes table is one may want to store any number of custom fields about the user, and it's impossible to know beforehand what they will be to create them as columns of the user table.
回答1:
Use INNER JOIN
for that:
SELECT u.id, a_city.value AS city, a_region.value AS region
FROM users u
INNER JOIN user_attribute_values a_city ON a_city.client_id = u.id AND a_city.attribute_id = 8
INNER JOIN user_attribute_values a_region ON a_region.client_id = u.id AND a_region.attribute_id = 9
WHERE LENGTH(a_city.value) > 0
AND LENGTH(a_region.value) > 0
回答2:
This is based on a principal misunderstanding of the inner workings of Postgres and EAV designs.
If you don't have hundreds of different fields or a dynamic set of attribute types, use a single table with all columns - except for database normalization. Columns without value are filled with NULL
.
Null storage is very cheap.
- 1 bit per column in the table for the null bitmap, typically allocated in units of 8 bytes which covers 64 columns.
A separate row for a single additional attribute occupies at least an additional 28 bytes.
4 bytes item pointer 23 bytes heap tuple header 1 byte padding
Typically more, due to padding and additional overhead.
There would have to be hundreds of different, sparsely populated columns before such an unwieldy EAV design could pay - and hstore or jsonb in Postgres 9.4 would be superior solutions for that. There is hardly any room in between for your design, and if there was, you'd probably be using an enum for the type.
At the same time, queries are more complicated and expensive. We're in a tight spot here.
Instead use a table layout like this:
CREATE TABLE users (
users_id serial PRIMARY KEY
, salutation text
, given_name text
, surname text
, alias text
... (many) more columns
);
CREATE TABLE address (
address_id serial PRIMARY KEY
, users_id int REFERENCES users
, city text -- or separate TABLE city incl region_id etc. ...
, region_id int REFERENCES region
, address text
... (many) more columns
);
Closely related answer with more advice:
- SQL : Create a full record from 2 tables
回答3:
select client_id,
min(case when attribute_id = 8 then value else '0' end) as city,
min(case when attribute_id = 9 then value else '0' end) as region
from user_attribute_values
group by clientid
having min(case when attribute_id = 8 then value else '0' end) <> '0'
or min(case when attribute_id = 9 then value else '0' end) <> '0'
This will show clients with a city or region value. If you want only clients with BOTH such attributes, in the having clause, change the OR to an AND.
来源:https://stackoverflow.com/questions/25704086/is-it-possible-to-name-sql-result-columns-from-rows-in-another-table-postgres