diff --git a/mitm_tooling/representation/sql_representation.py b/mitm_tooling/representation/sql_representation.py
index f0495beb1e8c2606b6cb78a7ac6cbef4d53adb0d..fc48f11c4ddbc7325a5607c843389fe141af397e 100644
--- a/mitm_tooling/representation/sql_representation.py
+++ b/mitm_tooling/representation/sql_representation.py
@@ -10,7 +10,6 @@ import sqlalchemy.sql.schema
 from pydantic import AnyUrl, ConfigDict
 from sqlalchemy import func
 from sqlalchemy.pool import StaticPool
-from sqlalchemy_utils.view import create_view
 
 from mitm_tooling.definition import RelationName
 from mitm_tooling.definition.definition_tools import ColGroupMaps
@@ -19,6 +18,7 @@ from .common import *
 from .intermediate_representation import Header, MITMData
 from .sql.common import *
 from ..utilities.io_utils import FilePath
+from ..utilities.backports.sqlchemy_sql_views import create_view
 
 if TYPE_CHECKING:
     pass
@@ -303,7 +303,7 @@ def mk_sql_rep_schema(header: Header,
     if view_generators is not None:
         for generator in view_generators:
             for name, queryable in generator(header.mitm, concept_tables, type_tables):
-                views[name] = create_view(name, queryable, meta) # TODO make `create_view` schema-aware and add `schema=schema_name`
+                views[name] = create_view(name, queryable, meta, schema=schema_name)
 
     return SQLRepresentationSchema(meta=meta, concept_tables=concept_tables, type_tables=type_tables, views=views)
 
diff --git a/mitm_tooling/utilities/backports/__init__.py b/mitm_tooling/utilities/backports/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/mitm_tooling/utilities/backports/sqlchemy_sql_views.py b/mitm_tooling/utilities/backports/sqlchemy_sql_views.py
new file mode 100644
index 0000000000000000000000000000000000000000..58d53876d2a397faf1f386c02de7cd9e20912173
--- /dev/null
+++ b/mitm_tooling/utilities/backports/sqlchemy_sql_views.py
@@ -0,0 +1,234 @@
+import sqlalchemy as sa
+from sqlalchemy.ext import compiler
+from sqlalchemy.schema import DDLElement, PrimaryKeyConstraint
+from sqlalchemy.sql.expression import ClauseElement, Executable
+
+from sqlalchemy_utils.functions import get_columns
+
+
+class CreateView(DDLElement):
+    def __init__(self, name, selectable, materialized=False, replace=False, schema=None):
+        if materialized and replace:
+            raise ValueError("Cannot use CREATE OR REPLACE with materialized views")
+        self.name = name
+        self.selectable = selectable
+        self.materialized = materialized
+        self.replace = replace
+        self.schema = schema
+
+
+@compiler.compiles(CreateView)
+def compile_create_materialized_view(element, compiler, **kw):
+    return 'CREATE {}{}VIEW {}{} AS {}'.format(
+        'OR REPLACE ' if element.replace else '',
+        'MATERIALIZED ' if element.materialized else '',
+        (compiler.dialect.identifier_preparer.quote(element.schema) + '.') if element.schema else '',
+        compiler.dialect.identifier_preparer.quote(element.name),
+        compiler.sql_compiler.process(element.selectable, literal_binds=True),
+    )
+
+
+class DropView(DDLElement):
+    def __init__(self, name, materialized=False, cascade=True, schema=None):
+        self.name = name
+        self.materialized = materialized
+        self.cascade = cascade
+        self.schema = schema
+
+
+@compiler.compiles(DropView)
+def compile_drop_materialized_view(element, compiler, **kw):
+    compiler.dialect.identifier_preparer.quote(element.schema)
+    return 'DROP {}VIEW IF EXISTS {}{} {}'.format(
+        'MATERIALIZED ' if element.materialized else '',
+        (compiler.dialect.identifier_preparer.quote(element.schema) + '.') if element.schema else '',
+        compiler.dialect.identifier_preparer.quote(element.name),
+        'CASCADE' if element.cascade else ''
+    )
+
+
+def create_table_from_selectable(
+        name,
+        selectable,
+        indexes=None,
+        metadata=None,
+        aliases=None,
+        **kwargs
+):
+    if indexes is None:
+        indexes = []
+    if metadata is None:
+        metadata = sa.MetaData()
+    if aliases is None:
+        aliases = {}
+    args = [
+               sa.Column(
+                   c.name,
+                   c.type,
+                   key=aliases.get(c.name, c.name),
+                   primary_key=c.primary_key
+               )
+               for c in get_columns(selectable)
+           ] + indexes
+    table = sa.Table(name, metadata, *args, **kwargs)
+
+    if not any([c.primary_key for c in get_columns(selectable)]):
+        table.append_constraint(
+            PrimaryKeyConstraint(*[c.name for c in get_columns(selectable)])
+        )
+    return table
+
+
+def create_materialized_view(
+        name,
+        selectable,
+        metadata,
+        indexes=None,
+        aliases=None,
+        schema=None
+):
+    """ Create a view on a given metadata
+
+    :param name: The name of the view to create.
+    :param selectable: An SQLAlchemy selectable e.g. a select() statement.
+    :param metadata:
+        An SQLAlchemy Metadata instance that stores the features of the
+        database being described.
+    :param indexes: An optional list of SQLAlchemy Index instances.
+    :param aliases:
+        An optional dictionary containing with keys as column names and values
+        as column aliases.
+
+    Same as for ``create_view`` except that a ``CREATE MATERIALIZED VIEW``
+    statement is emitted instead of a ``CREATE VIEW``.
+
+    """
+    table = create_table_from_selectable(
+        name=name,
+        selectable=selectable,
+        indexes=indexes,
+        metadata=None,
+        aliases=aliases,
+        schema=schema
+    )
+
+    sa.event.listen(
+        metadata,
+        'after_create',
+        CreateView(name, selectable, materialized=True, schema=schema)
+    )
+
+    @sa.event.listens_for(metadata, 'after_create')
+    def create_indexes(target, connection, **kw):
+        for idx in table.indexes:
+            idx.create(connection)
+
+    sa.event.listen(
+        metadata,
+        'before_drop',
+        DropView(name, materialized=True, schema=schema)
+    )
+    return table
+
+
+def create_view(
+        name,
+        selectable,
+        metadata,
+        cascade_on_drop=True,
+        replace=False,
+        schema=None
+):
+    """ Create a view on a given metadata
+
+    :param name: The name of the view to create.
+    :param selectable: An SQLAlchemy selectable e.g. a select() statement.
+    :param metadata:
+        An SQLAlchemy Metadata instance that stores the features of the
+        database being described.
+    :param cascade_on_drop: If ``True`` the view will be dropped with
+        ``CASCADE``, deleting all dependent objects as well.
+    :param replace: If ``True`` the view will be created with ``OR REPLACE``,
+        replacing an existing view with the same name.
+
+    The process for creating a view is similar to the standard way that a
+    table is constructed, except that a selectable is provided instead of
+    a set of columns. The view is created once a ``CREATE`` statement is
+    executed against the supplied metadata (e.g. ``metadata.create_all(..)``),
+    and dropped when a ``DROP`` is executed against the metadata.
+
+    To create a view that performs basic filtering on a table. ::
+
+        metadata = MetaData()
+        users = Table('users', metadata,
+                Column('id', Integer, primary_key=True),
+                Column('name', String),
+                Column('fullname', String),
+                Column('premium_user', Boolean, default=False),
+            )
+
+        premium_members = select(users).where(users.c.premium_user == True)
+        # sqlalchemy 1.3:
+        # premium_members = select([users]).where(users.c.premium_user == True)
+        create_view('premium_users', premium_members, metadata)
+
+        metadata.create_all(engine) # View is created at this point
+
+    """
+    table = create_table_from_selectable(
+        name=name,
+        selectable=selectable,
+        metadata=None,
+        schema=schema
+    )
+
+    sa.event.listen(
+        metadata,
+        'after_create',
+        CreateView(name, selectable, replace=replace, schema=schema),
+    )
+
+    @sa.event.listens_for(metadata, 'after_create')
+    def create_indexes(target, connection, **kw):
+        for idx in table.indexes:
+            idx.create(connection)
+
+    sa.event.listen(
+        metadata,
+        'before_drop',
+        DropView(name, cascade=cascade_on_drop, schema=schema)
+    )
+    return table
+
+
+class RefreshMaterializedView(Executable, ClauseElement):
+    inherit_cache = True
+
+    def __init__(self, name, concurrently, schema=None):
+        self.name = name
+        self.concurrently = concurrently
+        self.schema = schema
+
+
+@compiler.compiles(RefreshMaterializedView)
+def compile_refresh_materialized_view(element, compiler):
+    return 'REFRESH MATERIALIZED VIEW {concurrently}{schema}{name}'.format(
+        concurrently='CONCURRENTLY ' if element.concurrently else '',
+        schema=(compiler.dialect.identifier_preparer.quote(element.schema) + '.') if element.schema else '',
+        name=compiler.dialect.identifier_preparer.quote(element.name),
+    )
+
+
+def refresh_materialized_view(session, name, concurrently=False):
+    """ Refreshes an already existing materialized view
+
+    :param session: An SQLAlchemy Session instance.
+    :param name: The name of the materialized view to refresh.
+    :param concurrently:
+        Optional flag that causes the ``CONCURRENTLY`` parameter
+        to be specified when the materialized view is refreshed.
+    """
+    # Since session.execute() bypasses autoflush, we must manually flush in
+    # order to include newly-created/modified objects in the refresh.
+    session.flush()
+    session.execute(RefreshMaterializedView(name, concurrently))
diff --git a/pyproject.toml b/pyproject.toml
index f39c1ff51932550d3e663d0b6ea8ddc8649bd19a..9289a99b6acf4069243729e2e9d87abd802c591a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "mitm-tooling"
-version = "0.4.3"
+version = "0.4.4"
 description = ""
 authors = ["Leah Tacke genannt Unterberg <leah.tgu@pads.rwth-aachen.de>"]
 readme = "README.md"