diff --git a/db/post_migrate/20230501165244_remove_software_license_policies_without_scan_result_policy_id.rb b/db/post_migrate/20230501165244_remove_software_license_policies_without_scan_result_policy_id.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0e3b15a933c573e99cd0342c8d9022dde3663bbc
--- /dev/null
+++ b/db/post_migrate/20230501165244_remove_software_license_policies_without_scan_result_policy_id.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class RemoveSoftwareLicensePoliciesWithoutScanResultPolicyId < Gitlab::Database::Migration[2.1]
+  disable_ddl_transaction!
+
+  restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+  BATCH_SIZE = 1000
+
+  def up
+    each_batch_range('software_license_policies',
+      scope: ->(table) { table.where(scan_result_policy_id: nil) },
+      of: BATCH_SIZE) do |min, max|
+      execute("DELETE FROM software_license_policies WHERE id BETWEEN #{min} AND #{max}")
+    end
+  end
+
+  def down
+    # NO-OP
+  end
+end
diff --git a/db/schema_migrations/20230501165244 b/db/schema_migrations/20230501165244
new file mode 100644
index 0000000000000000000000000000000000000000..df068bbd3fd0a2d8a982e410588651f9dca94c06
--- /dev/null
+++ b/db/schema_migrations/20230501165244
@@ -0,0 +1 @@
+8d1f891b30ff45432ae9dff5d97d6d241dd98c168f4b5fe6db6637bf93dd18e3
\ No newline at end of file
diff --git a/ee/spec/migrations/20230501165244_remove_software_license_policies_without_scan_result_policy_id_spec.rb b/ee/spec/migrations/20230501165244_remove_software_license_policies_without_scan_result_policy_id_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d54167b5e4de34f2913312795847283e3f4c571d
--- /dev/null
+++ b/ee/spec/migrations/20230501165244_remove_software_license_policies_without_scan_result_policy_id_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe RemoveSoftwareLicensePoliciesWithoutScanResultPolicyId, feature_category: :security_policy_management do
+  let(:migration) { described_class.new }
+
+  let(:software_license_policies) { table(:software_license_policies) }
+  let(:projects) { table(:projects) }
+  let(:namespace) { table(:namespaces).create!(name: 'name', path: 'path') }
+  let(:project) { projects.create!(namespace_id: namespace.id, project_namespace_id: namespace.id) }
+  let(:scan_result_policies) { table(:scan_result_policies) }
+  let(:security_orchestration_policy_configurations) { table(:security_orchestration_policy_configurations) }
+  let(:software_licenses) { table(:software_licenses) }
+
+  let!(:security_orchestration_policy_configuration) do
+    security_orchestration_policy_configurations.create!(namespace_id: namespace.id,
+      security_policy_management_project_id: project.id)
+  end
+
+  let!(:scan_result_policy) do
+    scan_result_policies.create!(
+      security_orchestration_policy_configuration_id: security_orchestration_policy_configuration.id,
+      orchestration_policy_idx: 1)
+  end
+
+  let!(:spdx_identifier_license) { software_licenses.create!(name: 'spdx license') }
+
+  let!(:license_policy_with_scan_result_policy_id) do
+    software_license_policies.create!(project_id: project.id, software_license_id: spdx_identifier_license.id,
+      scan_result_policy_id: scan_result_policy.id)
+  end
+
+  describe '#up' do
+    context 'with orphan software licenses' do
+      let!(:license_policy_without_scan_result_policy_id) do
+        software_license_policies.create!(project_id: project.id, software_license_id: spdx_identifier_license.id)
+      end
+
+      it 'deletes only orphan software licenses' do
+        expect { migrate! }.to change { SoftwareLicensePolicy.count }.from(2).to(1)
+      end
+    end
+
+    context 'without orphan licenses' do
+      it 'does not delete any software license' do
+        expect { migrate! }.not_to change { SoftwareLicensePolicy.count }
+      end
+    end
+  end
+end