diff --git a/src/metabase/automagic_dashboards/core.clj b/src/metabase/automagic_dashboards/core.clj
index 844cf3725b248d3a89019993566cc6f5ad863ac3..36859f386c7669266809aab93de6eaf1b65a9c67 100644
--- a/src/metabase/automagic_dashboards/core.clj
+++ b/src/metabase/automagic_dashboards/core.clj
@@ -430,12 +430,10 @@
 
 (defn- build-query
   ([context bindings filters metrics dimensions limit order_by]
-   (walk/postwalk
-    (fn [subform]
-      (if (rules/dimension-form? subform)
-        (let [[_ identifier opts] subform]
-          (->reference :mbql (-> identifier bindings (merge opts))))
-        subform))
+   (qp.util/postwalk-pred
+    rules/dimension-form?
+    (fn [[_ identifier opts]]
+      (->reference :mbql (-> identifier bindings (merge opts))))
     {:type     :query
      :database (-> context :root :database)
      :query    (cond-> {:source_table (if (->> context :source (instance? (type Table)))
diff --git a/src/metabase/query_processor/interface.clj b/src/metabase/query_processor/interface.clj
index 42359622cc39cda6f39904e94392f225dece7faa..c1215ac47bf0bc66fda1fe17c2cc4c1317d7bb5b 100644
--- a/src/metabase/query_processor/interface.clj
+++ b/src/metabase/query_processor/interface.clj
@@ -177,11 +177,18 @@
   [unit]
   (contains? relative-datetime-value-units (keyword unit)))
 
+(def binning-strategies
+  "Valid binning strategies for a `BinnedField`"
+  #{:num-bins :bin-width :default})
+
 ;; TODO - maybe we should figure out some way to have the schema validate that the driver supports field literals,
 ;; like we do for some of the other clauses. Ideally we'd do that in a more generic way (perhaps in expand, we could
 ;; make the clauses specify required feature metadata and have that get checked automatically?)
-(s/defrecord FieldLiteral [field-name    :- su/NonBlankString
-                           base-type     :- su/FieldType]
+(s/defrecord FieldLiteral [field-name       :- su/NonBlankString
+                           base-type        :- su/FieldType
+                           binning-strategy :- (s/maybe (apply s/enum binning-strategies))
+                           binning-param    :- (s/maybe s/Num)
+                           fingerprint      :- (s/maybe i/Fingerprint)]
   nil
   :load-ns true
   clojure.lang.Named
@@ -210,11 +217,7 @@
   nil
   :load-ns true)
 
-(def binning-strategies
-  "Valid binning strategies for a `BinnedField`"
-  #{:num-bins :bin-width :default})
-
-(s/defrecord BinnedField [field     :- Field
+(s/defrecord BinnedField [field     :- (s/cond-pre Field FieldLiteral)
                           strategy  :- (apply s/enum binning-strategies)
                           num-bins  :- s/Int
                           min-value :- s/Num
diff --git a/src/metabase/query_processor/middleware/binning.clj b/src/metabase/query_processor/middleware/binning.clj
index 1cde0589b6d6b03754300630ad61b024340dbdec..53c333bb23e80575af7357f2860499e1f9185a0f 100644
--- a/src/metabase/query_processor/middleware/binning.clj
+++ b/src/metabase/query_processor/middleware/binning.clj
@@ -3,7 +3,8 @@
             [clojure.walk :as walk]
             [metabase
              [public-settings :as public-settings]
-             [util :as u]])
+             [util :as u]]
+            [metabase.query-processor.util :as qputil])
   (:import [metabase.query_processor.interface BetweenFilter BinnedField ComparisonFilter]))
 
 (defn- update!
@@ -162,8 +163,6 @@
   (fn [query]
     (let [filter-field-map (filter->field-map (get-in query [:query :filter]))]
       (qp
-       (walk/postwalk (fn [node]
-                        (if (instance? BinnedField node)
-                          (update-binned-field node filter-field-map)
-                          node))
-                      query)))))
+       (qputil/postwalk-pred #(instance? BinnedField %)
+                             #(update-binned-field % filter-field-map)
+                             query)))))
diff --git a/src/metabase/query_processor/middleware/expand.clj b/src/metabase/query_processor/middleware/expand.clj
index 19ca6f57fd6e67e5d554cbb53c0d5a2bf82f7c51..3ce1dc965d512e9041d13758967010904dc327dd 100644
--- a/src/metabase/query_processor/middleware/expand.clj
+++ b/src/metabase/query_processor/middleware/expand.clj
@@ -225,7 +225,7 @@
 
 ;;; ## breakout & fields
 
-(s/defn ^:ql binning-strategy :- FieldPlaceholder
+(s/defn ^:ql binning-strategy :- (s/cond-pre FieldPlaceholder FieldLiteral)
   "Reference to a `BinnedField`. This is just a `Field` reference with an associated `STRATEGY-NAME` and
   `STRATEGY-PARAM`"
   ([f strategy-name & [strategy-param]]
diff --git a/src/metabase/query_processor/middleware/expand_macros.clj b/src/metabase/query_processor/middleware/expand_macros.clj
index b40b54d579a4dcb34a5d4bb90f3ba5c828630547..aa7f74ddc40ae06f9ea3da1177368d33c3b0ba19 100644
--- a/src/metabase/query_processor/middleware/expand_macros.clj
+++ b/src/metabase/query_processor/middleware/expand_macros.clj
@@ -105,12 +105,9 @@
     (maybe-unnest-ag-clause ag-clause)))
 
 (defn- expand-metrics-in-ag-clause [query-dict filter-clauses-atom]
-  (walk/postwalk
-   (fn [form]
-     (if-not (metric? form)
-       form
-       (expand-metric form filter-clauses-atom)))
-   query-dict))
+  (qputil/postwalk-pred metric?
+                        #(expand-metric % filter-clauses-atom)
+                        query-dict))
 
 (defn merge-filter-clauses
   "Merge filter clauses."
diff --git a/src/metabase/query_processor/middleware/fetch_source_query.clj b/src/metabase/query_processor/middleware/fetch_source_query.clj
index c8498e412692453ad9dd4a47bc915ac615be02df..997d146e13c6bcdf8929272a2954c5f6cb7cfbd1 100644
--- a/src/metabase/query_processor/middleware/fetch_source_query.clj
+++ b/src/metabase/query_processor/middleware/fetch_source_query.clj
@@ -24,7 +24,7 @@
 (defn- card-id->source-query
   "Return the source query info for Card with CARD-ID."
   [card-id]
-  (let [card       (db/select-one ['Card :dataset_query :database_id] :id card-id)
+  (let [card       (db/select-one ['Card :dataset_query :database_id :result_metadata] :id card-id)
         card-query (:dataset_query card)]
     (assoc (or (:query card-query)
                (when-let [native (:native card-query)]
@@ -33,7 +33,8 @@
                (throw (Exception. (str "Missing source query in Card " card-id))))
       ;; include database ID as well; we'll pass that up the chain so it eventually gets put in its spot in the
       ;; outer-query
-      :database (:database card-query))))
+      :database        (:database card-query)
+      :result_metadata (:result_metadata card))))
 
 (defn- source-table-str->source-query
   "Given a SOURCE-TABLE-STR like `card__100` return the appropriate source query."
@@ -41,7 +42,10 @@
   (let [[_ card-id-str] (re-find #"^card__(\d+)$" source-table-str)]
     (u/prog1 (card-id->source-query (Integer/parseInt card-id-str))
       (when-not i/*disable-qp-logging*
-        (log/info "\nFETCHED SOURCE QUERY FROM CARD" card-id-str ":\n" (u/pprint-to-str 'yellow <>))))))
+        (log/infof "\nFETCHED SOURCE QUERY FROM CARD %s:\n%s"
+                   card-id-str
+                   ;; No need to include result metadata here, it can be large and will clutter the logs
+                   (u/pprint-to-str 'yellow (dissoc <> :result_metadata)))))))
 
 (defn- expand-card-source-tables
   "If `source-table` is a Card reference (a string like `card__100`) then replace that with appropriate
@@ -58,8 +62,9 @@
             ;; Add new `source-query` info in its place. Pass the database ID up the chain, removing it from the
             ;; source query
             (assoc
-              :source-query (dissoc source-query :database)
-              :database     (:database source-query)))))))
+              :source-query    (dissoc source-query :database :result_metadata)
+              :database        (:database source-query)
+              :result_metadata (:result_metadata source-query)))))))
 
 (defn- fetch-source-query* [{inner-query :query, :as outer-query}]
   (if-not inner-query
@@ -68,9 +73,11 @@
     ;; otherwise attempt to expand any source queries as needed
     (let [expanded-inner-query (expand-card-source-tables inner-query)]
       (merge outer-query
-             {:query (dissoc expanded-inner-query :database)}
+             {:query (dissoc expanded-inner-query :database :result_metadata)}
              (when-let [database (:database expanded-inner-query)]
-               {:database database})))))
+               {:database database})
+             (when-let [result-metadata (:result_metadata expanded-inner-query)]
+               {:result_metadata result-metadata})))))
 
 (defn fetch-source-query
   "Middleware that assocs the `:source-query` for this query if it was specified using the shorthand `:source-table`
diff --git a/src/metabase/query_processor/middleware/resolve.clj b/src/metabase/query_processor/middleware/resolve.clj
index 78cf8066fce793386cc16e3020a90d3fcf2703cf..eacd0d3475bc7044f30c57c90ab9948198c202af 100644
--- a/src/metabase/query_processor/middleware/resolve.clj
+++ b/src/metabase/query_processor/middleware/resolve.clj
@@ -25,7 +25,7 @@
              [db :as db]
              [hydrate :refer [hydrate]]])
   (:import java.util.TimeZone
-           [metabase.query_processor.interface DateTimeField DateTimeValue ExpressionRef Field FieldPlaceholder
+           [metabase.query_processor.interface DateTimeField DateTimeValue ExpressionRef Field FieldLiteral FieldPlaceholder
             RelativeDatetime RelativeDateTimeValue TimeField TimeValue Value ValuePlaceholder]))
 
 ;;; ---------------------------------------------------- UTIL FNS ----------------------------------------------------
@@ -161,7 +161,7 @@
 
 ;;; ----------------------------------------------- FIELD PLACEHOLDER ------------------------------------------------
 
-(defn- resolve-binned-field [{:keys [binning-strategy binning-param] :as field-ph} field]
+(defn- resolve-binned-field [binning-strategy binning-param field]
   (let [binned-field (i/map->BinnedField {:field    field
                                           :strategy binning-strategy})]
     (case binning-strategy
@@ -200,7 +200,7 @@
       (i/map->TimeField {:field field})
 
       binning-strategy
-      (resolve-binned-field this field)
+      (resolve-binned-field binning-strategy binning-param field)
 
       :else field)
     ;; If that fails just return ourselves as-is
@@ -399,6 +399,29 @@
           (assoc-in <> [:query :join-tables]  joined-tables)
           (walk/postwalk #(resolve-table % fk-id+table-id->table) <>))))))
 
+(defn- resolve-field-literals
+  "When resolving a field, we connect a `field-id` with a `Field` in our metadata tables. This is a similar process
+  for `FieldLiteral`s, except we are attempting to connect a `FieldLiteral` with an associated entry in the
+  `result_metadata` attached to the query (typically from the `Card` of a nested query)."
+  [{:keys [result_metadata] :as expanded-query-dict}]
+  (let [name->fingerprint (zipmap (map :name result_metadata)
+                                  (map :fingerprint result_metadata))]
+    (qputil/postwalk-pred #(instance? FieldLiteral %)
+                          (fn [{:keys [binning-strategy binning-param] :as node}]
+                            (let [fingerprint     (get name->fingerprint (:field-name node))
+                                  node-with-print (assoc node :fingerprint fingerprint)]
+                              (cond
+                                ;; We can't bin without min/max values found from a fingerprint
+                                (and binning-strategy (not fingerprint))
+                                (throw (Exception. "Binning not supported on a field literal with no fingerprint"))
+
+                                (and fingerprint binning-strategy)
+                                (resolve-binned-field binning-strategy binning-param node-with-print)
+
+                                :else
+                                node-with-print)))
+                          expanded-query-dict)))
+
 
 ;;; ------------------------------------------------ PUBLIC INTERFACE ------------------------------------------------
 
@@ -408,6 +431,7 @@
   (some-> expanded-query-dict
           record-fk-field-ids
           resolve-fields
+          resolve-field-literals
           resolve-tables))
 
 (defn resolve-middleware
diff --git a/src/metabase/query_processor/middleware/results_metadata.clj b/src/metabase/query_processor/middleware/results_metadata.clj
index 839a4ea8ebf7ce00b6a4d555f1702f82cf7add01..76110b33ed0f40ab94d7794cb72f176131bada3b 100644
--- a/src/metabase/query_processor/middleware/results_metadata.clj
+++ b/src/metabase/query_processor/middleware/results_metadata.clj
@@ -7,6 +7,7 @@
             [clojure.tools.logging :as log]
             [metabase.models.humanization :as humanization]
             [metabase.query-processor.interface :as i]
+            [metabase.sync.interface :as si]
             [metabase.util :as u]
             [metabase.util
              [encryption :as encryption]
@@ -29,7 +30,8 @@
                                         (s/optional-key :description)  (s/maybe su/NonBlankString)
                                         :base_type                     su/FieldTypeKeywordOrString
                                         (s/optional-key :special_type) (s/maybe su/FieldTypeKeywordOrString)
-                                        (s/optional-key :unit)         (s/maybe DateTimeUnitKeywordOrString)}]
+                                        (s/optional-key :unit)         (s/maybe DateTimeUnitKeywordOrString)
+                                        (s/optional-key :fingerprint)  (s/maybe si/Fingerprint)}]
                                       "Valid array of results column metadata maps")
     "value must be an array of valid results column metadata maps."))
 
@@ -44,7 +46,7 @@
           ;; if base-type isn't set put a default one in there. Similarly just use humanized value of `:name` for `:display_name` if one isn't set
           {:base_type    :type/*
            :display_name (humanization/name->human-readable-name (name (:name col)))}
-          (u/select-non-nil-keys col [:name :display_name :description :base_type :special_type :unit])
+          (u/select-non-nil-keys col [:name :display_name :description :base_type :special_type :unit :fingerprint])
           ;; since years are actually returned as text they can't be used for breakout purposes so don't advertise them as DateTime columns
           (when (= (:unit col) :year)
             {:base_type :type/Text
diff --git a/src/metabase/query_processor/util.clj b/src/metabase/query_processor/util.clj
index 5441fa30c01096ff1fed8fcd3795fafcc819d0a0..fedcc1671da0f148f10a0cab651226fbccd14e0d 100644
--- a/src/metabase/query_processor/util.clj
+++ b/src/metabase/query_processor/util.clj
@@ -4,7 +4,9 @@
              [codecs :as codecs]
              [hash :as hash]]
             [cheshire.core :as json]
-            [clojure.string :as str]
+            [clojure
+             [string :as str]
+             [walk :as walk]]
             [metabase.util :as u]
             [metabase.util.schema :as su]
             [schema.core :as s]))
@@ -138,3 +140,14 @@
     (when (string? source-table)
       (when-let [[_ card-id-str] (re-matches #"^card__(\d+$)" source-table)]
         (Integer/parseInt card-id-str)))))
+
+;;; ---------------------------------------- General Tree Manipulation Helpers ---------------------------------------
+
+(defn postwalk-pred
+  "Transform `form` by applying `f` to a node in the tree when that `pred` returns true for that node"
+  [pred f form]
+  (walk/postwalk (fn [node]
+                   (if (pred node)
+                     (f node)
+                     node))
+                 form))
diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj
index 63682bd2c8924b92c77206b32ec5d0cffc24a81f..573b4bad625aa929e9bd3401fae35c71fc4fb5fe 100644
--- a/test/metabase/api/table_test.clj
+++ b/test/metabase/api/table_test.clj
@@ -667,3 +667,29 @@
 (expect
   #{:metrics :segments :linked-from :linking-to :tables}
   (-> ((user->client :crowberto) :get 200 (format "table/%s/related" (data/id :venues))) keys set))
+
+;; Nested queries with a fingerprint should have dimension options for binning
+(datasets/expect-with-engines (qpt/non-timeseries-engines-with-feature :binning)
+  (repeat 2 (var-get #'table-api/coordinate-dimension-indexes))
+  (tt/with-temp Card [card {:database_id   (data/id)
+                            :dataset_query {:database (data/id)
+                                            :type    :query
+                                            :query    {:source-query {:source-table (data/id :venues)}}}}]
+    ;; run the Card which will populate its result_metadata column
+    ((user->client :crowberto) :post 200 (format "card/%d/query" (u/get-id card)))
+    (let [response ((user->client :crowberto) :get 200 (format "table/card__%d/query_metadata" (u/get-id card)))]
+      (map #(dimension-options-for-field response %)
+           ["latitude" "longitude"]))))
+
+;; Nested queries missing a fingerprint should not show binning-options
+(datasets/expect-with-engines (qpt/non-timeseries-engines-with-feature :binning)
+  [nil nil]
+  (tt/with-temp Card [card {:database_id   (data/id)
+                            :dataset_query {:database (data/id)
+                                            :type    :query
+                                            :query    {:source-query {:source-table (data/id :venues)}}}}]
+    ;; By default result_metadata will be nil (and no fingerprint). Just asking for query_metadata after the card was
+    ;; created but before it was ran should not allow binning
+    (let [response ((user->client :crowberto) :get 200 (format "table/card__%d/query_metadata" (u/get-id card)))]
+      (map #(dimension-options-for-field response %)
+           ["latitude" "longitude"]))))
diff --git a/test/metabase/query_processor/middleware/fetch_source_query_test.clj b/test/metabase/query_processor/middleware/fetch_source_query_test.clj
index 0b9d4936ddb4710ab1a7eee87503386b201feafa..8905749e71f69e8256e2e17ebf16fdd815929e23 100644
--- a/test/metabase/query_processor/middleware/fetch_source_query_test.clj
+++ b/test/metabase/query_processor/middleware/fetch_source_query_test.clj
@@ -76,21 +76,22 @@
                        :query    {:source-table (str "card__" (u/get-id card))}})))
 
 (expect
-  (default-expanded-results
-   {:source-query {:source-table {:schema "PUBLIC" :name "CHECKINS" :id (data/id :checkins)}, :join-tables nil}
-    :filter {:filter-type :between,
-             :field {:field-name "date", :base-type :type/Date},
-             :min-val {:value (tcoerce/to-timestamp (du/str->date-time "2015-01-01"))
-                       :field {:field {:field-name "date", :base-type :type/Date}, :unit :default}},
-             :max-val {:value (tcoerce/to-timestamp (du/str->date-time "2015-02-01"))
-                       :field {:field {:field-name "date", :base-type :type/Date}, :unit :default}}}})
+  (let [date-field-literal {:field-name "date", :base-type :type/Date, :binning-strategy nil, :binning-param nil, :fingerprint nil}]
+    (default-expanded-results
+     {:source-query {:source-table {:schema "PUBLIC" :name "CHECKINS" :id (data/id :checkins)}, :join-tables nil}
+      :filter       {:filter-type :between,
+                     :field       date-field-literal
+                     :min-val     {:value (tcoerce/to-timestamp (du/str->date-time "2015-01-01"))
+                                   :field {:field date-field-literal, :unit :default}},
+                     :max-val     {:value (tcoerce/to-timestamp (du/str->date-time "2015-02-01"))
+                                   :field {:field date-field-literal, :unit :default}}}}))
   (tt/with-temp Card [card {:dataset_query {:database (data/id)
                                             :type     :query
                                             :query    {:source-table (data/id :checkins)}}}]
     (expand-and-scrub {:database database/virtual-id
                        :type     :query
                        :query    {:source-table (str "card__" (u/get-id card))
-                                  :filter ["BETWEEN" ["field-id" ["field-literal" "date" "type/Date"]] "2015-01-01" "2015-02-01"]}})))
+                                  :filter       ["BETWEEN" ["field-id" ["field-literal" "date" "type/Date"]] "2015-01-01" "2015-02-01"]}})))
 
 ;; make sure that nested nested queries work as expected
 (expect
diff --git a/test/metabase/query_processor/middleware/results_metadata_test.clj b/test/metabase/query_processor/middleware/results_metadata_test.clj
index 301942ad7b933d765b69439bd70d4824425d4fc2..e681b7e31866bf3b2480945e0737ef5b4bea3dc3 100644
--- a/test/metabase/query_processor/middleware/results_metadata_test.clj
+++ b/test/metabase/query_processor/middleware/results_metadata_test.clj
@@ -91,7 +91,9 @@
   [{:base_type    "type/Text"
     :display_name "Date"
     :name         "DATE"
-    :unit         nil}
+    :unit         nil
+    :fingerprint  {:global {:distinct-count 618}, :type {:type/DateTime {:earliest "2013-01-03T00:00:00.000Z"
+                                                                         :latest "2015-12-29T00:00:00.000Z"}}}}
    {:base_type    "type/Integer"
     :display_name "count"
     :name         "count"
diff --git a/test/metabase/query_processor_test/breakout_test.clj b/test/metabase/query_processor_test/breakout_test.clj
index cdca9fa82415ed980cb3b9e5995c0838ca0075d5..a0a7e040ae6fd014fb0ec5ba11cb9dd5e8a0e2c7 100644
--- a/test/metabase/query_processor_test/breakout_test.clj
+++ b/test/metabase/query_processor_test/breakout_test.clj
@@ -5,9 +5,11 @@
              [query-processor-test :refer :all]
              [util :as u]]
             [metabase.models
+             [card :refer [Card]]
              [dimension :refer [Dimension]]
              [field :refer [Field]]
              [field-values :refer [FieldValues]]]
+            [metabase.query-processor :as qp]
             [metabase.query-processor.middleware
              [add-dimension-projections :as add-dim-projections]
              [expand :as ql]]
@@ -17,7 +19,8 @@
             [metabase.test.data
              [dataset-definitions :as defs]
              [datasets :as datasets]]
-            [toucan.db :as db]))
+            [toucan.db :as db]
+            [toucan.util.test :as tt]))
 
 ;;; single column
 (qp-expect-with-all-engines
@@ -245,3 +248,34 @@
                         (ql/aggregation (ql/count))
                         (ql/breakout (ql/binning-strategy $latitude :default)))
         (select-keys [:status :class :error]))))
+
+(defn- field->result-metadata [field]
+  (select-keys field [:name :display_name :description :base_type :special_type :unit :fingerprint]))
+
+(defn- nested-venues-query [card-or-card-id]
+  {:database metabase.models.database/virtual-id
+   :type :query
+   :query {:source-table (str "card__" (u/get-id card-or-card-id))
+           :aggregation  [:count]
+           :breakout     [(ql/binning-strategy (ql/field-literal "LATITUDE" :type/Float) :num-bins 20)]}})
+
+;; Binning should be allowed on nested queries that have result metadata
+(datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
+  [[10.0 1] [32.0 4] [34.0 57] [36.0 29] [40.0 9]]
+  (tt/with-temp Card [card {:dataset_query {:database (data/id)
+                                            :type :query
+                                            :query {:source-query {:source-table (data/id :venues)}}}
+                            :result_metadata (mapv field->result-metadata (db/select Field :table_id (data/id :venues)))}]
+    (-> (nested-venues-query card)
+        qp/process-query
+        rows)))
+
+;; Binning is not supported when there is no fingerprint to determine boundaries
+(datasets/expect-with-engines (non-timeseries-engines-with-feature :binning)
+  Exception
+  (tt/with-temp Card [card {:dataset_query {:database (data/id)
+                                            :type :query
+                                            :query {:source-query {:source-table (data/id :venues)}}}}]
+    (-> (nested-venues-query card)
+        qp/process-query
+        rows)))