diff --git a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php
index 4a0b440..868961e 100644
--- a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php
+++ b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Query.php
@@ -48,27 +48,14 @@ public function execute() {
     $entity_type = $this->entityType;
     $entity_info = entity_get_info($entity_type);
     if (!isset($entity_info['base_table'])) {
-      throw new QueryException("No base table, nothing to query.");
+      throw new QueryException("No base table, invalid query.");
     }
-    $configurable_fields = array_map(function ($data) use ($entity_type) {
-      return isset($data['bundles'][$entity_type]);
-    }, field_info_field_map());
     $base_table = $entity_info['base_table'];
-    // Assemble a list of entity tables, primarily for use in
-    // \Drupal\field_sql_storage\Entity\Tables::ensureEntityTable().
-    $entity_tables = array();
     $simple_query = TRUE;
-    // ensureEntityTable() decides whether an entity property will be queried
-    // from the data table or the base table based on where it finds the
-    // property first. The data table is prefered, which is why it gets added
-    // before the base table.
     if (isset($entity_info['data_table'])) {
-      $entity_tables[$entity_info['data_table']] = drupal_get_schema($entity_info['data_table']);
       $simple_query = FALSE;
     }
-    $entity_tables[$base_table] = drupal_get_schema($base_table);
     $sqlQuery = $this->connection->select($base_table, 'base_table', array('conjunction' => $this->conjunction));
-    $sqlQuery->addMetaData('configurable_fields', $configurable_fields);
     $sqlQuery->addMetaData('entity_type', $entity_type);
     // Determines the key of the column to join on. This is either the entity
     // id key or the revision id key, depending on whether the entity type
@@ -87,10 +74,6 @@ public function execute() {
       $revision_field = $entity_info['entity_keys']['revision'];
       $fields[$revision_field] = TRUE;
       $sqlQuery->addField('base_table', $revision_field);
-      // Now revision id is column 0 and the value column is 1.
-      if ($this->age == FIELD_LOAD_CURRENT) {
-        $id_key = 'revision';
-      }
     }
     // Now add the value column for fetchAllKeyed(). This is always the
     // entity id.
@@ -116,14 +99,7 @@ public function execute() {
     }
     // This now contains first the table containing entity properties and
     // last the entity base table. They might be the same.
-    $sqlQuery->addMetaData('entity_tables', $entity_tables);
     $sqlQuery->addMetaData('age', $this->age);
-    // This contains the relevant SQL field to be used when joining entity
-    // tables.
-    $sqlQuery->addMetaData('entity_id_field', $entity_info['entity_keys'][$id_key]);
-    // This contains the relevant SQL field to be used when joining field
-    // tables.
-    $sqlQuery->addMetaData('field_id_field', $id_key == 'id' ? 'entity_id' : 'revision_id');
     $sqlQuery->addMetaData('simple_query', $simple_query);
     $this->condition->compile($sqlQuery);
     if ($this->count) {
diff --git a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php
index 7ba89b3..ffd0f1b 100644
--- a/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php
+++ b/core/modules/field/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php
@@ -59,19 +59,64 @@ function __construct(SelectInterface $sql_query) {
    *   of this in a query for a condition or sort.
    */
   function addField($field, $type, $langcode) {
-    $parts = explode('.', $field);
-    $property = $parts[0];
-    $configurable_fields = $this->sqlQuery->getMetaData('configurable_fields');
-    if (!empty($configurable_fields[$property]) || substr($property, 0, 3) == 'id:') {
-      $field_name = $property;
-      $table = $this->ensureFieldTable($field_name, $type, $langcode);
-      // Default to .value.
-      $column = isset($parts[1]) ? $parts[1] : 'value';
-      $sql_column = _field_sql_storage_columnname($field_name, $column);
-    }
-    else {
-      $sql_column = $property;
-      $table = $this->ensureEntityTable($property, $type, $langcode);
+    $age = $this->sqlQuery->getMetaData('age');
+    $specifiers = explode(':', $field);
+    $path = '';
+    foreach ($specifiers as $key => $specifier) {
+      if ($key) {
+        $definitions = entity_get_controller($entity_type)->getFieldDefinitions(array());
+        $entity_type = $definitions[$property]['settings']['entity type'];
+      }
+      else {
+        $entity_type = $this->sqlQuery->getMetaData('entity_type');
+      }
+      $entity_info = entity_get_info($entity_type);
+      if (!isset($entity_info['base_table'])) {
+        throw new QueryException("No base table, invalid query.");
+      }
+      $entity_id = $entity_info['entity_keys']['id'];
+      $base_table = $key ? $this->sqlQuery->leftJoin($entity_info['base_table'], NULL, "%alias.$entity_id = $table.$sql_column") : 'base_table';
+      if (!empty($entity_info['entity_keys']['revision']) && $age == FIELD_LOAD_CURRENT) {
+        // This contains the relevant SQL field to be used when joining entity
+        // tables.
+        $id_field = $entity_info['entity_keys']['revision'];
+        // This contains the relevant SQL field to be used when joining field
+        // tables.
+        $field_id_field = 'revision_id';
+      }
+      else {
+        $id_field = $entity_info['entity_keys']['id'];
+        $field_id_field = 'entity_id';
+      }
+      $configurable_fields = array_map(function ($data) use ($entity_type) {
+        return isset($data['bundles'][$entity_type]);
+      }, field_info_field_map());
+      // ensureEntityTable() decides whether an entity property will be
+      // queried from the data table or the base table based on where it finds
+      // the property first. The data table is prefered, which is why it gets
+      // added before the base table.
+      $entity_tables = array();
+      if (isset($entity_info['data_table'])) {
+        $this->sqlQuery->addMetaData('simple_query', FALSE);
+        $entity_tables[$entity_info['data_table']] = drupal_get_schema($entity_info['data_table']);
+      }
+      $entity_tables[$entity_info['base_table']] = drupal_get_schema($entity_info['base_table']);
+      $parts = explode('.', $specifier);
+      $property = $parts[0];
+      if (!empty($configurable_fields[$property]) || substr($property, 0, 3) == 'id:') {
+        $field_name = $property;
+        $table = $this->ensureFieldTable($path, $field_name, $type, $langcode, $base_table, $id_field, $field_id_field);
+        // Default to .value.
+        $column = isset($parts[1]) ? $parts[1] : 'value';
+        $sql_column = _field_sql_storage_columnname($field_name, $column);
+      }
+      else {
+        $sql_column = $property;
+        $table = $this->ensureEntityTable($path, $property, $type, $langcode, $base_table, $id_field, $entity_tables);
+      }
+      // This is used to keep track which tables have been joined for
+      // grouping.
+      $path .= "$specifier.";
     }
     return "$table.$sql_column";
   }
@@ -83,18 +128,13 @@ function addField($field, $type, $langcode) {
    * @return string
    * @throws \Drupal\Core\Entity\Query\QueryException
    */
-  protected function ensureEntityTable($property, $type, $langcode) {
-    $entity_tables = $this->sqlQuery->getMetaData('entity_tables');
-    if (!$entity_tables) {
-      throw new QueryException('Can not query entity properties without entity tables.');
-    }
+  protected function ensureEntityTable($path, $property, $type, $langcode, $base_table, $id_field, $entity_tables) {
     foreach ($entity_tables as $table => $schema) {
       if (isset($schema['fields'][$property])) {
-        if (!isset($this->entityTables[$table])) {
-          $id_field = $this->sqlQuery->getMetaData('entity_id_field');
-          $this->entityTables[$table] = $this->addJoin($type, $table, "%alias.$id_field = base_table.$id_field", $langcode);
+        if (!isset($this->entityTables[$path . $table])) {
+          $this->entityTables[$path . $table] = $this->addJoin($type, $table, "%alias.$id_field = $base_table.$id_field", $langcode);
         }
-        return $this->entityTables[$table];
+        return $this->entityTables[$path . $table];
       }
     }
     throw new QueryException(format_string('@property not found', array('@property' => $property)));
@@ -108,8 +148,8 @@ protected function ensureEntityTable($property, $type, $langcode) {
    * @return string
    * @throws \Drupal\Core\Entity\Query\QueryException
    */
-  protected function ensureFieldTable(&$field_name, $type, $langcode) {
-    if (!isset($this->fieldTables[$field_name])) {
+  protected function ensureFieldTable($path, &$field_name, $type, $langcode, $base_table, $entity_id_field, $field_id_field) {
+    if (!isset($this->fieldTables[$path . $field_name])) {
       // This is field_purge_batch() passing in a field id.
       if (substr($field_name, 0, 3) == 'id:') {
         $field = field_info_field_by_id(substr($field_name, 3));
@@ -124,15 +164,13 @@ protected function ensureFieldTable(&$field_name, $type, $langcode) {
       // before throwing the exception.
       $field_name = $field['field_name'];
       $table = $this->sqlQuery->getMetaData('age') == FIELD_LOAD_CURRENT ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
-      $field_id_field = $this->sqlQuery->getMetaData('field_id_field');
-      $entity_id_field = $this->sqlQuery->getMetaData('entity_id_field');
       if ($field['cardinality'] != 1) {
         $this->sqlQuery->addMetaData('simple_query', FALSE);
       }
       $entity_type = $this->sqlQuery->getMetaData('entity_type');
-      $this->fieldTables[$field_name] = $this->addJoin($type, $table, "%alias.$field_id_field = base_table.$entity_id_field AND %alias.entity_type = '$entity_type'", $langcode);
+      $this->fieldTables[$path . $field_name] = $this->addJoin($type, $table, "%alias.$field_id_field = $base_table.$entity_id_field AND %alias.entity_type = '$entity_type'", $langcode);
     }
-    return $this->fieldTables[$field_name];
+    return $this->fieldTables[$path . $field_name];
   }
 
   protected function addJoin($type, $table, $join_condition, $langcode) {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryRelationshipTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryRelationshipTest.php
new file mode 100644
index 0000000..a304319
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityQueryRelationshipTest.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Entity\Tests\EntityQueryRelationshipTest.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests Entity Query API relationship functionality.
+ */
+class EntityQueryRelationshipTest extends WebTestBase  {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('entity_test', 'field', 'field_sql_storage');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Entity Query relationship',
+      'description' => 'Tests the Entity Query relationship API',
+      'group' => 'Entity API',
+    );
+  }
+
+  /**
+   * Overrides \Drupal\simpletest\WebTestBase::setUp().
+   */
+  protected function setUp() {
+    parent::setUp();
+    for ($i = 0; $i <= 1; $i++) {
+      $this->accounts[] = $this->drupalCreateUser();
+    }
+    for ($i = 0; $i <= 2; $i++) {
+      $entity = entity_create('entity_test', array());
+      $entity->name->value = $this->randomName();
+      $entity->user_id->value = $this->accounts[$i ? 1 : 0]->uid;
+      $entity->save();
+      $this->entities[] = $entity;
+    }
+    $this->factory = drupal_container()->get('entity.query');
+  }
+
+  /**
+   * Tests querying.
+   */
+  public function testQuery() {
+    $results = $this->factory->get('entity_test')
+      ->condition("user_id:name", $this->accounts[0]->name)
+      ->execute();
+    $this->assertEqual(count($results), 1);
+    $id = $this->entities[0]->id();
+    $this->assertEqual($results, array($id => $id));
+    $results = $this->factory->get('entity_test')
+      ->condition("user_id:name", $this->accounts[0]->name, '<>')
+      ->execute();
+    $this->assertEqual(count($results), 2);
+    $this->assertFalse(isset($results[$id]));
+    for ($i = 1; $i <=2 ; $i++) {
+      $id = $this->entities[$i]->id();
+      $this->assertEqual($results[$id], $id);
+    }
+    $results = $this->factory->get('entity_test')
+      ->exists("user_id:name")
+      ->execute();
+    $this->assertEqual(count($results), 3);
+    for ($i = 0; $i <=2 ; $i++) {
+      $id = $this->entities[$i]->id();
+      $this->assertEqual($results[$id], $id);
+    }
+    $results = $this->factory->get('entity_test')
+      ->notExists("user_id:name")
+      ->execute();
+    $this->assertEqual(count($results), 0);
+  }
+}
