diff --git a/db/db.ddl b/db/db.ddl
index 378c9d9e3c41fe291c43f032ab993f0387d3ed9c..f6dc6f5beada03e45c29e40d127817c76175fd5c 100644
--- a/db/db.ddl
+++ b/db/db.ddl
@@ -151,6 +151,7 @@ CREATE TABLE contests (
 	round_id		int		NOT NULL REFERENCES rounds(round_id),
 	place_id		int		NOT NULL REFERENCES places(place_id),
 	state			round_state	NOT NULL DEFAULT 'preparing',	-- používá se, pokud round.state='delegate', jinak kopíruje round.state
+	scoretable_id		int		DEFAULT NULL,			-- odkaz na snapshot představující oficiální výsledkovou listinu soutěže
 	UNIQUE (round_id, place_id)
 );
 
@@ -439,3 +440,18 @@ CREATE TABLE scan_pages (
 	--	-4	pro stránku, která nepatří do této soutěže
 	UNIQUE (job_id, file_nr, page_nr)
 );
+
+-- Uložené výsledkové listiny (pro zveřejnění)
+
+CREATE TABLE score_tables (
+	scoretable_id	serial		PRIMARY KEY,
+	contest_id	int		NOT NULL REFERENCES contests(contest_id) ON DELETE CASCADE,	-- soutěž ke které patří
+	created_at	timestamp with time zone	NOT NULL DEFAULT CURRENT_TIMESTAMP,		-- datum vytvoření snapshotu
+	created_by	int		NOT NULL REFERENCES users(user_id),				-- autor snapshotu
+	score_mode	score_mode	NOT NULL,							-- mód výsledkovky
+	note		text		NOT NULL,							-- poznámka viditelná pro orgy
+	tasks		jsonb		NOT NULL,							-- seznam názvů a kódů úloh
+	rows		jsonb		NOT NULL							-- seznam řádků výsledkové listiny
+);
+
+ALTER TABLE contests ADD CONSTRAINT "contests_scoretable_id" FOREIGN KEY (scoretable_id) REFERENCES score_tables(scoretable_id);
diff --git a/db/upgrade-20211204.sql b/db/upgrade-20211204.sql
new file mode 100644
index 0000000000000000000000000000000000000000..eecfd5bf5a714b538c6ab5442f24be8adfebecab
--- /dev/null
+++ b/db/upgrade-20211204.sql
@@ -0,0 +1,15 @@
+SET ROLE 'mo_osmo';
+
+CREATE TABLE score_tables (
+	scoretable_id	serial		PRIMARY KEY,
+	contest_id	int		NOT NULL REFERENCES contests(contest_id) ON DELETE CASCADE,	-- soutěž ke které patří
+	created_at	timestamp with time zone	NOT NULL DEFAULT CURRENT_TIMESTAMP,		-- datum vytvoření snapshotu
+	created_by	int		NOT NULL REFERENCES users(user_id),				-- autor snapshotu
+	score_mode	score_mode	NOT NULL,							-- mód výsledkovky
+	note		text		NOT NULL,							-- poznámka viditelná pro orgy
+	tasks		jsonb		NOT NULL,							-- seznam názvů a kódů úloh
+	rows		jsonb		NOT NULL							-- seznam řádků výsledkové listiny
+);
+
+ALTER TABLE contests ADD COLUMN
+	scoretable_id	int		DEFAULT NULL REFERENCES score_tables(scoretable_id);
diff --git a/mo/db.py b/mo/db.py
index b9f1d42693cff5c9bad3cada8a2a000346fab85b..af3d60184be11bb64566afc4ea681fdb4b013209 100644
--- a/mo/db.py
+++ b/mo/db.py
@@ -10,7 +10,7 @@ import re
 from sqlalchemy import \
     Boolean, Column, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, \
     text, func, \
-    create_engine, inspect, select, or_, and_
+    create_engine, inspect, select
 from sqlalchemy.engine import Engine
 from sqlalchemy.orm import relationship, sessionmaker, Session, class_mapper, joinedload, aliased
 from sqlalchemy.orm.attributes import get_history
@@ -391,10 +391,12 @@ class Contest(Base):
     round_id = Column(Integer, ForeignKey('rounds.round_id'), nullable=False)
     place_id = Column(Integer, ForeignKey('places.place_id'), nullable=False)
     state = Column(Enum(RoundState, name='round_state'), nullable=False, server_default=text("'preparing'::round_state"))
+    scoretable_id = Column(Integer, ForeignKey('score_tables.scoretable_id'), nullable=True)
 
     master = relationship('Contest', primaryjoin='Contest.master_contest_id == Contest.contest_id', remote_side='Contest.contest_id', post_update=True)
     place = relationship('Place')
     round = relationship('Round')
+    scoretable = relationship('ScoreTable', primaryjoin='Contest.scoretable_id == ScoreTable.scoretable_id')
 
     def is_subcontest(self) -> bool:
         return self.master_contest_id != self.contest_id
@@ -869,6 +871,22 @@ SCAN_PAGE_CONTINUE = -3
 SCAN_PAGE_UFO = -4
 
 
+class ScoreTable(Base):
+    __tablename__ = 'score_tables'
+
+    scoretable_id = Column(Integer, primary_key=True, server_default=text("nextval('score_tables_scoretable_id_seq'::regclass)"))
+    contest_id = Column(Integer, ForeignKey('contests.contest_id', ondelete='CASCADE'), nullable=False)
+    created_at = Column(DateTime(True), nullable=False, server_default=text("CURRENT_TIMESTAMP"))
+    created_by = Column(Integer, ForeignKey('users.user_id'), nullable=False)
+    score_mode = Column(Enum(RoundScoreMode, name='score_mode'), nullable=False)
+    note = Column(Text, nullable=False)
+    tasks = Column(JSONB, nullable=False)
+    rows = Column(JSONB, nullable=False)
+
+    user = relationship('User')
+    contest = relationship('Contest', primaryjoin='Contest.scoretable_id == ScoreTable.scoretable_id')
+
+
 _engine: Optional[Engine] = None
 _session: Optional[Session] = None
 flask_db: Any = None