diff --git a/redash/query_runner/python.py b/redash/query_runner/python.py
index 83e135bf43..763fa3615d 100644
--- a/redash/query_runner/python.py
+++ b/redash/query_runner/python.py
@@ -2,9 +2,11 @@
 import importlib
 import logging
 import sys
+import pystache
+from funcy import distinct
 
 from redash.query_runner import *
-from redash.utils import json_dumps, json_loads
+from redash.utils import json_dumps, json_loads, mustache_render
 from redash import models
 from RestrictedPython import compile_restricted
 from RestrictedPython.Guards import safe_builtins, guarded_iter_unpack_sequence, guarded_unpack_sequence
@@ -20,6 +22,29 @@
 
 logger = logging.getLogger(__name__)
 
+def get_query(query_id):
+    try:
+        query = models.Query.get_by_id(query_id)
+    except models.NoResultFound:
+        raise Exception("Query id %s does not exist." % query_id)
+    return query
+
+def _collect_key_names(nodes):
+    keys = []
+    for node in nodes._parse_tree:
+        if isinstance(node, pystache.parser._EscapeNode):
+            keys.append(node.key)
+        elif isinstance(node, pystache.parser._SectionNode):
+            keys.append(node.key)
+            keys.extend(_collect_key_names(node.parsed))
+
+    return distinct(keys)
+
+def _collect_query_parameters(query):
+    nodes = pystache.parse(query)
+    keys = _collect_key_names(nodes)
+    return keys
+
 
 class CustomPrint(object):
     """CustomPrint redirect "print" calls to be sent as "log" on the result object."""
@@ -248,10 +273,11 @@ def get_query_result(query_id):
         Parameters:
         :query_id integer: ID of existing query
         """
-        try:
-            query = models.Query.get_by_id(query_id)
-        except models.NoResultFound:
-            raise Exception("Query id %s does not exist." % query_id)
+        # try:
+        #     query = models.Query.get_by_id(query_id)
+        # except models.NoResultFound:
+        #     raise Exception("Query id %s does not exist." % query_id)
+        query = get_query(query_id)
 
         if query.latest_query_data is None:
             raise Exception("Query does not have results yet.")
@@ -260,7 +286,29 @@ def get_query_result(query_id):
             raise Exception("Query does not have results yet.")
 
         return query.latest_query_data.data
-
+    
+    @staticmethod
+    def execute_by_query_id(query_id, params=None):
+        """Run query from specific query_id.
+        Parameters:
+        :query_id int: Query id to run
+        :params dict: Params for bind to query
+        """
+        query = get_query(query_id)
+        query_text = query.query_text
+        query_params = set(_collect_query_parameters(query_text))
+        if params is None:
+            query_text = query.query_text
+            missing_params = set(query_params)
+        else:
+            query_text = mustache_render(query.query_text, params)
+            missing_params = set(query_params) - set(params.keys())
+        if len(missing_params) > 0:
+            raise Exception('Missing parameter value for: {}'.format(", ".join(missing_params)))
+        data, error = query.data_source.query_runner.run_query(query_text, None)
+        if error is not None:
+            raise Exception(error)
+        return json_loads(data)
     def dataframe_to_result(self, result, df):
 
         result["rows"] = df.to_dict("records")
@@ -322,6 +370,7 @@ def run_query(self, query, user):
             restricted_globals["get_source_schema"] = self.get_source_schema
             restricted_globals["get_current_user"] = self.get_current_user
             restricted_globals["execute_query"] = self.execute_query
+            restricted_globals["execute_by_query_id"] = self.execute_by_query_id
             restricted_globals["add_result_column"] = self.add_result_column
             if pandas_installed:
                 restricted_globals["dataframe_to_result"] = self.dataframe_to_result