How to create an index on an XML column in PostgreSQL with an xpath expression?

How to create an index on an XML column in PostgreSQL with an xpath expression?



I'm running into this error when attempting to create a btree index on an XML data type column that uses an xpath expression on AuroraDB - PostgreSQL 9.6:


ERROR: could not identify a comparison function for type xml
SQL state: 42883



This 2009 thread without a clear resolution is the only one I've found discussing this error message in regards to creating an xpath based index for a much earlier version of PostgreSQL:
https://www.postgresql-archive.org/Slow-select-times-on-select-with-xpath-td2074839.html



In my case I do also need to specify namespaces as well and the original poster in that thread cast the result of the xpath expression to text which does get by the error for me too - but why is that even needed? I also don't see PostgreSQL ever using my index even when I have thousands of rows to go through.



So I tried out a simpler case and the error still occurs - please shed some light as to why if you could:


CREATE TABLE test
(
id integer NOT NULL,
xml_data xml NOT NULL,
CONSTRAINT test_pkey PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;



CREATE INDEX test_idx
ON test USING btree
(xpath('/book/title', xml_data))



and the resulting message is:


ERROR: could not identify a comparison function for type xml
SQL state: 42883



The database encoding is UTF8.
The Collation and Character Type are en_US.UTF-8.



Some sample insert statements too:


insert into source_data.test(id, xml_data)
values(1, XMLPARSE (DOCUMENT '<?xml version="1.0"?><book><title>Manual</title><chapter>1</chapter><chapter>2</chapter></book>'))

insert into source_data.test(id, xml_data)
values(2, XMLPARSE (DOCUMENT '<?xml version="1.0"?><book><title>Apropos</title><chapter>1</chapter><chapter>2</chapter></book>'))




1 Answer
1



Your getting this error because the XML data type does not provide any comparison operators, hence you can't create an index on the result of xpath(), because it returns an array of XML values.


xpath()



Therefore you need to cast the XPath expression to a text array when creating the index:


CREATE INDEX test_idx
ON test USING BTREE
(cast(xpath('/book/title', xml_data) as text)) ;



This index then gets used when querying the table:


EXPLAIN ANALYZE
SELECT * FROM test where
cast(xpath('/book/title', xml_data) as text) = '<title>Apropos</title>';



gives


QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
Index Scan using test_idx on test (cost=0.13..8.15 rows=1 width=36) (actual time=0.034..0.038 rows=1 loops=1)
Index Cond: ((xpath('/book/title'::text, xml_data, ''::text))::text = '<title>Apropos</title>'::text)
Planning time: 0.168 ms
Execution time: 0.073 ms (4 rows)



This works the same when using text():


text()


CREATE INDEX test_idx
ON test USING BTREE
(cast(xpath('/book/title/text()', xml_data) as text)) ;

explain analyze select * from test where
cast(xpath('/book/title/text()', xml_data) as text) = 'Apropos';



gives


QUERY PLAN
----------------------------------------------------------------------------------------------------------------
Index Scan using test_idx on test (cost=0.13..8.15 rows=1 width=36) (actual time=0.034..0.038 rows=1 loops=1)
Index Cond: ((xpath('/book/title/text()'::text, xml_data, ''::text))::text = 'Apropos'::text)
Planning time: 0.166 ms
Execution time: 0.076 ms
(4 rows)



Note that I forced the use of the index via the following command, as I only had 4 rows in the test table I created.


SET enable_seqscan TO off;





It would be nice as well to be able to create an index on text() like say (cast(xpath('/book/title/text()', xml_data) as text)); but that'll result in ERROR: functions in index expression must be marked IMMUTABLE Does that mean text() is considered mutable and there's no way around that?
– Khorkrak
Sep 10 '18 at 16:24






@Khorkrak I added an example using text().
– Markus
Sep 10 '18 at 19:00


text()



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Some of your past answers have not been well-received, and you're in danger of being blocked from answering.



Please pay close attention to the following guidance:



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)