diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ee3c3586733d9c9ac713ee671a999762033fdcf..f790db0ea08e7b77cea3c8c44bb70bc5ef4eb540 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added ###
 
+* `[Entity|Container].get_property_values` for deeply nested references, e.g.
+  from results of SELECT queries.
 * two new `password_method`s for the `pycaosdb.ini` and the
   `configure_connection` function: `unauthenticated` for staying
   unauthenticated (and using the anonymous user) and `auth_token`. If
diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py
index 0ccb0410b80b3d1fe3ea9311796df37aa31da1ab..34fb6a400ab8259ef629667d228ca7ddd16075fb 100644
--- a/src/caosdb/common/models.py
+++ b/src/caosdb/common/models.py
@@ -7,6 +7,7 @@
 # Max-Planck-Institute for Dynamics and Self-Organization Göttingen
 # Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
 # Copyright (C) 2020 Florian Spreckelsen <f.spreckelsen@indiscale.com>
+# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
@@ -373,7 +374,7 @@ class Entity(object):
 
         del copy_kwargs['importance']
         del copy_kwargs['inheritance']
-        new_property = Property(value=value, **copy_kwargs)
+        new_property = Property(**copy_kwargs)
 
         if abstract_property is not None:
             new_property._wrap(property)
@@ -383,6 +384,8 @@ class Entity(object):
             if new_property.datatype is None and isinstance(
                     property, (RecordType, Record, File)):
                 new_property.datatype = property
+        new_property.value = value
+
         self.properties.append(
             property=new_property, importance=(
                 kwargs['importance'] if 'importance' in kwargs else None), inheritance=(
@@ -611,6 +614,103 @@ class Entity(object):
 
         return None
 
+    def _get_value_for_selector(self, selector):
+        """return the value described by the selector
+
+        A selector is a list or a tuple of strings describing a path in an
+        entity tree with self as root. The last selector may be a special one
+        like unit or name.
+
+        See also get_property_values()
+        """
+        SPECIAL_SELECTORS = ["unit", "value", "description", "id", "name"]
+
+        if not isinstance(selector, (tuple, list)):
+            selector = [selector]
+
+        val = None
+        ref = self
+
+        # there are some special selectors which can be applied to the
+        # final element; if such a special selector exists we split it
+        # from the list
+
+        if selector[-1].lower() in SPECIAL_SELECTORS:
+            special_selector = selector[-1]
+            selector = selector[:-1]
+        else:
+            special_selector = None
+
+        # iterating through the entity tree according to the selector
+        for subselector in selector:
+            # selector does not match the structure, we cannot get a
+            # property of non-entity
+
+            if not isinstance(ref, Entity):
+                return None
+
+            prop = ref.get_property(subselector)
+
+            # selector does not match the structure, we did not get a
+            # property
+            if prop is None:
+                return None
+
+            # if the property is a reference, we are interested in the
+            # corresponding entities attributes
+            if isinstance(prop.value, Entity):
+                ref = prop.value
+
+            # otherwise in the attributes of the property
+            else:
+                ref = prop
+
+        # if we saved a special selector before, apply it
+        if special_selector is None:
+            return prop.value
+        else:
+            return getattr(ref, special_selector.lower())
+
+    def get_property_values(self, *selectors):
+        """ Return a tuple with the values described by the given selectors.
+
+        This represents an entity's properties as if it was a row of a table
+        with the given columns.
+
+        If the elements of the selectors parameter are tuples, they will return
+        the properties of the referenced entity, if present. E.g. ("window",
+        "height") will return the value of the height property of the
+        referenced window entity.
+
+        The tuple's values correspond to the order of selectors parameter.
+
+        The tuple contains None for all values that are not available in the
+        entity. That does not necessarily mean, that the values are not stored
+        in the database (e.g. if a single entity was retrieved without
+        referenced entities).
+
+        Parameters
+        ----------
+        *selectors : str or tuple of str
+            Each selector is a list or tuple of property names, e.g. `"height",
+            "width"`.
+
+        Returns
+        -------
+        row : tuple
+            A row-like representation of the entity's properties.
+        """
+        row = tuple()
+
+        for selector in selectors:
+            val = self._get_value_for_selector(selector)
+
+            if isinstance(val, Entity):
+                val = val.id if val.id is not None else val.name
+            row += (val,)
+
+        return row
+
     def get_messages(self):
         """Get all messages of this entity.
 
@@ -839,17 +939,27 @@ class Entity(object):
                 entity.add_message(child)
             elif child is None or hasattr(child, "encode"):
                 vals.append(child)
+            elif isinstance(child, Entity):
+                vals.append(child)
             else:
                 raise TypeError(
                     'Child was neither a Property, nor a Parent, nor a Message.\
                     Was ' + str(type(child)))
 
-        # parse VALUE
+        # add VALUE
+        value = None
+
         if len(vals):
             # The value[s] have been inside a <Value> tag.
-            entity.value = vals
+            value = vals
         elif elem.text is not None and elem.text.strip() != "":
-            entity.value = elem.text.strip()
+            value = elem.text.strip()
+
+        try:
+            entity.value = value
+        except ValueError:
+            # circumvent the parsing.
+            entity.__value = value
 
         return entity
 
@@ -998,12 +1108,16 @@ class Entity(object):
 def _parse_value(datatype, value):
     if value is None:
         return value
+
     if datatype is None:
         return value
+
     if datatype == DOUBLE:
         return float(value)
+
     if datatype == INTEGER:
         return int(str(value))
+
     if datatype == BOOLEAN:
         if str(value).lower() == "true":
             return True
@@ -1011,14 +1125,17 @@ def _parse_value(datatype, value):
             return False
         else:
             raise ValueError("Boolean value was {}.".format(value))
+
     if datatype in [DATETIME, TEXT]:
         if isinstance(value, str):
             return value
 
     # deal with collections
+
     if isinstance(datatype, str):
         matcher = re.compile(r"^(?P<col>[^<]+)<(?P<dt>[^>]+)>$")
         m = matcher.match(datatype)
+
         if m:
             col = m.group("col")
             dt = m.group("dt")
@@ -1039,14 +1156,18 @@ def _parse_value(datatype, value):
 
     # This is for a special case, where the xml parser could not differentiate
     # between single values and lists with one element. As
+
     if hasattr(value, "__len__") and len(value) == 1:
         return _parse_value(datatype, value[0])
 
     # deal with references
+
     if isinstance(value, Entity):
         return value
+
     if isinstance(value, str) and "@" in value:
         # probably this is a versioned reference
+
         return str(value)
     else:
         # for unversioned references
@@ -1054,6 +1175,7 @@ def _parse_value(datatype, value):
             return int(value)
         except ValueError:
             # reference via name
+
             return str(value)
 
 
@@ -3132,6 +3254,43 @@ class Container(list):
 
         return self
 
+    def get_property_values(self, *selectors):
+        """ Return a list of tuples with values of the given selectors.
+
+        I.e. a tabular representation of the container's content.
+
+        If the elements of the selectors parameter are tuples, they will return
+        the properties of the referenced entity, if present. E.g. ("window",
+        "height") will return the value of the height property of the
+        referenced window entity.
+
+        All tuples of the returned list have the same length as the selectors
+        parameter and the ordering of the tuple's values correspond to the
+        order of the parameter as well.
+
+        The tuple contains None for all values that are not available in the
+        entity. That does not necessarily mean, that the values are not stored
+        in the database (e.g. if a single entity was retrieved without
+        referenced entities).
+
+        Parameters
+        ----------
+        *selectors : str or tuple of str
+            Each selector is a list or tuple of property names, e.g. `"height",
+            "width"`.
+
+        Returns
+        -------
+        table : list of tuples
+            A tabular representation of the container's content.
+        """
+        table = []
+
+        for e in self:
+            table.append(e.get_property_values(*selectors))
+
+        return table
+
 
 def sync_global_acl():
     c = get_connection()
@@ -3565,6 +3724,7 @@ class Info():
             http_response = c.retrieve(["Info"])
         except ConnectionException as conn_e:
             print(conn_e)
+
             return
 
         xml = etree.fromstring(http_response.read())
@@ -3682,6 +3842,7 @@ def _parse_single_xml_element(elem):
             return ""
         elif elem.text is None or elem.text.strip() == "":
             return None
+
         return str(elem.text.strip())
     elif elem.tag.lower() == "querytemplate":
         return QueryTemplate._from_xml(elem)
diff --git a/unittests/test_container.py b/unittests/test_container.py
new file mode 100644
index 0000000000000000000000000000000000000000..b34055372fc83a5608ffcf54423a601001add12b
--- /dev/null
+++ b/unittests/test_container.py
@@ -0,0 +1,79 @@
+
+# -*- encoding: utf-8 -*-
+#
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# ** end header
+#
+"""Tests for the Container class."""
+from __future__ import absolute_import
+
+import caosdb as c
+
+
+def test_get_property_values():
+    rt_house = c.RecordType("House")
+    rt_window = c.RecordType("Window")
+    rt_owner = c.RecordType("Owner")
+    p_height = c.Property("Height", datatype=c.DOUBLE)
+
+    window = c.Record().add_parent(rt_window)
+    window.id = 1001
+    window.add_property(p_height, 20.5, unit="m")
+
+    owner = c.Record("The Queen").add_parent(rt_owner)
+
+    house = c.Record("Buckingham Palace")
+    house.add_parent(rt_house)
+    house.add_property(rt_owner, owner)
+    house.add_property(rt_window, window)
+    house.add_property(p_height, 40.2, unit="ft")
+
+    container = c.Container()
+    container.extend([
+        house,
+        owner
+    ])
+
+    assert getattr(house.get_property(p_height), "unit") == "ft"
+    assert getattr(window.get_property(p_height), "unit") == "m"
+
+    table = container.get_property_values("naME",
+                                          "height",
+                                          ("height", "unit"),
+                                          "window",
+                                          ("window", "non-existing"),
+                                          ("window", "non-existing", "unit"),
+                                          ("window", "unit"),
+                                          ("window", "heiGHT"),
+                                          ("window", "heiGHT", "value"),
+                                          ("window", "heiGHT", "unit"),
+                                          "owner",
+                                          )
+    assert len(table) == 2
+    house_row = table[0]
+    assert house_row == (house.name, 40.2, "ft", window.id, None, None, None, 20.5, 20.5, "m", owner.name)
+
+    owner_row = table[1]
+    assert owner_row == (owner.name, None, None, None, None, None, None, None, None, None, None)
+
+    assert container.get_property_values("non-existing") == [(None,), (None,)]
+    assert container.get_property_values("name") == [(house.name,),
+                                                     (owner.name,)]