diff --git a/src/optimizer/rule/IndexScanRule.cpp b/src/optimizer/rule/IndexScanRule.cpp
index 9aedb52f1f553434e8baa0376a00de33eb202f0e..78fffec7e53cc9577f9063f545ef3fb08ae456e4 100644
--- a/src/optimizer/rule/IndexScanRule.cpp
+++ b/src/optimizer/rule/IndexScanRule.cpp
@@ -31,15 +31,16 @@ StatusOr<OptRule::TransformResult> IndexScanRule::transform(graph::QueryContext*
                                                             const MatchedResult& matched) const {
     auto groupNode = matched.node;
     auto filter = filterExpr(groupNode);
+    IndexQueryCtx iqctx = std::make_unique<std::vector<IndexQueryContext>>();
     if (filter == nullptr) {
-        return Status::SemanticError("WHERE clause error");
+        // Only filter is nullptr when lookup on tagname
+        NG_RETURN_IF_ERROR(createIndexQueryCtx(iqctx, qctx, groupNode));
+    } else {
+        FilterItems items;
+        ScanKind kind;
+        NG_RETURN_IF_ERROR(analyzeExpression(filter.get(), &items, &kind, isEdge(groupNode)));
+        NG_RETURN_IF_ERROR(createIndexQueryCtx(iqctx, kind, items, qctx, groupNode));
     }
-    FilterItems items;
-    ScanKind kind;
-    NG_RETURN_IF_ERROR(analyzeExpression(filter.get(), &items, &kind, isEdge(groupNode)));
-
-    IndexQueryCtx iqctx = std::make_unique<std::vector<IndexQueryContext>>();
-    NG_RETURN_IF_ERROR(createIndexQueryCtx(iqctx, kind, items, qctx, groupNode));
 
     auto newIN = static_cast<const IndexScan*>(groupNode->node())->clone(qctx);
     newIN->setIndexQueryContext(std::move(iqctx));
@@ -68,6 +69,20 @@ Status IndexScanRule::createIndexQueryCtx(IndexQueryCtx &iqctx,
            : createIQCWithLogicOR(iqctx, items, qctx, groupNode);
 }
 
+Status IndexScanRule::createIndexQueryCtx(IndexQueryCtx &iqctx,
+                                          graph::QueryContext *qctx,
+                                          const OptGroupNode *groupNode) const {
+    auto index = findLightestIndex(qctx, groupNode);
+    if (index == nullptr) {
+        return Status::IndexNotFound("No valid index found");
+    }
+    auto ret = appendIQCtx(index, iqctx);
+    NG_RETURN_IF_ERROR(ret);
+
+    return Status::OK();
+}
+
+
 Status IndexScanRule::createIQCWithLogicAnd(IndexQueryCtx &iqctx,
                                             const FilterItems& items,
                                             graph::QueryContext *qctx,
@@ -148,6 +163,15 @@ Status IndexScanRule::appendIQCtx(const IndexItem& index,
     return Status::OK();
 }
 
+Status IndexScanRule::appendIQCtx(const IndexItem& index,
+                                  IndexQueryCtx &iqctx) const {
+    IndexQueryContext ctx;
+    ctx.set_index_id(index->get_index_id());
+    ctx.set_filter("");
+    iqctx->emplace_back(std::move(ctx));
+    return Status::OK();
+}
+
 #define CHECK_BOUND_VALUE(v, name)                                                                 \
     do {                                                                                           \
         if (v == Value::kNullBadType) {                                                      \
@@ -277,8 +301,12 @@ std::unique_ptr<Expression>
 IndexScanRule::filterExpr(const OptGroupNode *groupNode) const {
     auto in = static_cast<const IndexScan *>(groupNode->node());
     auto qct = in->queryContext();
-    // The initial IndexScan plan node has only one queryContext.
+    // The initial IndexScan plan node has only zeor or one queryContext.
     // TODO(yee): Move this condition to match interface
+    if (qct == nullptr) {
+        return nullptr;
+    }
+
     if (qct->size() != 1) {
         LOG(ERROR) << "Index Scan plan node error";
         return nullptr;
@@ -406,6 +434,24 @@ IndexItem IndexScanRule::findOptimalIndex(graph::QueryContext *qctx,
     return indexesRange[0];
 }
 
+// Find the index with the fewest fields
+// Only use "lookup on tagname"
+IndexItem IndexScanRule::findLightestIndex(graph::QueryContext *qctx,
+                                           const OptGroupNode *groupNode) const {
+    auto indexes = allIndexesBySchema(qctx, groupNode);
+    if (indexes.empty()) {
+        return nullptr;
+    }
+
+    auto result = indexes[0];
+    for (size_t i = 1; i < indexes.size(); i++) {
+        if (result->get_fields().size() > indexes[i]->get_fields().size()) {
+            result = indexes[i];
+        }
+    }
+    return result;
+}
+
 std::vector<IndexItem>
 IndexScanRule::allIndexesBySchema(graph::QueryContext *qctx,
                                   const OptGroupNode *groupNode) const {
diff --git a/src/optimizer/rule/IndexScanRule.h b/src/optimizer/rule/IndexScanRule.h
index 1fcffd8285dfbdc2e652f372e4719f9cdf76a01f..7581475192335eb244366c889d92ae5bd99615fb 100644
--- a/src/optimizer/rule/IndexScanRule.h
+++ b/src/optimizer/rule/IndexScanRule.h
@@ -108,6 +108,10 @@ private:
                                graph::QueryContext *qctx,
                                const OptGroupNode *groupNode) const;
 
+    Status createIndexQueryCtx(IndexQueryCtx &iqctx,
+                               graph::QueryContext *qctx,
+                               const OptGroupNode *groupNode) const;
+
     Status createIQCWithLogicAnd(IndexQueryCtx &iqctx,
                                  const FilterItems& items,
                                  graph::QueryContext *qctx,
@@ -123,6 +127,9 @@ private:
                        IndexQueryCtx &iqctx,
                        const std::string& filter = "") const;
 
+    Status appendIQCtx(const IndexItem& index,
+                       IndexQueryCtx &iqctx) const;
+
     Status appendColHint(std::vector<IndexColumnHint>& hitns,
                          const FilterItems& items,
                          const meta::cpp2::ColumnDef& col) const;
@@ -155,6 +162,9 @@ private:
                                const OptGroupNode *groupNode,
                                const FilterItems& items) const;
 
+    IndexItem findLightestIndex(graph::QueryContext *qctx,
+                                const OptGroupNode *groupNode) const;
+
     std::vector<IndexItem>
     allIndexesBySchema(graph::QueryContext *qctx, const OptGroupNode *groupNode) const;
 
diff --git a/src/validator/IndexScanValidator.cpp b/src/validator/IndexScanValidator.cpp
index 758093256a33878d2d0cb079d5f821f12ebe0ece..c814fb832c3f90f793cc15f78af1b266f96df52f 100644
--- a/src/validator/IndexScanValidator.cpp
+++ b/src/validator/IndexScanValidator.cpp
@@ -46,6 +46,12 @@ Status IndexScanValidator::prepareYield() {
     if (sentence->yieldClause() == nullptr) {
         return Status::OK();
     }
+    // When whereClause is nullptr, yieldClause is not nullptr,
+    // only return vid for tag. return src, ranking, dst for edge
+    if (sentence->whereClause() == nullptr) {
+        return Status::SemanticError("Yield clauses are not supported "
+                                     "when WHERE clause does not exist");
+    }
     auto columns = sentence->yieldClause()->columns();
     auto schema = isEdge_
                   ? qctx_->schemaMng()->getEdgeSchema(spaceId_, schemaId_)
@@ -82,6 +88,10 @@ Status IndexScanValidator::prepareYield() {
 
 Status IndexScanValidator::prepareFilter() {
     auto *sentence = static_cast<const LookupSentence *>(sentence_);
+    if (sentence->whereClause() == nullptr) {
+        return Status::OK();
+    }
+
     auto *filter = sentence->whereClause()->filter();
     auto ret = checkFilter(filter, *sentence->from());
     NG_RETURN_IF_ERROR(ret);
diff --git a/src/validator/IndexScanValidator.h b/src/validator/IndexScanValidator.h
index a8e686ebcaee8dc37c7f4ffcb9dccadbea8e539a..4d27eed343260c62420601957b1a93297d699fa9 100644
--- a/src/validator/IndexScanValidator.h
+++ b/src/validator/IndexScanValidator.h
@@ -42,7 +42,7 @@ private:
 
 private:
     GraphSpaceID               spaceId_{0};
-    IndexScan::IndexQueryCtx   contexts_{};
+    IndexScan::IndexQueryCtx   contexts_{nullptr};
     IndexScan::IndexReturnCols returnCols_{};
     bool                       isEdge_{false};
     int32_t                    schemaId_;