JCR-SQL2 (Java Content Repository – Structured Query Language 2) is domain specific language used to query JCR nodes held in the JCR repository. The syntax for JCR-SQL2 is fairly similar to SQL, so if you have some experience with SQL, this tutorial will be a walk in the park for you.
The best way to learn the power of the JCR-SQL2 language is to try it out on CRX/DE Lite. Without future or do, below will be a lesson on JCR-SQL2. To locate CRX/DE Lite’s query tool, visit http://localhost:4502/crx/de/index.jsp, then select on the tools drop down, and choose query.
2023 Added Section: SQL-JCR2 Snippets
Examples can be found at the bottom of this tutorial, JCR-SQL2 Code Snippets Cheat Sheet, where you can find some examples for real life use cases. Head into your crx-de > tools > query > type:JCR SQL2, and try it our yourself.
JCR-SQL2 Syntax
- SELECT Statement
- NAME() Statement
- ISDESCENDANTNODE Statement
- CONTAINS Statement
- LIKE Operator
- IS NOT NULL Property
- ORDER BY Keyword
- CAST() Statement
- JCR-SQL2 Code Snippets Cheat Sheet (new 2023)
- JCR SQL2 Java Code Example Servlet (new 2023)
1. The JCR-SQL2 SELECT Statement
- The SELECT statement is used to select all JCR nodes that matches the JCR node’s primary type.
- Returns in CRX/DE Lite: A list of JCR nodes in the results table with their corresponding paths.
- Returns in OSGI Bundle (Sling API): All the found nodes stored in the Iterator<Resource> object.
- Returns in OSGI Bundle (JCR API): The QueryResult object where you can call getNodes() method to get the NodeIterator object.
1 2 3 4 5 6 7 8 9 | -- nt:base is a primary type that represents every single node in the JCR. -- returns all nodes in the JCR. SELECT * FROM [nt:base] -- returns all [cq:Page] nodes SELECT * FROM [cq:Page] -- returns all [dam:Asset] nodes SELECT * FROM [dam:Asset] |
2. The JCR-SQL2 NAME() Statement
- Selecting nodes with a specific node name.
- Returns in CRX/DE Lite: A list of JCR nodes in the results table with their corresponding paths.
- Returns in OSGI Bundle (Sling API): All the found nodes stored in the Iterator<Resource> object.
- Returns in OSGI Bundle (JCR API): The QueryResult object where you can call getNodes() method to get the NodeIterator object.
1 2 3 4 5 | -- returns all [cq:Page] nodes WHERE the node's name is equal to "we-retail" SELECT * FROM [cq:Page] AS nodes WHERE NAME(nodes) = "we-retail" -- returns all [dam:Asset] nodes WHERE the node's name is equal to "we-retail" SELECT * FROM [dam:Asset] AS nodes WHERE NAME(nodes) = "we-retail" |
3. The JCR-SQL2 ISDESCENDANTNODE Statement
- Selecting nodes under a file path.
- Returns in CRX/DE Lite: A list of JCR nodes in the results table with their corresponding paths.
- Returns in OSGI Bundle (Sling API): All the found nodes stored in the Iterator<Resource> object.
- Returns in OSGI Bundle (JCR API): The QueryResult object where you can call getNodes() method to get the NodeIterator object.
1 2 3 4 5 6 7 8 9 10 11 | -- returns all nodes WHERE nodes exist under "/content/we-retail" select * FROM [nt:base] WHERE ISDESCENDANTNODE ([/content/we-retail]) -- returns all [cq:Page] nodes WHERE nodes exist under "/content/we-retail" SELECT * FROM [cq:Page] WHERE ISDESCENDANTNODE ([/content/we-retail]) -- returns all [dam:Asset] nodes WHERE nodes exist under "/content/dam/we-retail" SELECT * FROM [dam:Asset] WHERE ISDESCENDANTNODE ([/content/dam/we-retail]) -- returns all [nt:unstructured] nodes WHERE nodes exist under "/content/we-retail" SELECT * FROM [nt:unstructured] WHERE ISDESCENDANTNODE([/content/we-retail]) |
4. The JCR-SQL2 CONTAINS Statement
- Selecting nodes where the properties contain a value.
- Returns in CRX/DE Lite: A list of JCR nodes in the results table with their corresponding paths.
- Returns in OSGI Bundle (Sling API): All the found nodes stored in the Iterator<Resource> object.
- Returns in OSGI Bundle (JCR API): The QueryResult object where you can call getNodes() method to get the NodeIterator object.
1 2 3 4 5 | -- returns all nodes WHERE node's property jcr:title CONTAINS "we-retail" SELECT * FROM [nt:base] AS nodes WHERE CONTAINS(nodes.title, "we-retail") -- returns all [cq:Page] nodes where node's property jcr:title CONTAINS "we-retail" SELECT * FROM [cq:Page] AS nodes WHERE CONTAINS(nodes.title, "we-retail") |
5. The JCR-SQL2 LIKE Operator
- The LIKE operator is used to search for a specified pattern in node properties.
- There are two wildcards used in conjunction with the LIKE operator: % and _
- Returns in CRX/DE Lite: A list of JCR nodes in the results table with their corresponding paths.
- Returns in OSGI Bundle (Sling API): All the found nodes stored in the Iterator<Resource> object.
- Returns in OSGI Bundle (JCR API): The QueryResult object where you can call getNodes() method to get the NodeIterator object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | -- returns all [cq:PageContent] nodes WHERE nodes exist under "/content" -- AND node's property jcr:title starts with any values and ends with "w" SELECT * FROM [cq:PageContent] AS nodes WHERE ISDESCENDANTNODE ([/content]) AND nodes.[jcr:title] LIKE "%w" -- returns all [cq:PageContent] nodes WHERE nodes exist under "/content" -- AND node's property jcr:title starts with "w" and ends with any values SELECT * FROM [cq:PageContent] AS nodes WHERE ISDESCENDANTNODE ([/content]) AND nodes.[jcr:title] LIKE "w%" -- returns all [cq:PageContent] nodes WHERE nodes exist under "/contentl" -- AND node's property jcr:title matching "w" in any position SELECT * FROM [cq:PageContent] AS nodes WHERE ISDESCENDANTNODE ([/content]) AND nodes.[jcr:title] LIKE "%w%" -- returns all [cq:PageContent] nodes WHERE nodes exist under "/content" -- AND node's property jcr:title start with any values and have "w" in the second position and ends with any values SELECT * FROM [cq:PageContent] AS nodes WHERE ISDESCENDANTNODE ([/content]) AND nodes.[jcr:title] LIKE "_w%" |
6. The JCR-SQL2 IS NOT NULL Property
- Used for validation of property’s value is not null.
- Returns in CRX/DE Lite: A list of JCR nodes in the results table with their corresponding paths.
- Returns in OSGI Bundle (Sling API): All the found nodes stored in the Iterator<Resource> object.
- Returns in OSGI Bundle (JCR API): The QueryResult object where you can call getNodes() method to get the NodeIterator object.
1 2 3 4 5 6 7 8 9 | -- returns all nodes WHERE nodes exist under "/content" -- AND node's property jcr:title IS NOT NULL SELECT * FROM [nt:base] AS nodes WHERE ISDESCENDANTNODE ([/content]) AND nodes.[jcr:title] IS NOT NULL -- returns all [cq:PageContent] nodes WHERE nodes exist under "/content" -- AND node's property jcr:title IS NOT NULL SELECT * FROM [cq:PageContent] AS nodes WHERE ISDESCENDANTNODE ([/content]) AND nodes.[jcr:title] IS NOT NULL |
7. The JCR-SQL2 ORDER BY Keyword
- The ORDER BY keyword is used to sort the result-set in ascending or descending order.
- Returned values are sorted by default in ascending order.
- Returns in CRX/DE Lite: A list of JCR nodes in the results table with their corresponding paths.
- Returns in OSGI Bundle (Sling API): All the found nodes stored in the Iterator<Resource> object.
- Returns in OSGI Bundle (JCR API): The QueryResult object where you can call getNodes() method to get the NodeIterator object.
1 2 3 4 5 6 7 8 9 | -- returns all nodes WHERE nodes exist under the "/content/we-retail" -- ORDER BY jcr:created ascending SELECT * FROM [nt:base] AS nodes WHERE ISDESCENDANTNODE ([/content/we-retail]) ORDER BY nodes.[jcr:created] -- returns all nodes WHERE nodes exist under the "/content/we-retail" -- ORDER BY node's property jcr:created descending SELECT * FROM [nt:base] AS nodes WHERE ISDESCENDANTNODE ([/content/we-retail]) ORDER BY nodes.[jcr:created] DESC |
8. The JCR-SQL2 CAST() Statement
- Convert an expression from one data type to another.
- Cast Types:
- STRING
- BINARY
- DATE
- LONG
- DOUBLE
- DECIMAL
- BOOLEAN
- NAME
- PATH
- REFERENCE
- WEAKREFERENCE
- URI
- Returns in CRX/DE Lite: A list of JCR nodes in the results table with their corresponding paths.
- Returns in OSGI Bundle (Sling API): All the found nodes stored in the Iterator<Resource> object.
- Returns in OSGI Bundle (JCR API): The QueryResult object where you can call getNodes() method to get the NodeIterator object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | -- returns all [cq:PageContent] nodes WHERE nodes exist under the "/content/we-retail" -- AND node's navRoot equals to "true" SELECT * FROM [cq:PageContent] as nodes WHERE ISDESCENDANTNODE ([/content/we-retail]) AND nodes.[navRoot] = CAST("true" AS BOOLEAN) -- returns all [cq:PageContent] nodes WHERE nodes exist under the "/content/we-retail" -- AND node's creation date is greater than "April 1st, 2018" SELECT * FROM [cq:PageContent] AS nodes WHERE ISDESCENDANTNODE ([/content/we-retail]) AND nodes.[jcr:created] > CAST("2018-04-01T00:00:00.000Z" AS DATE) -- returns all [cq:PageContent] nodes WHERE nodes exist under the "/content/we-retail" -- AND node's maxRating is less than "5.0" SELECT * FROM [cq:PageContent] as nodes WHERE ISDESCENDANTNODE ([/content/we-retail]) AND nodes.[maxRating] < CAST("5.0" AS DECIMAL) |
Final Notes: When is the perfect time to use JCR-SQL2?
- Build a images and documents search component, if your AEM site that allows consumers to search for images or documents.
- Build a blog post search component, If your AEM site has a blog section.
- Build a secure resources query API, If you are allowing 3rd party applications to query and find nodes in your AEM site.
There’s many other things to utilize JCR-SQL2 to query nodes, but those are just some quick examples.
9. JCR-SQL2 Code Snippets Cheat Sheet
Use these code snippets below to rapidly build your AEM JCR SQL2 search queries, I will try my best to give you some really great examples:
a. Find all [nt:unstructured] nodes, under the path “/content/we-retail”, where the node name = “root”
1 2 3 | SELECT * FROM [nt:unstructured] AS node WHERE ISDESCENDANTNODE(node, "/content/we-retail") AND NAME() = "root" |
b. Find all components ([nt:unstructured] nodes), under the path “/content/we-retail”, where component resourceType = “weretail/components/content/image”
1 2 3 | SELECT * FROM [nt:unstructured] AS node WHERE ISDESCENDANTNODE(node, "/content/we-retail") AND [sling:resourceType] = "weretail/components/content/image" |
b. Find all components ([nt:unstructured] nodes), under the path “/content/we-retail”, where component resourceType = “weretail/components/content/image” AND the fileReference property contains with “/content/dam/we-retail/en/experiences”
1 2 3 4 | SELECT * FROM [nt:unstructured] AS node WHERE ISDESCENDANTNODE(node, "/content/we-retail") AND [sling:resourceType] = "weretail/components/content/image" AND CONTAINS([fileReference], "/content/dam/we-retail/en/experiences") |
c. Find all [nt:unstructured] nodes, under the path “/content/we-retail”, where the property(“jcr:title”) contains “Featured products”
1 2 3 | SELECT * FROM [nt:unstructured] AS node WHERE ISDESCENDANTNODE(node, "/content/we-retail") AND CONTAINS([jcr:title], "Featured products") |
d. Find all pages (cq:Page nodes), under the path “/content/we-retail”, where the cq:lastModified date is before or equals to “January 1st, 2000”
1 2 3 4 | SELECT page.* FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS jcrContentNode ON ISCHILDNODE(jcrContentNode, page) WHERE ISDESCENDANTNODE(page, "/content/we-retail") AND jcrContentNode.[cq:lastModified] <= CAST("2023-01-01T00:00:00.000+00:00" AS DATE) |
e. Find all pages (cq:Page nodes), under the path “/content/we-retail”, where the jcr:created date is before to “July 1st, 2022”
1 2 3 4 | SELECT page.* FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS jcrContentNode ON ISCHILDNODE(jcrContentNode, page) WHERE ISDESCENDANTNODE(page, "/content/we-retail") AND jcrContentNode.[jcr:created] < CAST("2022-07-01T00:00:00.000+00:00" AS DATE) |
f. Find all [cq:PageContent] nodes, under the path “/content/we-retail”, where the cq:template equals to “/conf/we-retail/settings/wcm/templates/hero-page”
This AEM JCR SQ2 Query is an example for how to find all nodes with a specific template, results returned as cq:PageContent node.
1 2 3 | SELECT * FROM [cq:PageContent] AS node WHERE ISDESCENDANTNODE(node, "/content/we-retail") AND [cq:template] = "/conf/we-retail/settings/wcm/templates/hero-page" |
g. Find all pages (cq:Page nodes), under the path “/content/we-retail”, where the cq:template equals to “/conf/we-retail/settings/wcm/templates/hero-page”
This AEM JCR SQ2 Query is an example for how to find all nodes with a specific template, results returned as cq:Page node.
1 2 3 4 | SELECT page.* FROM [cq:Page] AS page INNER JOIN [cq:PageContent] AS jcrContentNode ON ISCHILDNODE(jcrContentNode, page) WHERE ISDESCENDANTNODE(page, "/content/we-retail") AND jcrContentNode.[cq:template] = "/conf/we-retail/settings/wcm/templates/hero-page" |
h. Find all [cq:PageContent] nodes, under the path “/content/we-retail”, where the cq:tags[] contains “we-retail:gender/men”
1 2 3 4 | This AEM JCR SQ2 Query is an example for how to find all nodes with a single cq:tags[], results returned as cq:PageContent node. SELECT * FROM [cq:PageContent] AS node WHERE ISDESCENDANTNODE(node, "/content/we-retail") AND [cq:tags] = "we-retail:gender/men" |
i. Find all [cq:PageContent] nodes, under the path “/content/we-retail”, where the cq:tags[] contains “we-retail:gender/men” AND cq:tags[] contains “we-retail:activity/surfing”
1 2 3 4 5 | This AEM JCR SQ2 Query is an example for how to find all nodes with multiple cq:tags[], results returned as cq:PageContent node. SELECT * FROM [cq:PageContent] AS node WHERE ISDESCENDANTNODE(node, "/content/we-retail") AND [cq:tags] = "we-retail:gender/men" AND [cq:tags] = "we-retail:activity/surfing" |
9. JCR-SQL2 Java Code Example Servlet (2023)
This is an example of how an AEM servlet would actually utilize the JCR-SQL2 Query with the examples above. It will output a JSON array of the page paths.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.query.Query; import javax.jcr.query.QueryManager; import javax.jcr.query.QueryResult; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.servlets.SlingSafeMethodsServlet; import org.osgi.service.component.annotations.Component; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @Component(service = javax.servlet.Servlet.class, property = { "sling.servlet.paths=/bin/getpagedbypath" }) public class GetPagedByPathServlet extends SlingSafeMethodsServlet { @Override protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException { try { // Get the JCR session Session session = request.getResourceResolver().adaptTo(Session.class); // Query statement String queryString = "SELECT page.* FROM [cq:Page] AS page " + "INNER JOIN [cq:PageContent] AS jcrContentNode ON ISCHILDNODE(jcrContentNode, page) " + "WHERE ISDESCENDANTNODE(page, '/content/we-retail') " + "AND jcrContentNode.[cq:lastModified] <= CAST('2023-01-01T00:00:00.000+00:00' AS DATE)"; // Create the query object QueryManager queryManager = session.getWorkspace().getQueryManager(); Query query = queryManager.createQuery(queryString, Query.JCR_SQL2); // Execute the query QueryResult result = query.execute(); // Get the nodes from the query result NodeIterator nodeIterator = result.getNodes(); // Collect the paths of the first ten pages in a list List<String> pagePaths = new ArrayList<>(); while (nodeIterator.hasNext()) { Node pageNode = nodeIterator.nextNode(); String pagePath = pageNode.getPath(); pagePaths.add(pagePath); } // Create a JSON object to hold the results JsonObject jsonResponse = new JsonObject(); JsonArray resultsArray = new JsonArray(); // Add the paths to the JSON array for (String path : pagePaths) { resultsArray.add(path); } // Add the JSON array to the response object jsonResponse.add("results", resultsArray); // Set the response content type response.setContentType("application/json"); // Write the JSON response to the output PrintWriter writer = response.getWriter(); writer.print(jsonResponse.toString()); writer.flush(); } catch (RepositoryException e) { // Handle exception response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.getWriter().println("Error executing query: " + e.getMessage()); } } } |
JSON response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | { "results":[ "/content/we-retail/page1", "/content/we-retail/page2", "/content/we-retail/page3", "/content/we-retail/page4", "/content/we-retail/page5", "/content/we-retail/page6", "/content/we-retail/page7", "/content/we-retail/page8", "/content/we-retail/page9", "/content/we-retail/page10" ] } |
Curl call
1 | curl -X GET http://localhost:4502/bin/getpagedbypath |
Thank you very much for the post, I like it!
Hi,
Can you please let me know how can i get the count of results? i need only the count like in mysql ‘SELECT COUNT(*)….’
Krishna, In CRX/DE, you can see the count in the execution console. – https://sourcedcode.com/storage/2019/05/jcr-count-example.png
awesome article! thanks for sharing!
Thank you. This is great!
Its not my first time to go to see this website, i am visiting this website daily and take good data from here every day.
Simple, precise. please post more blogs since AEM lacks quality blogging community!
The layout of your website makes it very easy to follow this tutorial. Thanks!
Keep posting!
Thank you!
Can’t wait for the next SQL
Good one, thanks!
I really needed this, great lesson. Thank you!
The youtube video makes it very easy to follow, thanks!
Thanks a lot for this!
Nice to see good content out there, thank you.
Thank you!
Thank you sir!
Thanks for this article, Brian. Keep it up!
This is a good article, thank you!
great jcr sql guide!
best sql guide ever!
thanks!
This article is very useful, I still use it all the time. Thanks for the new examples!