Postgres

to_json(anyelement)    

disclaimer: I am terrible at writing SQL

Chase Gilliam

Postgres Supports JSON Serialization at the DB Level

  • Some functions in 9.2 +
  • Faster than AR queires + to_json
  • Easy(ish) to use
  • Offers some guarantees/validations 
  • SQL is declarative
  • Postgres has a query planner

As easy as...

For simple examples you can simply use a nested select, but the benefits similarly small.

select row_to_json(p)
from (
  select 
    name, 
    phone_number, 
    birthdate, 
    hair_color, 
    height, 
    weight 
  from people
) p

But We Can Do Better

Associations are where we see it start to pay off.

select row_to_json(person)
    from (
      select name, phone_number, 
        birthdate, hair_color, 
        height, weight, 
        date_part('year',age(birthdate)) as age,
        (
          select COALESCE(array_to_json(array_agg(row_to_json(a))), '[]')
          from (
            select DISTINCT street, unit, city, state, zip
            from addresses
            where person_id=people.id
          ) a
        ) as addresses
    from  people
    where id = #{id}
    ) as person
pry(main)> Person.json_associations(1)
  Person Load (1.1ms)      select row_to_json(person)
    from (
      select name, phone_number, birthdate, 
        hair_color, height, weight, 
        date_part('year',age(birthdate)) as age,
        (
          select COALESCE(array_to_json(array_agg(row_to_json(a))), '[]')
          from (
            select DISTINCT street, unit, city, state, zip
            from addresses
            where person_id=people.id
          ) a
        ) as addresses
    from  people
    where id = 1
    ) as person

=> {"name"=>"Chase",
 "phone_number"=>"4342509824",
 "birthdate"=>"1987-02-28",
 "hair_color"=>"brown",
 "height"=>72,
 "weight"=>168,
 "age"=>29,
 "addresses"=>
  [{"street"=>"10 West Chase St.", "unit"=>"1B", 
    "city"=>"Baltimore", "state"=>"MD", "zip"=>"21201"}, 
    {"street"=>"Some Street", "unit"=>"2100", 
    "city"=>"Brooklyn", "state"=>"NY", "zip"=>"11217"}]}

Wow 1.1ms!

Alternatively

a = Address.where(person_id: 1).map(&:attributes)
Person.last.attributes.merge(addresses: a)

But that takes twice as long.

And it won't Scale ™!

How About ActiveModelSerializers?

Don't Do This! I mean, maybe if you like.

Serializers are easier to write, but slower.

serializer_options = {}
serializer = SomeSerializer.new(resource, serializer_options)

Nontrivial SQL Examples Are Far Faster!

Comparison:
sql:                           84.7 i/s
active_model_serializer:       7.1 i/s - 11.94x slower

=> #<Benchmark::IPS::Report:0x007f994da66ba0 
@entries=[
#<Benchmark::IPS::Report::Entry:0x007f994dad9e20 
@label="sql", @microseconds=5068335.294723511, 
@iterations=427, @ips=84.73684840784838, @ips_sd=7, 
@measurement_cycle=7, @show_total_time=false>, 
#<Benchmark::IPS::Report::Entry:0x007f9952426768 
@label="active_model_serializer", @microseconds=5097223.043441772, 
@iterations=35, @ips=7.0979351818635905, @ips_sd=2, 
@measurement_cycle=1, @show_total_time=false>], 
@data=nil>

Taken From Production Code

Now That I Have Convinced You!

Some Methods Worth knowing

  • coalesce() - handles empty records
  • array_to_json() - returns the array as a JSON array
  • array_agg() - turns values into an array
  • row_to_json() - turns a DB row into json
  • json_build_object() - creates JSON from variadic k,v list

DEMOS!

Thanks For Your Time

Chase Gilliam

Avhana Health

@chasegilliam | chasegilliam.io

deck

By Chase Gilliam

deck

  • 84