Quinn's Blog

Postgresql Array and Hstore Column Reference

April 14, 2014

First of all, let’s start with links to the relevant postgres docs:

http://www.postgresql.org/docs/9.3/static/hstore.html
http://www.postgresql.org/docs/9.3/static/arrays.html
http://www.postgresql.org/docs/9.3/static/functions-array.html

The postgres project has excellent documentation, and these links do covere everything you would need to work with hstore and arrays with postgres. Sometimes though it can be difficult to translate their examples into real world examples, and it can also be difficult to translate this into ActiveRecord.

Postgresql Arrays

Arrays in postgres in that they aren’t their own type per se, rather than it’s own separate type. Rather, the existing column types can be declared as an array (e.g. an array of ints, array of texts, etc).

Declaring array fields

Arrays can be declared in the following ways:

SQL

CREATE TABLE articles (body text, tags text[]);

ActiveRecord

create_table :articles do |t|
  t.text :body
  t.text :tags, array: true
end

That’s pretty straightforward. I’m going to go through some common operations.

Array inclusion

Sometimes you want to find out if an array includes, or does not include a given value, and return the records where it is true. Given the above table, this is what that could look like:

SQL

--- which articles are tagged with 'programming'
SELECT * FROM articles WHERE 'programming' = ANY (tags);

--- which articles are not tagged with 'programming'
SELECT * FROM articles WHERE 'programming' != ALL (tags);
--- or
SELECT * FROM articles WHERE NOT ('programming' = ANY (tags));

ActiveRecord

# which articles are tagged with 'programming'
Article.where('? = ANY (tags)', 'programming')

# which articles are not tagged with 'programming'
Article.where('? != ALL (tags)', 'programming')
# or
Article.where('NOT (? = ANY (tags))', 'programming')

Updating arrays

How do you append to an array? You could use your ORM in a predictable way:

article = Article.find(1)
article.tags << 'programming'
article.save!

Note (2014-04-16): There may be currently a bug in rails that can cause

issues with the above syntax. If so, try running article.tags_will_change!. More info on the bug here. Thanks, Dieter Komendera for pointing this out.

But what if you wanted to perform this in a single operation, or what if you wanted to perform this on multiple records at once in a single query? This is possible, using the append_array function:

SQL

UPDATE articles SET tags = array_append(tags, 'programming')

ActiveRecord

Article.update_all Article.send(:sanitize_sql, ['tags = array_append(tags, ?)', 'programming'])

This unfortunately isn’t as clean as one might like it to be in ActiveRecord. I’d love to know if there is a better way to do this.

Find empty arrays

Generally when creating an array column I think it is advisable to to make it non-null and default to an empty array:

t.text :tags, array: true, null: false, default: []

This prevents a small “gotcha” when an array column is null (not an empty array). For example, in the example above that tests for a value not being in the array field, the query would have returned a misleading empty set if all of the array columns were null. You can get around this by setting a column default, or by testing to see if the array column is null:

SELECT * FROM articles where 'programming' != ALL (tags) || tags IS NULL;

If you wanted to find all records with empty tags and null tags, you could do:

SELECT * FROM articles where tags = '{}' OR tags IS NULL;

Array intersection

It’s a common scenario to want to find records with an array field that has at least one member from a given list. You can use the && operator to find these:

SQL

SELECT * FROM articles WHERE tags && ARRAY['devops', 'design'];

--- or, if your array column is of something other than text (possibly varchar)
SELECT * FROM articles WHERE CAST (tags as text[]) && ARRAY['devops', 'design'];

ActiveRecord

Article.where('tags && ARRAY[?]', ['devops', 'design'])

Postgresql Hstore

Postgres hstore is column type for storing hierarchical schemaless data (sometimes called “documents”) in a Postgres table. This is great if you are going to be storing data you’d like to be able to query, but aren’t necessarily going to have consistent field names. Hstore is often compared to NoSQL document stores such as MongoDB.

Declaring Hstore column type

hstore is just a column type like any other, so declaring it is straightforward.

SQL

CREATE TABLE web_requests (
  url varchar,
  body text,
  headers hstore
);

ActiveRecord

create_table :web_requests do |t|
  t.string :url
  t.text :body
  t.hstore :headers
end

Setting a value to a key in hstore

Hstore keys are strings, and one of two types can be assigned to a key in hstore: a string, or another hstore value.

SQL

INSERT INTO web_requests (url, body, headers) VALUES (
  '/articles.json',
  '',
  'Accept        => application/json,
   Cache-Control => no-cache'
);

ActiveRecord

web_request = WebRequest.create({
  url: '/articles.json',
  headers: {
    'Accept'        => 'application/json',
    'Cache-Control' => 'no-cache'
  }
})

Finding a record with an hstore with a given value

These are examples of how to find records that have a key with a given value for an hstore column.

SQL

SELECT * FROM web_requests WHERE headers->'Accept' = 'application/json'

ActiveRecord

WebRequest.where("headers->'Accept' = ?", 'application/json')

Finding a record with an hstore with a non-null value for a given key

Sometimes you want to find all records that have any value set for a given key. Using our WebRequest example this could be useful for finding all requests that set caching headers. Postgresql provides a defined function for this.

SQL

SELECT * FROM web_requests WHERE defined(headers, 'Cache-Control')

ActiveRecord

WebRequest.where('defined(headers, ?)', 'Cache-Control')

Removing a key from an hstore

Rather than setting a key to an empty string or NULL, sometimes it can be nice to completely remove the value and key from hstore. Postgresql provides a delete function to this. Again, I am not aware of a clean interface for running functions in an UPDATE while using ActiveRecord, so I’m using the same hacky solution described previously for append_array.

SQL

UPDATE articles SET headers = delete(headers, 'Cache-Control') WHERE id = 1

ActiveRecord

Article.where(id: 1).update_all Article.send(:sanitize_sql, ['headers = delete(headers, ?)', 'Cache-Control'])

That’s all for now. Hopefully this helps clarify some ways of using arrays and hstore especially in practice, and when using ActiveRecord. Stay tuned for more snippets as well as examples of using postgres’s enum functionality in ActiveRecord.