I think the doc is clear that it will only list the relationships between components so the lack of one would not be returned.
Usage
Use SOQL queries to list the relationships between the metadata components in your org. The query results include one row for each relationship. Each relationship is a directional dependency between two metadata components.
I think that answers the question of whether you could do a single SOQL query on this to get components without a relationship - probably not. It'd be cool to use an anti-join in your 2nd example, but I get an error saying
semi join sub selects can only query id fields, cannot use:
'RefMetadataComponentId'
If they open that up (as this is in beta), that could be a good option. However, you're not too far off from something without much extra effort (extra query and two for loops). I did the below in anonymous apex (and deployed the wrapper class to dev environment first) just as a quick POC.
//Class to deserialize the response from Tooling API query
public class ApexDependencyWrapper {
public List<DependencyRecords> records {get; set;}
public class DependencyRecords{
//class being referred to
public String RefMetadataComponentName {get; set;}
//class relying on the above
public String MetadataComponentName {get; set;}
}
}
//Used in anonymous apex
Http httpProtocol = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm()+
'/services/data/v47.0/tooling/query/?q='+
'SELECT+RefMetadataComponentName'+
'+FROM+MetadataComponentDependency'+
'+WHERE+RefMetadataComponentType=\'ApexClass\'');
//setting method and header
req.setMethod('GET');
req.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
HttpResponse resp = httpProtocol.send(req);
ApexDependencyWrapper classesWithDependency = (ApexDependencyWrapper) System.JSON.deserialize(resp.getBody(), ApexDependencyWrapper.Class);
//only want custom classes, not from managed packages
List<ApexClass> allClasses = [SELECT Name FROM ApexClass WHERE NamespacePrefix = null];
//get all class names that are referenced
List<String> classesReferenced = new List<String>();
for(ApexDependencyWrapper.DependencyRecords apexDepRec : classesWithDependency.records){
//ignore test classes that rely on apex classes
if(apexDepRec.MetadataComponentName != null && !apexDepRec.MetadataComponentName.containsIgnoreCase('test')){
classesReferenced.add(apexDepRec.RefMetadataComponentName);
}
}
//find which classes are not referenced in org
List<String> classesNotReferenced = new List<String>();
//not including test classes
for(ApexClass apexName : allClasses){
if(!classesReferenced.contains(apexName.Name) && !apexName.Name.containsIgnoreCase('test')){
classesNotReferenced.add(apexName.Name);
}
}
System.debug('Classes with no references ' + classesNotReferenced);
I added the 'test' filter because we use Test in the names of our apex tests and that was basically all the results (since no one references them).
Edit: made a quick github repo that makes them both classes and easier to use if you want to fork/check it out.
... WHERE Id NOT IN (SELECT...
?