索引巢狀文件
Solr 支援索引巢狀文件,此處會說明,以及如何非常有效率地搜尋和擷取它們的方法。
舉例來說:Solr 中的巢狀文件可用於將部落格文章(父文件)與評論(子文件)連結在一起,或作為將主要產品線建模為父文件的一種方式,其中多種子文件代表個別 SKU(具有獨特的大小/顏色)和支援文件(直接巢狀於產品下方,或在個別 SKU 下方)。
具有所有子項的「最頂層」父項稱為「根」文件(或以前的「區塊文件」),並且它解釋了相關功能的一些命名。
在查詢時,區塊聯結查詢剖析器可以搜尋這些關聯性,而 [child
] 文件轉換器可以將子項(或其他「後代」)文件附加到結果文件。在效能方面,索引文件之間的關聯性通常會產生比對等的 「查詢時聯結」 快得多的查詢,因為關聯性已儲存在索引中,不需要計算。
但是,巢狀文件不如查詢時聯結彈性,因為它會強加某些應用程式可能無法接受的規則。巢狀文件可以透過 XML 或 JSON 資料語法進行索引,並且 SolrJ 搭配 javabin 也支援。
重新索引注意事項
除了就地更新之外,如果巢狀文件樹有更新,Solr 必須在內部重新索引整個巢狀文件樹。對於某些應用程式來說,這可能會導致大量的額外索引開銷,與其他建模方法相比,這可能不值得在查詢時獲得效能提升。 |
在此頁面的範例中,總是會提供子文件的 ID。但是,您不必產生這類 ID;您可以讓 Solr 自動填入它們。它會將其父項的 ID 與分隔符號和應該是唯一的路徑資訊串連起來。親自試試看!
範例索引語法:虛擬欄位
此範例顯示索引兩個根「產品」文件的樣子,每個文件都包含在「虛擬欄位」中指定的兩種不同類型的子文件:「skus」和「manuals」。其中兩個「sku」類型的文件有自己的巢狀子「manuals」文件…
即使在這些範例中,子文件在語法上被視為欄位值,這也僅僅是語法的問題,因此 |
-
JSON
-
XML
-
SolrJ
[{ "id": "P11!prod",
"name_s": "Swingline Stapler",
"description_t": "The Cadillac of office staplers ...",
"skus": [ { "id": "P11!S21",
"color_s": "RED",
"price_i": 42,
"manuals": [ { "id": "P11!D41",
"name_s": "Red Swingline Brochure",
"pages_i":1,
"content_t": "..."
} ]
},
{ "id": "P11!S31",
"color_s": "BLACK",
"price_i": 3
} ],
"manuals": [ { "id": "P11!D51",
"name_s": "Quick Reference Guide",
"pages_i":1,
"content_t": "How to use your stapler ..."
},
{ "id": "P11!D61",
"name_s": "Warranty Details",
"pages_i":42,
"content_t": "... lifetime guarantee ..."
} ]
},
{ "id": "P22!prod",
"name_s": "Mont Blanc Fountain Pen",
"description_t": "A Premium Writing Instrument ...",
"skus": [ { "id": "P22!S22",
"color_s": "RED",
"price_i": 89,
"manuals": [ { "id": "P22!D42",
"name_s": "Red Mont Blanc Brochure",
"pages_i":1,
"content_t": "..."
} ]
},
{ "id": "P22!S32",
"color_s": "BLACK",
"price_i": 67
} ],
"manuals": [ { "id": "P22!D52",
"name_s": "How To Use A Pen",
"pages_i":42,
"content_t": "Start by removing the cap ..."
} ]
} ]
|
<add>
<doc>
<field name="id">P11!prod</field>
<field name="name_s">Swingline Stapler</field>
<field name="description_t">The Cadillac of office staplers ...</field>
<field name="skus">
<doc>
<field name="id">P11!S21</field>
<field name="color_s">RED</field>
<field name="price_i">42</field>
<field name="manuals">
<doc>
<field name="id">P11!D41</field>
<field name="name_s">Red Swingline Brochure</field>
<field name="pages_i">1</field>
<field name="content_t">...</field>
</doc>
</field>
</doc>
<doc>
<field name="id">P11!S31</field>
<field name="color_s">BLACK</field>
<field name="price_i">3</field>
</doc>
</field>
<field name="manuals">
<doc>
<field name="id">P11!D51</field>
<field name="name_s">Quick Reference Guide</field>
<field name="pages_i">1</field>
<field name="content_t">How to use your stapler ...</field>
</doc>
<doc>
<field name="id">P11!D61</field>
<field name="name_s">Warranty Details</field>
<field name="pages_i">42</field>
<field name="content_t">... lifetime guarantee ...</field>
</doc>
</field>
</doc>
<doc>
<field name="id">P22!prod</field>
<field name="name_s">Mont Blanc Fountain Pen</field>
<field name="description_t">A Premium Writing Instrument ...</field>
<field name="skus">
<doc>
<field name="id">P22!S22</field>
<field name="color_s">RED</field>
<field name="price_i">89</field>
<field name="manuals">
<doc>
<field name="id">P22!D42</field>
<field name="name_s">Red Mont Blanc Brochure</field>
<field name="pages_i">1</field>
<field name="content_t">...</field>
</doc>
</field>
</doc>
<doc>
<field name="id">P22!S32</field>
<field name="color_s">BLACK</field>
<field name="price_i">67</field>
</doc>
</field>
<field name="manuals">
<doc>
<field name="id">P22!D52</field>
<field name="name_s">How To Use A Pen</field>
<field name="pages_i">42</field>
<field name="content_t">Start by removing the cap ...</field>
</doc>
</field>
</doc>
</add>
try (SolrClient client = getSolrClient()) {
final SolrInputDocument p1 = new SolrInputDocument();
p1.setField("id", "P11!prod");
p1.setField("name_s", "Swingline Stapler");
p1.setField("description_t", "The Cadillac of office staplers ...");
{
final SolrInputDocument s1 = new SolrInputDocument();
s1.setField("id", "P11!S21");
s1.setField("color_s", "RED");
s1.setField("price_i", 42);
{
final SolrInputDocument m1 = new SolrInputDocument();
m1.setField("id", "P11!D41");
m1.setField("name_s", "Red Swingline Brochure");
m1.setField("pages_i", 1);
m1.setField("content_t", "...");
s1.setField("manuals", m1);
}
final SolrInputDocument s2 = new SolrInputDocument();
s2.setField("id", "P11!S31");
s2.setField("color_s", "BLACK");
s2.setField("price_i", 3);
p1.setField("skus", Arrays.asList(s1, s2));
}
{
final SolrInputDocument m1 = new SolrInputDocument();
m1.setField("id", "P11!D51");
m1.setField("name_s", "Quick Reference Guide");
m1.setField("pages_i", 1);
m1.setField("content_t", "How to use your stapler ...");
final SolrInputDocument m2 = new SolrInputDocument();
m2.setField("id", "P11!D61");
m2.setField("name_s", "Warranty Details");
m2.setField("pages_i", 42);
m2.setField("content_t", "... lifetime guarantee ...");
p1.setField("manuals", Arrays.asList(m1, m2));
}
final SolrInputDocument p2 = new SolrInputDocument();
p2.setField("id", "P22!prod");
p2.setField("name_s", "Mont Blanc Fountain Pen");
p2.setField("description_t", "A Premium Writing Instrument ...");
{
final SolrInputDocument s1 = new SolrInputDocument();
s1.setField("id", "P22!S22");
s1.setField("color_s", "RED");
s1.setField("price_i", 89);
{
final SolrInputDocument m1 = new SolrInputDocument();
m1.setField("id", "P22!D42");
m1.setField("name_s", "Red Mont Blanc Brochure");
m1.setField("pages_i", 1);
m1.setField("content_t", "...");
s1.setField("manuals", m1);
}
final SolrInputDocument s2 = new SolrInputDocument();
s2.setField("id", "P22!S32");
s2.setField("color_s", "BLACK");
s2.setField("price_i", 67);
p2.setField("skus", Arrays.asList(s1, s2));
}
{
final SolrInputDocument m1 = new SolrInputDocument();
m1.setField("id", "P22!D52");
m1.setField("name_s", "How To Use A Pen");
m1.setField("pages_i", 42);
m1.setField("content_t", "Start by removing the cap ...");
p2.setField("manuals", m1);
}
client.add(Arrays.asList(p1, p2));
結構描述設定
索引巢狀文件需要一個名為 _root_
的索引欄位
<field name="_root_" type="string" indexed="true" stored="false" docValues="false" />
不要將此欄位新增至已經有資料的索引! 您必須重新建立索引。
-
Solr 會自動在所有文件中使用其根文件(也就是其最高層祖先,可能就是它自己)的
id
值來填入此欄位。 -
此欄位必須被索引 (
indexed="true"
),但不需要被儲存 (stored="true"
) 或使用 doc values (docValues="true"
),如果您覺得這樣有用,可以自由使用。 如果您想使用uniqueBlock(_root_)
欄位類型限制,則應啟用 docValues。
最好您也定義 _nest_path_
,這會增加功能並易於使用
<fieldType name="_nest_path_" class="solr.NestPathField" />
<field name="_nest_path_" type="_nest_path_" />`
-
Solr 會自動為任何子文件填入此欄位,但不會為根文件填入。
-
當使用
[child
] doc transformer 時,此欄位可讓 Solr 正確記錄並重建文件的命名和巢狀關係。-
如果此欄位不存在,
[child]
transformer 將返回所有後代子文件作為一個扁平的列表,就像它們已被索引為匿名子文件一樣。
-
-
如果您不使用
_nest_path_
,強烈建議每個文件都有一些欄位可以區分根文件和其巢狀子文件,並區分不同「類型」的子文件。 這並非絕對必要,只要可以編寫一個「篩選」查詢,用於隔離並選擇僅用於 Block Join Query Parser 和[child
] doc transformer 的父文件即可。 -
雖然目前只記錄如何在
[child]
的childFilter
參數的上下文中使用此欄位進行查詢,但還是可以在此欄位上查詢。
您可以選擇性地定義 _nest_parent_
來儲存父 ID
<field name="_nest_parent_" type="string" indexed="true" stored="true" />
-
Solr 會自動在子文件中填入此欄位,但不會在根文件中填入。
最後,請理解巢狀子文件本身就是文件,即使某些巢狀文件與父文件或其他子文件持有不同的資訊,因此
-
綱要中的所有欄位名稱只能以一種方式配置,不同類型的子文件不能以不同的方式配置相同的欄位名稱。
-
對於所有類型的文件並非都必需的任何欄位名稱,可能都無法使用
required
。 -
即使是子文件也需要一個全域唯一的
id
。
當使用 SolrCloud 時,非常建議使用基於字首的 compositeIds,其中巢狀文件樹中的所有文件都有一個共同字首。 這使得對個別子文件應用原子更新更加容易。 |
透過更新和刪除維護完整性
可以使用原子更新修改巢狀文件樹,以操作巢狀樹中的任何文件,甚至可以新增新的子文件。 這方面與更新任何普通文件沒有區別,Solr 內部會刪除舊的巢狀文件樹並新增新修改的文件樹。 只要注意如果部分更新是對子文件進行,請新增 root
欄位,以便 Solr 知道它與哪個根文件相關。
Solr 要求集合中所有文件的 id
都是唯一的。Solr 會在 shard 內強制執行根文件的唯一性,但不會為了避免檢查的開銷而對子文件執行。 客戶端應非常小心,絕對不要違反此規定。
要刪除整個巢狀文件樹,您只需使用根文件的 id
進行刪除(透過 ID 刪除)。 刪除(透過 ID 刪除)不適用於子文件的 id
,因為只會考慮根文件 ID。 相反地,使用刪除(透過查詢)(最有效率)或原子更新從其父項中移除子文件。
如果您使用 Solr 的刪除(透過查詢)API,您必須小心確保任何刪除查詢的結構,以確保任何正在刪除的文件都不會留下任何後代子文件。 否則會違反 Solr 預期的完整性假設。
索引匿名子文件
雖然不建議,但也可以「匿名地」索引子文件
-
JSON
-
XML
-
SolrJ
[{ "id": "P11!prod",
"name_s": "Swingline Stapler",
"type_s": "PRODUCT",
"description_t": "The Cadillac of office staplers ...",
"_childDocuments_": [
{ "id": "P11!S21",
"type_s": "SKU",
"color_s": "RED",
"price_i": 42,
"_childDocuments_": [
{ "id": "P11!D41",
"type_s": "MANUAL",
"name_s": "Red Swingline Brochure",
"pages_i":1,
"content_t": "..."
} ]
},
{ "id": "P11!S31",
"type_s": "SKU",
"color_s": "BLACK",
"price_i": 3
},
{ "id": "P11!D51",
"type_s": "MANUAL",
"name_s": "Quick Reference Guide",
"pages_i":1,
"content_t": "How to use your stapler ..."
},
{ "id": "P11!D61",
"type_s": "MANUAL",
"name_s": "Warranty Details",
"pages_i":42,
"content_t": "... lifetime guarantee ..."
}
]
} ]
<add>
<doc>
<field name="id">P11!prod</field>
<field name="type_s">PRODUCT</field>
<field name="name_s">Swingline Stapler</field>
<field name="description_t">The Cadillac of office staplers ...</field>
<doc>
<field name="id">P11!S21</field>
<field name="type_s">SKU</field>
<field name="color_s">RED</field>
<field name="price_i">42</field>
<doc>
<field name="id">P11!D41</field>
<field name="type_s">MANUAL</field>
<field name="name_s">Red Swingline Brochure</field>
<field name="pages_i">1</field>
<field name="content_t">...</field>
</doc>
</doc>
<doc>
<field name="id">P11!S31</field>
<field name="type_s">SKU</field>
<field name="color_s">BLACK</field>
<field name="price_i">3</field>
</doc>
<doc>
<field name="id">P11!D51</field>
<field name="type_s">MANUAL</field>
<field name="name_s">Quick Reference Guide</field>
<field name="pages_i">1</field>
<field name="content_t">How to use your stapler ...</field>
</doc>
<doc>
<field name="id">P11!D61</field>
<field name="type_s">MANUAL</field>
<field name="name_s">Warranty Details</field>
<field name="pages_i">42</field>
<field name="content_t">... lifetime guarantee ...</field>
</doc>
</doc>
</add>
try (SolrClient client = getSolrClient()) {
final SolrInputDocument p1 = new SolrInputDocument();
p1.setField("id", "P11!prod");
p1.setField("type_s", "PRODUCT");
p1.setField("name_s", "Swingline Stapler");
p1.setField("description_t", "The Cadillac of office staplers ...");
{
final SolrInputDocument s1 = new SolrInputDocument();
s1.setField("id", "P11!S21");
s1.setField("type_s", "SKU");
s1.setField("color_s", "RED");
s1.setField("price_i", 42);
{
final SolrInputDocument m1 = new SolrInputDocument();
m1.setField("id", "P11!D41");
m1.setField("type_s", "MANUAL");
m1.setField("name_s", "Red Swingline Brochure");
m1.setField("pages_i", 1);
m1.setField("content_t", "...");
s1.addChildDocument(m1);
}
final SolrInputDocument s2 = new SolrInputDocument();
s2.setField("id", "P11!S31");
s2.setField("type_s", "SKU");
s2.setField("color_s", "BLACK");
s2.setField("price_i", 3);
final SolrInputDocument m1 = new SolrInputDocument();
m1.setField("id", "P11!D51");
m1.setField("type_s", "MANUAL");
m1.setField("name_s", "Quick Reference Guide");
m1.setField("pages_i", 1);
m1.setField("content_t", "How to use your stapler ...");
final SolrInputDocument m2 = new SolrInputDocument();
m2.setField("id", "P11!D61");
m2.setField("type_s", "MANUAL");
m2.setField("name_s", "Warranty Details");
m2.setField("pages_i", 42);
m2.setField("content_t", "... lifetime guarantee ...");
p1.addChildDocuments(Arrays.asList(s1, s2, m1, m2));
}
client.add(p1);
這種簡化方法在舊版本的 Solr 中很常見,仍然可以與「僅限根」綱要一起使用,這些綱要除了 _root_
之外不包含任何其他巢狀相關欄位。 許多現有的綱要都是這樣,僅僅是因為預設 configset 是這樣,即使應用程式沒有使用巢狀文件。
當綱要包含 _nest_path_
欄位時,不應該使用此方法,因為該欄位的存在會在各種查詢時間功能(例如 [child
])中觸發假設並改變行為,而當巢狀文件沒有任何內在的「巢狀路徑」資訊時,這些功能將無法運作。
使用「僅限根」綱要索引匿名巢狀子文件的結果,類似於嘗試使用「僅限根」綱要索引「偽欄位」巢狀文件時的情況。 值得注意的是:由於 [child
] transformer 沒有巢狀路徑資訊可使用來重建巢狀文件的結構,因此它會返回所有符合的子文件作為一個扁平的列表,其結構與最初索引時的結構類似
-
JSON
-
XML
$ curl --globoff 'https://127.0.0.1:8983/solr/gettingstarted/select?omitHeader=true&q=id:P11!prod&fl=*,[child%20parentFilter=%22type_s:PRODUCT%22]'
{
"response":{"numFound":1,"start":0,"maxScore":0.7002023,"numFoundExact":true,"docs":[
{
"id":"P11!prod",
"name_s":"Swingline Stapler",
"type_s":"PRODUCT",
"description_t":"The Cadillac of office staplers ...",
"_version_":1673055562829398016,
"_childDocuments_":[
{
"id":"P11!D41",
"type_s":"MANUAL",
"name_s":"Red Swingline Brochure",
"pages_i":1,
"content_t":"...",
"_version_":1673055562829398016},
{
"id":"P11!S21",
"type_s":"SKU",
"color_s":"RED",
"price_i":42,
"_version_":1673055562829398016},
{
"id":"P11!S31",
"type_s":"SKU",
"color_s":"BLACK",
"price_i":3,
"_version_":1673055562829398016},
{
"id":"P11!D51",
"type_s":"MANUAL",
"name_s":"Quick Reference Guide",
"pages_i":1,
"content_t":"How to use your stapler ...",
"_version_":1673055562829398016},
{
"id":"P11!D61",
"type_s":"MANUAL",
"name_s":"Warranty Details",
"pages_i":42,
"content_t":"... lifetime guarantee ...",
"_version_":1673055562829398016}]}]
}}
$ curl --globoff 'https://127.0.0.1:8983/solr/gettingstarted/select?omitHeader=true&q=id:P11!prod&fl=*,[child%20parentFilter=%22type_s:PRODUCT%22]&wt=xml'
<?xml version="1.0" encoding="UTF-8"?>
<response>
<result name="response" numFound="1" start="0" maxScore="0.7002023" numFoundExact="true">
<doc>
<str name="id">P11!prod</str>
<str name="name_s">Swingline Stapler</str>
<str name="type_s">PRODUCT</str>
<str name="description_t">The Cadillac of office staplers ...</str>
<long name="_version_">1673055562829398016</long>
<doc>
<str name="id">P11!D41</str>
<str name="type_s">MANUAL</str>
<str name="name_s">Red Swingline Brochure</str>
<int name="pages_i">1</int>
<str name="content_t">...</str>
<long name="_version_">1673055562829398016</long></doc>
<doc>
<str name="id">P11!S21</str>
<str name="type_s">SKU</str>
<str name="color_s">RED</str>
<int name="price_i">42</int>
<long name="_version_">1673055562829398016</long></doc>
<doc>
<str name="id">P11!S31</str>
<str name="type_s">SKU</str>
<str name="color_s">BLACK</str>
<int name="price_i">3</int>
<long name="_version_">1673055562829398016</long></doc>
<doc>
<str name="id">P11!D51</str>
<str name="type_s">MANUAL</str>
<str name="name_s">Quick Reference Guide</str>
<int name="pages_i">1</int>
<str name="content_t">How to use your stapler ...</str>
<long name="_version_">1673055562829398016</long></doc>
<doc>
<str name="id">P11!D61</str>
<str name="type_s">MANUAL</str>
<str name="name_s">Warranty Details</str>
<int name="pages_i">42</int>
<str name="content_t">... lifetime guarantee ...</str>
<long name="_version_">1673055562829398016</long></doc></doc>
</result>
</response>