diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index a779ceb2e2a4a019db0c9dce5f60c15ba2b5e2af..02b10f3e4bd6f42a0a3c2aafe5414e001c2303f4 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -87,8 +87,10 @@ class JobType < BaseObject
                                                 description: 'Play path of the job.'
       field :playable, GraphQL::Types::Boolean, null: false, method: :playable?,
                                                 description: 'Indicates the job can be played.'
-      field :previous_stage_jobs_or_needs, Types::Ci::JobNeedUnion.connection_type, null: true,
-                                                                                    description: 'Jobs that must complete before the job runs. Returns `BuildNeed`, which is the needed jobs if the job uses the `needs` keyword, or the previous stage jobs otherwise.'
+      field :previous_stage_jobs_or_needs, Types::Ci::JobNeedUnion.connection_type,
+            null: true,
+            description: 'Jobs that must complete before the job runs. Returns `BuildNeed`, ' \
+                         'which is the needed jobs if the job uses the `needs` keyword, or the previous stage jobs otherwise.'
       field :ref_name, GraphQL::Types::String, null: true,
                                                description: 'Ref name of the job.'
       field :ref_path, GraphQL::Types::String, null: true,
@@ -179,7 +181,9 @@ def previous_stage_jobs
             stages = pipeline.stages.by_position(positions)
 
             stages.each do |stage|
-              loader.call([pipeline, stage.position], stage.latest_statuses)
+              # Without `.to_a`, the memoization will only preserve the activerecord relation object. And when there is
+              # a call, the SQL query will be executed again.
+              loader.call([pipeline, stage.position], stage.latest_statuses.to_a)
             end
           end
         end
diff --git a/app/graphql/types/ci/stage_type.rb b/app/graphql/types/ci/stage_type.rb
index c0f3d1db57b3c06c2612d6df84a37480b57a1f15..a9d8075329d91028fd27fbbe909200dfc02c97b2 100644
--- a/app/graphql/types/ci/stage_type.rb
+++ b/app/graphql/types/ci/stage_type.rb
@@ -33,7 +33,7 @@ def groups(lookahead:)
           by_pipeline = keys.group_by(&:pipeline)
           include_needs = keys.any? do |k|
             k.requires?(%i[nodes jobs nodes needs]) ||
-            k.requires?(%i[nodes jobs nodes previousStageJobsAndNeeds])
+            k.requires?(%i[nodes jobs nodes previousStageJobsOrNeeds])
           end
 
           by_pipeline.each do |pl, key_group|
diff --git a/spec/requests/api/graphql/project/jobs_spec.rb b/spec/requests/api/graphql/project/jobs_spec.rb
index aea6cad9e627cd70ef36ced7a7caf9b293577dbd..2c45c7e9b79af2296feded690785e3cee21aa8fb 100644
--- a/spec/requests/api/graphql/project/jobs_spec.rb
+++ b/spec/requests/api/graphql/project/jobs_spec.rb
@@ -11,6 +11,9 @@
     create(:ci_pipeline, project: project, user: user)
   end
 
+  let!(:job1) { create(:ci_build, pipeline: pipeline, name: 'job 1') }
+  let!(:job2) { create(:ci_build, pipeline: pipeline, name: 'job 2') }
+
   let(:query) do
     <<~QUERY
     {
@@ -18,11 +21,6 @@
         jobs {
           nodes {
             name
-            previousStageJobsAndNeeds {
-              nodes {
-                name
-              }
-            }
           }
         }
       }
@@ -30,27 +28,10 @@
     QUERY
   end
 
-  it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
-    build_stage = create(:ci_stage, position: 1, name: 'build', project: project, pipeline: pipeline)
-    test_stage = create(:ci_stage, position: 2, name: 'test', project: project, pipeline: pipeline)
-    create(:ci_build, pipeline: pipeline, name: 'docker 1 2', ci_stage: build_stage)
-    create(:ci_build, pipeline: pipeline, name: 'docker 2 2', ci_stage: build_stage)
-    create(:ci_build, pipeline: pipeline, name: 'rspec 1 2', ci_stage: test_stage)
-    test_job = create(:ci_build, pipeline: pipeline, name: 'rspec 2 2', ci_stage: test_stage)
-    create(:ci_build_need, build: test_job, name: 'docker 1 2')
-
+  it 'fetches jobs' do
     post_graphql(query, current_user: user)
+    expect_graphql_errors_to_be_empty
 
-    control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
-      post_graphql(query, current_user: user)
-    end
-
-    create(:ci_build, name: 'test-a', ci_stage: test_stage, pipeline: pipeline)
-    test_b_job = create(:ci_build, name: 'test-b', ci_stage: test_stage, pipeline: pipeline)
-    create(:ci_build_need, build: test_b_job, name: 'docker 2 2')
-
-    expect do
-      post_graphql(query, current_user: user)
-    end.not_to exceed_all_query_limit(control)
+    expect(graphql_data['project']['jobs']['nodes'].pluck('name')).to contain_exactly('job 1', 'job 2')
   end
 end
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index fb1489372fcffb1464f4aba42bfe8986f541e81e..d20ee5bfdffb4d17e0871770da0005886cd86af6 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -337,16 +337,33 @@ def successful_pipeline
     end
   end
 
-  context 'N+1 queries on pipeline jobs' do
+  context 'N+1 queries on pipeline jobs.previousStageJobsOrNeeds' do
     let(:pipeline) { create(:ci_pipeline, project: project) }
 
     let(:fields) do
       <<~FIELDS
-      jobs {
+      stages {
         nodes {
-          previousStageJobsAndNeeds {
+          groups {
             nodes {
-              name
+              jobs {
+                nodes {
+                  previousStageJobsOrNeeds {
+                    nodes {
+                      ... on JobNeedUnion {
+                        ... on CiBuildNeed {
+                          id
+                          name
+                        }
+                        ... on CiJob {
+                          id
+                          name
+                        }
+                      }
+                    }
+                  }
+                }
+              }
             }
           }
         }
@@ -357,13 +374,15 @@ def successful_pipeline
     it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
       build_stage = create(:ci_stage, position: 1, name: 'build', project: project, pipeline: pipeline)
       test_stage = create(:ci_stage, position: 2, name: 'test', project: project, pipeline: pipeline)
-      create(:ci_build, pipeline: pipeline, name: 'docker 1 2', ci_stage: build_stage)
+
+      docker_1_2 = create(:ci_build, pipeline: pipeline, name: 'docker 1 2', ci_stage: build_stage)
       create(:ci_build, pipeline: pipeline, name: 'docker 2 2', ci_stage: build_stage)
       create(:ci_build, pipeline: pipeline, name: 'rspec 1 2', ci_stage: test_stage)
-      test_job = create(:ci_build, pipeline: pipeline, name: 'rspec 2 2', ci_stage: test_stage)
-      create(:ci_build_need, build: test_job, name: 'docker 1 2')
+      create(:ci_build, :dependent, needed: docker_1_2, pipeline: pipeline, name: 'rspec 2 2', ci_stage: test_stage)
 
+      # warm up
       post_graphql(query, current_user: current_user)
+      expect_graphql_errors_to_be_empty
 
       control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
         post_graphql(query, current_user: current_user)
@@ -371,7 +390,7 @@ def successful_pipeline
 
       create(:ci_build, name: 'test-a', ci_stage: test_stage, pipeline: pipeline)
       test_b_job = create(:ci_build, name: 'test-b', ci_stage: test_stage, pipeline: pipeline)
-      create(:ci_build_need, build: test_b_job, name: 'docker 2 2')
+      create(:ci_build, :dependent, needed: test_b_job, pipeline: pipeline, name: 'docker 2 2', ci_stage: test_stage)
 
       expect do
         post_graphql(query, current_user: current_user)
@@ -424,6 +443,7 @@ def successful_pipeline
 
       # warm up
       post_graphql(query, current_user: current_user)
+      expect_graphql_errors_to_be_empty
 
       control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
         post_graphql(query, current_user: current_user)