A Table With A View

A Quick Glance at SQL Views

During a recent mock interview I was asked if I had ever created any SQL views. First I misunderstood the question, and then understood that I had no idea what that even meant.

Jumping Cat; fail

Now, I could spend more time describing how stupid I felt at that point, but why do that when I can actually go and create such a view for practice?

As explained to me by my interviewers or as described in w3schools.com, an SQL view is a virtual table generated by the SQL server based on a custom SQL query. It presents up-to-date information on all the entries used to create said table. Such views have a few different uses:

  • Organizational tool for presenting data from complex queries comprised of table joins and/or aggregate functions.
  • Possible performance enhancement upon building a view with a unique clustered index
  • Security measure as an additional abstraction level between the actual data and the consuming applications

Slightly Unorthodox Use for an SQL View

Recently, I had run into a problem when generating JSON responses using Active Model Serializer (AMS) on associated model data. I have a game model with many locations associated to it. It turned out that every time when I updated a location row in the database, the order of the locations returned with the game instance would change. This is because Postgres returns the queried rows in an unspecified order, depending on the entry write-order on disk, among other things.

Why would I care about the order? Because the presentation of the locations on the rendered webpage is a simple for-loop. Thus, if a location’s position in the json-response changes every time a location is updated (aka another player takes control over it in the game), it will lead on its position changing on the page as well. Imagine if Ireland would have relocated from Europe to Australia on the map the minute she declared independence. Weird!

Now, if I was querying these locations straight via a locations controller, it would be easy to just add an ActiveRecord order clause to the query, eg. @locations = Location.where('game_id = ?', current_game.id).order(:id) . However, the locations associated to a game were now returned as JSON in random order via AMS by querying the game table. Thus, I found no simple way to inject the order by clause into the formation of the hash. On the other hand, building a custom method for repeated sorting of a part of a JSON hash didn’t seem worthwhile, either. After all, shouldn’t the DB be able to do this simple sorting for me?

Enter SQL view. Following the principles of setting SQL views together with Rails as described in this handy blog post, I decided to test this feature by creating a virtual ordered_locations table via a SELECT statement on the locations table with index ordering. First, a migration:

class CreateOrderedLocations < ActiveRecord::Migration[5.0]
  def up
    self.connection.execute %Q( CREATE OR REPLACE VIEW ordered_locations AS
        SELECT
            id,
            content,
            defense,
            controlled_by,
            game_id
          FROM locations
          ORDER BY id ASC;
        )
  end

  def down
    self.connection.execute "DROP VIEW IF EXISTS ordered_locations;"
  end

end

Then, the model without an actual DB table:

class OrderedLocation < ApplicationRecord
  belongs_to :game
  has_many :character_locations
  has_many :characters, through: :character_locations

  self.table_name = 'ordered_locations'
end

For the associations to work in the serializers, I added has_many :ordered_locations to app/models/game.rb, and changed the custom associated serializer:

class GameSerializer < ActiveModel::Serializer
  attributes :id, :title, :status, :turn, :winner, :map_name, :map_size, :background_image_link
  has_many :ordered_locations, serializer: CustomLocationSerializer
  # has_many :locations, serializer: CustomLocationSerializer
  has_many :game_players, serializer: CustomGPSerializer
end

And thus, an ajax get request to the games.controller #show action produces JSON with locations ordered by their primary key!

{
  game: {
    id: 1,
    title: "Purge",
    status: "active",
    turn: 2,
    winner: null,
    map_name: "Asylum",
    map_size: 8,
    ordered_locations: [
      {
        id: 1,
        defense: 145,
        controlled_by: 1
      },
      {
        id: 2,
        defense: 142,
        controlled_by: 2
      },
      {
        id: 3,
        defense: 197,
        controlled_by: 1
      },
      {
        id: 4,
        defense: 122,
        controlled_by: 1
      },
      {
        id: 5,
        defense: 58,
        controlled_by: 1
      },
      {
        id: 6,
        defense: 113,
        controlled_by: 1
      },
      {
        id: 7,
        defense: 195,
        controlled_by: 2
      },
      {
        id: 8,
        defense: 143,
        controlled_by: 2
      }
    ],
    game_players: [
      {
        game_id: 1,
        player_id: 1,
        creator: false
      },
      {
        game_id: 1,
        player_id: 2,
        creator: true
      }
    ]
  }
}

Since I’m querying the whole set of locations only through the associated game instance, nothing needed to be changed in the controllers.

While it might be a bit of an overkill to use an SQL view to just guarantee a straightforward table ordering, this simple experiment did provide a solution to my problem of preventing locations from changing position upon updating and I hope it will give some idea how handy such a feature is for organizing data resulting from considerably more complex queries.

Resources

Postgres documentation: Sorting Rows
W3Schools: SQL Views
Tutorialspoint: Using Views
Using Rails and SQL Views For a Report
Common misconceptions on SQL views
More on designing indexed views

Written on October 31, 2016