Reading directory data intelligently: the base, one, sub, and children search scopes, comparison and logical filter operators, ready-to-use combined filter examples, attribute selection, and practices for keeping search performance healthy.

In episode 8 you learned to modify and delete data. Episode 9 covers the ability you'll use most often day to day: reading data intelligently. You'll learn search scopes, how to build filters with comparison and logical operators, select the attributes you request, and understand the performance limits of searches in slapd.
The scope determines how far a search reaches from the base DN:
| Scope | -s value | Result |
|---|---|---|
| Base | -s base | Only the entry at the base DN itself |
| One | -s one | The base's direct children, without the base |
| Sub | -s sub | The base and all its descendants |
| Children | -s children | All descendants, without the base |
The main difference is between one and sub: one is only one level down, sub descends all the way to the leaves. A comparison example:
ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b dc=example,dc=com -s base "(objectClass=*)"
ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b dc=example,dc=com -s one "(objectClass=*)"
ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b dc=example,dc=com -s sub "(objectClass=*)"With base dc=example,dc=com, -s base returns only the domain entry itself, -s one returns the OUs below it, and -s sub returns everything, including all users and groups.
The filter is the heart of LDAP search. Every filter is written in parentheses, e.g. (uid=budi). Basic operators:
| Operator | Example | Meaning |
|---|---|---|
Equality = | (uid=budi) | Value exactly equal |
Substring * | (cn=John*) | Prefix, suffix, or middle part |
Greater than >= | (uidNumber>=1000) | Greater than or equal |
Less than <= | (uidNumber<=2000) | Less than or equal |
Presence =* | (mail=*) | Attribute has a value |
Approximate ~= | (sn~=santoso) | Similar, usually without an index |
Example usage in ldapsearch:
ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b "ou=People,dc=example,dc=com" "(cn=Budi*)"The filter (cn=Budi*) searches all entries whose cn starts with Budi. Wildcards can be used at the front, middle, or end: *@example.com, Bu*i, *Pratama.
For combinations, use three logical operators:
| Operator | Symbol | Semantics |
|---|---|---|
| AND | & | All conditions must hold |
| OR | ` | ` |
| NOT | ! | Negates a condition |
Combined filters wrap several filters in one pair of parentheses:
(&(objectClass=person)(mail=*@example.com))
(|(uid=budi)(uid=sari))
(!(mail=*))
(&(|(uid=budi)(uid=sari))(objectClass=posixAccount))The rule of thumb: AND joins requirements, OR joins alternatives, and negation is often used for "everything except". Filters can be nested as deep as you like, as long as the parentheses stay balanced.
Some combinations that are immediately useful in real directories:
(uid=john)
(cn=John*)
(&(objectClass=person)(mail=*@example.com))
(|(uid=john)(uid=jane))
(&(objectClass=posixAccount)(uidNumber>=1000))The last one — all Unix accounts with a UID of at least 1000 — is often used to separate real users from system accounts:
ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b "ou=People,dc=example,dc=com" "(&(objectClass=posixAccount)(uidNumber>=1000))"Remember: a filter only searches within the specified base. If you want to cover the whole tree, raise the base to dc=example,dc=com and use -s sub.
Besides filters, you can control the attributes returned by adding an attribute list after the filter:
ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b "ou=People,dc=example,dc=com" "(objectClass=posixAccount)" uid mailSelection rules:
* — all user attributes.+ — operational attributes like entryUUID, createTimestamp, and modifyTimestamp.userPassword only appears if the ACL permits reading it.ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b "uid=budi,ou=People,dc=example,dc=com" -s base "(objectClass=*)" +To see only DNs without attributes, use the attribute list 1.1 — a classic bandwidth-saving trick.
"Expensive" filters are those without an index on the server. In the indexing episode you'll create indexes for cn, uid, sn, mail, member, and uidNumber. Without an index, slapd does a full scan that slows down as the database grows.
Optimal patterns:
(&(objectClass=posixAccount)(uidNumber>=1000)) is far more efficient than (objectClass=posixAccount) alone for lists of thousands of entries.-z (entry count) and -l (seconds):ldapsearch -z 50 -l 10 -x -D "cn=admin,dc=example,dc=com" -W -b dc=example,dc=com "(objectClass=*)"The -z 50 option caps results at 50 entries; -l 10 caps the run at 10 seconds. The slapd server also enforces its own limits via olcLimits, so the effective value is whichever is stricter.
Tip
If a search suddenly gets slow, first check whether the filter uses an indexed attribute. Filters with ~= (approximate) and mid-value substrings can rarely use an index — set up the right olcDbIndex before your data grows.
Episode 9 equips you with the LDAP query language: the base, one, sub, and children scopes; the comparison operators =, *, >=, <=, =*, and ~=; the logical operators &, |, and !; attribute selection including operational attributes; and the performance discipline of indexes plus size and time limits.
Key takeaways:
sub reaches the whole tree.+ for operational attributes.In the next episode, episode 10, we move to access security: access control lists. You'll decide who can read what, who can change what, and how to enforce the principle of least privilege in the directory.