diff --git a/frontend/src/card/card.controllers.js b/frontend/src/card/card.controllers.js
index ccbd9c3c8112c28057298e0cf592f49a3025fb94..249bca54c3b95b9f0571599ee98d12c8ba97aca6 100644
--- a/frontend/src/card/card.controllers.js
+++ b/frontend/src/card/card.controllers.js
@@ -219,22 +219,16 @@ CardControllers.controller('CardDetail', [
                 renderAll();
             },
             setSortFn: function(fieldId) {
-                // for now, just put this into the query and re-run
-                var sortField = fieldId;
-                if (fieldId === "agg") {
-                    sortField = ["aggregation", 0];
-                }
-
                 // NOTE: we only allow this for structured type queries & we only allow sorting by a single column
                 if (card.dataset_query.type === "query") {
-                    var sortClause = [sortField, "ascending"];
+                    var sortClause = [fieldId, "ascending"];
                     if (card.dataset_query.query.order_by &&
                         card.dataset_query.query.order_by.length > 0 &&
                         card.dataset_query.query.order_by[0].length > 0 &&
                         card.dataset_query.query.order_by[0][1] === "ascending" &&
-                        Query.isSameField(card.dataset_query.query.order_by[0][0], sortField)) {
+                        Query.isSameField(card.dataset_query.query.order_by[0][0], fieldId)) {
                         // someone triggered another sort on the same column, so flip the sort direction
-                        sortClause = [sortField, "descending"];
+                        sortClause = [fieldId, "descending"];
                     }
 
                     // set clause
diff --git a/frontend/src/lib/query.js b/frontend/src/lib/query.js
index 1ea69230285aa04d3b1b1e4e758cc267acfbd848..91c893c5f0b39751c5fac2388a1f6b1b183b9efb 100644
--- a/frontend/src/lib/query.js
+++ b/frontend/src/lib/query.js
@@ -59,34 +59,39 @@ var Query = {
             query.order_by = query.order_by.map((s) => {
                 let field = s[0];
 
+                // remove incomplete sorts
+                if (!Query.isValidField(field) || s[1] == null) {
+                    return null;
+                }
+
                 if (Query.isAggregateField(field)) {
                     // remove aggregation sort if we can't sort by this aggregation
-                    if (!Query.canSortByAggregateField(query)) {
-                        return null;
+                    if (Query.canSortByAggregateField(query)) {
+                        return s;
                     }
                 } else if (Query.hasValidBreakout(query)) {
-                    // remove sort if it references a non-broken-out field
-                    let matchingBreakout = query.breakout.filter(b => Query.isSameField(b, field));
-                    if (matchingBreakout.length > 0) {
-                        // query processor expect the order_by clause to match the breakout's granularity
-                        if (Array.isArray(matchingBreakout[0]) && matchingBreakout[0][0] === "datetime_field") {
-                            return [matchingBreakout[0], s[1]];
-                        } else {
-                            return s;
+                    let exactMatches = query.breakout.filter(b => Query.isSameField(b, field, true));
+                    if (exactMatches.length > 0) {
+                        return s;
+                    }
+                    let targetMatches = query.breakout.filter(b => Query.isSameField(b, field, false));
+                    if (targetMatches.length > 0) {
+                        // query processor expect the order_by clause to match the breakout's datetime_field unit or fk-> target,
+                        // so just replace it with the one that matches the target field
+                        // NOTE: if we have more than one breakout for the same target field this could match the wrong one
+                        if (targetMatches.length > 1) {
+                            console.warn("Sort clause matches more than one breakout field", s[0], targetMatches);
                         }
-                    } else {
-                        return null;
+                        return [targetMatches[0], s[1]];
                     }
-                } else if (!Query.isBareRowsAggregation(query)) {
-                    // otherwise remove sort if it doesn't have a breakout but isn't a bare rows aggregation
-                    return null;
-                }
-                // remove incomplete sorts
-                if (Query.isValidField(s[0]) && s[1] != null) {
+                } else if (Query.isBareRowsAggregation(query)) {
                     return s;
                 }
+
+                // otherwise remove sort if it doesn't have a breakout but isn't a bare rows aggregation
                 return null;
             }).filter(s => s != null);
+
             if (query.order_by.length === 0) {
                 delete query.order_by;
             }
@@ -99,10 +104,6 @@ var Query = {
         return query;
     },
 
-    isAggregateField: function(field) {
-        return Array.isArray(field) && field[0] === "aggregation";
-    },
-
     canAddDimensions: function(query) {
         var MAX_DIMENSIONS = 2;
         return query && query.breakout && (query.breakout.length < MAX_DIMENSIONS);
@@ -347,49 +348,63 @@ var Query = {
         }
     },
 
+    isRegularField: function(field) {
+        return typeof field === "number";
+    },
+
+    isForeignKeyField: function(field) {
+        return Array.isArray(field) && field[0] === "fk->";
+    },
+
+    isDatetimeField: function(field) {
+        return Array.isArray(field) && field[0] === "datetime_field";
+    },
+
+    isAggregateField: function(field) {
+        return Array.isArray(field) && field[0] === "aggregation";
+    },
+
     isValidField: function(field) {
         return (
-            typeof field === "number" ||
-            (Array.isArray(field) && (
-                (field[0] === 'fk->' && typeof field[1] === "number" && typeof field[2] === "number") ||
-                (field[0] === 'datetime_field' && Query.isValidField(field[1]) && field[2] === "as" && typeof field[3] === "string") ||
-                (field[0] === 'aggregation' && typeof field[1] === "number")
-            ))
+            (Query.isRegularField(field)) ||
+            (Query.isForeignKeyField(field) && Query.isRegularField(field[1]) && Query.isRegularField(field[2])) ||
+            (Query.isDatetimeField(field)   && Query.isValidField(field[1]) && field[2] === "as" && typeof field[3] === "string") ||
+            (Query.isAggregateField(field)  && typeof field[1] === "number")
         );
     },
 
-    isSameField: function(fieldA, fieldB, strict = false) {
-        if (strict) {
+    isSameField: function(fieldA, fieldB, exact = false) {
+        if (exact) {
             return _.isEqual(fieldA, fieldB);
         } else {
             return Query.getFieldTargetId(fieldA) === Query.getFieldTargetId(fieldB);
         }
     },
 
+    // gets the target field ID (recursively) from any type of field, including raw field ID, fk->, and datetime_field cast.
     getFieldTargetId: function(field) {
-        if (Array.isArray(field)) {
-            if (field[0] === "fk->") {
-                return field[2];
-            }
-            if (field[0] === "datetime_field") {
-                return field[1];
-            }
-        } else {
+        if (Query.isRegularField(field)) {
             return field;
+        } else if (Query.isForeignKeyField(field)) {
+            return Query.getFieldTargetId(field[2]);
+        } else if (Query.isDatetimeField(field)) {
+            return Query.getFieldTargetId(field[1]);
         }
+        console.warn("Unknown field type: ", field);
     },
 
-    getFieldTarget: function(field, tableMetadata) {
-        let table, fieldId, fk;
-        if (Array.isArray(field) && field[0] === "fk->") {
-            fk = tableMetadata.fields_lookup[field[1]];
-            table = fk.target.table;
-            fieldId = field[2];
-        } else {
-            table = tableMetadata;
-            fieldId = field;
+    // gets the table and field definitions from from a raw, fk->, or datetime_field field
+    getFieldTarget: function(field, tableDef) {
+        if (Query.isRegularField(field)) {
+            return { table: tableDef, field: tableDef.fields_lookup[field] };
+        } else if (Query.isForeignKeyField(field)) {
+            let fkFieldDef = tableDef.fields_lookup[field[1]];
+            let targetTableDef = fkFieldDef.target.table;
+            return Query.getFieldTarget(field[2], targetTableDef);
+        } else if (Query.isDatetimeField(field)) {
+            return Query.getFieldTarget(field[1], tableDef);
         }
-        return { table, field: table.fields_lookup[fieldId] };
+        console.warn("Unknown field type: ", field);
     },
 
     getFieldOptions: function(fields, includeJoins = false, filterFn = (fields) => fields, usedFields = {}) {
@@ -403,7 +418,7 @@ var Query = {
         results.count += results.fields.length;
         if (includeJoins) {
             results.fks = fields.filter((f) => f.special_type === "fk" && f.target).map((joinField) => {
-                var targetFields = filterFn(joinField.target.table.fields).filter((f) => (!Array.isArray(f.id) || f.id[0] !== "aggregation") && !usedFields[f.id]);
+                var targetFields = filterFn(joinField.target.table.fields).filter(f => (!Array.isArray(f.id) || f.id[0] !== "aggregation") && !usedFields[f.id]);
                 results.count += targetFields.length;
                 return {
                     field: joinField,
@@ -420,15 +435,15 @@ var Query = {
         }
 
         function getFieldName(id, table) {
-            if (Array.isArray(id)) {
-                if (id[0] === "fk->") {
-                    var field = table.fields_lookup[id[1]];
-                    if (field) {
-                        return field.display_name.replace(/\s+id$/i, "") + " → " + getFieldName(id[2], field.target.table);
-                    }
-                } else if (id[0] === "datetime_field") {
-                    return getFieldName(id[1], table) + " (by " + id[3] + ")";
+            if (Query.isForeignKeyField(id)) {
+                var field = table.fields_lookup[id[1]];
+                if (field) {
+                    return field.display_name.replace(/\s+id$/i, "") + " → " + getFieldName(id[2], field.target.table);
                 }
+            } else if (Query.isDatetimeField(id)) {
+                return getFieldName(id[1], table) + " (by " + id[3] + ")";
+            } else if (Query.isAggregateField(id)) {
+                return "aggregation";
             } else if (table.fields_lookup[id]) {
                 return table.fields_lookup[id].display_name
             }
diff --git a/frontend/src/query_builder/QueryVisualizationTable.jsx b/frontend/src/query_builder/QueryVisualizationTable.jsx
index 64879b19e834d20bb25ebbe0b02d17794146fdd4..2024f89ae0c0af565fbbde4229363025dc9d927a 100644
--- a/frontend/src/query_builder/QueryVisualizationTable.jsx
+++ b/frontend/src/query_builder/QueryVisualizationTable.jsx
@@ -132,8 +132,15 @@ export default class QueryVisualizationTable extends Component {
         return (this.props.setSortFn !== undefined);
     }
 
-    setSort(fieldId) {
-        this.props.setSortFn(fieldId);
+    setSort(column) {
+        if (column.id == null) {
+            // ICK.  this is hacky for dealing with aggregations.  need something better
+            this.props.setSortFn(["aggregation", 0]);
+        } else if (column.unit != null) {
+            this.props.setSortFn(["datetime_field", column.id, "as", column.unit]);
+        } else {
+            this.props.setSortFn(column.id);
+        }
 
         MetabaseAnalytics.trackEvent('QueryBuilder', 'Set Sort', 'table column');
     }
@@ -243,11 +250,8 @@ export default class QueryVisualizationTable extends Component {
         }
 
         if (this.isSortable()) {
-            // ICK.  this is hacky for dealing with aggregations.  need something better
-            var fieldId = (column.id) ? column.id : "agg";
-
             return (
-                <div key={columnIndex} className={headerClasses} onClick={this.setSort.bind(this, fieldId)}>
+                <div key={columnIndex} className={headerClasses} onClick={this.setSort.bind(this, column)}>
                     <span>
                         {colVal}
                     </span>
diff --git a/frontend/test/unit/lib/query.spec.js b/frontend/test/unit/lib/query.spec.js
index e08332267b98fe4d48323486310502c2c5f84f3e..e6a78fb928f8fde1d3e4cba6c75ae14d6d820454 100644
--- a/frontend/test/unit/lib/query.spec.js
+++ b/frontend/test/unit/lib/query.spec.js
@@ -84,6 +84,62 @@ describe('Query', () => {
             Query.cleanQuery(query);
             expect(query.order_by).toBe(undefined);
         });
+
+        it('should not remove sort clauses with foreign keys on fields appearing in breakout', () => {
+            let query = {
+                source_table: 0,
+                aggregation: ["count"],
+                breakout: [["fk->", 1, 2]],
+                filter: [],
+                order_by: [
+                    [["fk->", 1, 2], "ascending"]
+                ]
+            };
+            Query.cleanQuery(query);
+            expect(JSON.stringify(query.order_by)).toBe(JSON.stringify([[["fk->", 1, 2], "ascending"]]));
+        });
+
+        it('should not remove sort clauses with datetime_fields on fields appearing in breakout', () => {
+            let query = {
+                source_table: 0,
+                aggregation: ["count"],
+                breakout: [["datetime_field", 1, "as", "week"]],
+                filter: [],
+                order_by: [
+                    [["datetime_field", 1, "as", "week"], "ascending"]
+                ]
+            };
+            Query.cleanQuery(query);
+            expect(JSON.stringify(query.order_by)).toBe(JSON.stringify([[["datetime_field", 1, "as", "week"], "ascending"]]));
+        });
+
+        it('should replace order_by clauses with the exact matching datetime_fields version in the breakout', () => {
+            let query = {
+                source_table: 0,
+                aggregation: ["count"],
+                breakout: [["datetime_field", 1, "as", "week"]],
+                filter: [],
+                order_by: [
+                    [1, "ascending"]
+                ]
+            };
+            Query.cleanQuery(query);
+            expect(JSON.stringify(query.order_by)).toBe(JSON.stringify([[["datetime_field", 1, "as", "week"], "ascending"]]));
+        });
+
+        it('should replace order_by clauses with the exact matching fk-> version in the breakout', () => {
+            let query = {
+                source_table: 0,
+                aggregation: ["count"],
+                breakout: [["fk->", 1, 2]],
+                filter: [],
+                order_by: [
+                    [2, "ascending"]
+                ]
+            };
+            Query.cleanQuery(query);
+            expect(JSON.stringify(query.order_by)).toBe(JSON.stringify([[["fk->", 1, 2], "ascending"]]));
+        });
     });
 
     describe('removeDimension', () => {