<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Search Nuggets &#187; solr5</title>
	<atom:link href="http://blog.comperiosearch.com/blog/tag/solr5/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.comperiosearch.com</link>
	<description>A blog about Search as THE solution</description>
	<lastBuildDate>Mon, 13 Jun 2016 08:59:45 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=3.9.40</generator>
	<item>
		<title>Solr: Indexing SQL databases made easier! &#8211; Part 2</title>
		<link>http://blog.comperiosearch.com/blog/2015/04/14/solr-indexing-index-sql-databases-made-easier-part-2/</link>
		<comments>http://blog.comperiosearch.com/blog/2015/04/14/solr-indexing-index-sql-databases-made-easier-part-2/#comments</comments>
		<pubDate>Tue, 14 Apr 2015 12:56:21 +0000</pubDate>
		<dc:creator><![CDATA[Seb Muller]]></dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Solr]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[solr5]]></category>

		<guid isPermaLink="false">http://blog.comperiosearch.com/?p=3477</guid>
		<description><![CDATA[Last summer I wrote a blog post about indexing a MySQL database into Apache Solr. I would like to now revisit the post to update it for use with Solr 5 and start diving into how to implement some basic search features such as Facets Spellcheck Phonetic search Query Completion Setting up our environment The [...]]]></description>
				<content:encoded><![CDATA[<p>Last summer I wrote a <a href="http://blog.comperiosearch.com/blog/2014/08/28/indexing-database-using-solr/">blog post</a> about indexing a MySQL database into <a href="http://lucene.apache.org/solr/">Apache Solr</a>. I would like to now revisit the post to update it for use with Solr 5 and start diving into how to implement some basic search features such as</p>
<ul>
<li>Facets</li>
<li>Spellcheck</li>
<li>Phonetic search</li>
<li>Query Completion</li>
</ul>
<h2>Setting up our environment</h2>
<p>The requirements remain the same as with the original blogpost:</p>
<ol>
<li>Java 1.7 or greater</li>
<li>A <a href="http://dev.mysql.com/downloads/mysql/">MySQL</a> database</li>
<li>A copy of the <a href="https://launchpad.net/test-db/employees-db-1/1.0.6/+download/employees_db-full-1.0.6.tar.bz2">sample employees database</a></li>
<li>The MySQL <a href="http://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.32.tar.gz">jdbc driver</a></li>
</ol>
<p>We&#8217;ll now be using Solr 5, which runs a little differently from previous incarnations of Solr. Download <a href="http://www.apache.org/dyn/closer.cgi/lucene/solr/5.0.0">Solr</a> and extract it to a directory of your choice.Open a terminal and navigate to your Solr directory.<br />
Start Solr with the command <pre class="crayon-plain-tag">bin/solr start</pre> .<img class="alignright wp-image-3497 size-medium" src="http://blog.comperiosearch.com/wp-content/uploads/2022/04/Screen-Shot-2015-04-11-at-20.30.03-300x114.png" alt="Solr Status" width="300" height="114" /></p>
<p>To confirm Solr successfully started up, run <pre class="crayon-plain-tag">bin/solr status</pre></p>
<p>Unlike previously, we now need to create a Solr core for our employee data. To do so run this command <pre class="crayon-plain-tag">bin/solr create_core -c employees -d basic_configs</pre> . This will create a core named employees using Solr&#8217;s minimal configuration options. Try <pre class="crayon-plain-tag">bin/solr create_core -help</pre>  to see what else is possible.</p>
<ol>
<li>Open server/solr/employees/conf/solrconfig.xml in a text editor and add the following within the config tags:
<div id="file-dataimporthandler2-LC1" class="line">
<pre class="crayon-plain-tag">&lt;lib dir="../../../dist/" regex="solr-dataimporthandler-\d.*\.jar" /&gt;
 
&lt;requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler"&gt;
&lt;lst name="defaults"&gt;
&lt;str name="config"&gt;db-data-config.xml&lt;/str&gt;
&lt;/lst&gt;
&lt;/requestHandler&gt;</pre>
</div>
</li>
<li>In the same directory, open schema.xml and add this this line:<br />
<pre class="crayon-plain-tag">&lt;dynamicField name="*_name" type="text_general" multiValued="false" indexed="true" stored="true" /&gt;</pre>
</li>
<li>Create a lib subdir in server/solr/employees and extract the MySQL jdbc driver jar into it.</li>
<li>Finally, restart the Solr server with the command <pre class="crayon-plain-tag">bin/solr restart</pre></li>
</ol>
<p>When started this way, Solr runs by default on port 8983. Use <pre class="crayon-plain-tag">bin/solr start -p portnumber</pre>  and replace portnumber with your preferred choice to start it on that one.</p>
<p>Navigate to <a href="http://localhost:8983/solr">http://localhost:8983/solr</a> and you should see the Solr admin GUI splash page. From here, use the Core Selector dropdown button to select our employee core and then click on the Dataimport option. Expanding the Configuration section should show an XML response with a stacktrace with a message along the lines of <pre class="crayon-plain-tag">Can't find resource 'db-data-config.xml' in classpath</pre> . This is normal as we haven&#8217;t actually created this file yet, which stores the configs for connecting to our target database.</p>
<p>We&#8217;ll come back to that file later but let&#8217;s make our demo database now. If you haven&#8217;t already downloaded the sample employees database and installed MySQL, now would be a good time!</p>
<h2>Setting up our database</h2>
<p>Please refer to the instructions in the same section in the <a href="http://blog.comperiosearch.com/blog/2014/08/28/indexing-database-using-solr/">original blog post</a>. The steps are still the same.</p>
<h2>Indexing our database</h2>
<p>Again, please refer to the instructions in the same section in the original blog post. The only difference is the Postman collection should be imported from <a href="https://www.getpostman.com/collections/f7634c89cd9851dd2c13"> this url</a> instead. The commands you can use alternatively have also changed and are now</p><pre class="crayon-plain-tag">Clear index: http://localhost:8983/solr/employees/update?stream.body=&lt;delete&gt;&lt;query&gt;*:*&lt;/query&gt;&lt;/delete&gt;&amp;commit=true
Retrieve all: http://localhost:8983/solr/employees/select?q=*:*&amp;omitHeader=true
Index db: http://localhost:8983/solr/employees/collection1/dataimport?command=full-import
Reload core: http://localhost:8983/solr/employees/admin/cores?action=RELOAD&amp;core=collection1
Georgi query: http://localhost:8983/solr/employees/select?q=georgi&amp;wt=json&amp;qf=first_name%20last_name&amp;defType=edismax
Facet query: http://localhost:8983/solr/employees/select?q=*:*&amp;wt=json&amp;facet=true&amp;facet.field=dept_s&amp;facet.field=title_s&amp;facet.mincount=1&amp;rows=0
Gorgi spellcheck: http://localhost:8983/solr/employees/select?q=gorgi&amp;wt=json&amp;qf=first_name&amp;defType=edismax
Georgi Phonetic: http://localhost:8983/solr/employees/select?q=georgi&amp;wt=json&amp;qf=first_name%20last_name%20phonetic&amp;defType=edismax</pre><p></p>
<h2>The next step</h2>
<p>We should now be back where we ended with the original blog post. So far we have successfully</p>
<ul>
<li>Setup a database with content</li>
<li>Indexed the database into our Solr index</li>
<li>Setup basic scheduled delta reindexing</li>
</ul>
<p>Let&#8217;s get started with the more interesting stuff!</p>
<h2>Facets</h2>
<p>Facets, also known as filters or navigators, allow a search user to refine and drill down through search results. Before we get started with them, we need to update our data import configuration. Replace the contents of our existing db-data-config.xml with:</p>
<div id="file-db-data-config2-LC1" class="line">
<pre class="crayon-plain-tag">select e.emp_no as 'id', e.birth_date,
(
select t.title
order by t.`from_date` desc
limit 1
) as 'title_s', e.first_name, e.last_name, e.gender as 'gender_s', d.`dept_name` as 'dept_s'
from employees e
join dept_emp de on de.`emp_no` = e.`emp_no`
join departments d on d.`dept_no` = de.`dept_no`
join titles t on t.`emp_no` = e.`emp_no`
group by e.`emp_no`
limit 1000;</pre><br />
To be able to facet, we need appropriate fields upon which to actually facet. Our new SQL retrieves additional fields such as employee titles and departments. Fields perfect for use as facets.</p>
</div>
<p><a href="http://blog.comperiosearch.com/wp-content/uploads/2015/04/Screen-Shot-2015-09-23-at-10.27.47.png"><img class="aligncenter size-medium wp-image-3520" src="http://blog.comperiosearch.com/wp-content/uploads/2015/04/Screen-Shot-2015-09-23-at-10.27.47.png" alt="Updated Employee SQL" width="300" height="166" /></a><br />
You&#8217;ll notice we map title, gender and dept_name to title_s, gender_s and dept_s respectively. This allows us to take advantage of an existing dynamic field mapping in Solr&#8217;s default basic config, *_s. A dynamic field allows us to assign all fields with a certain pre/suffix the same field type. In this case, given the field type <pre class="crayon-plain-tag">&lt;dynamicField name="*_s" type="string" indexed="true" stored="true" /&gt;</pre> , any fields ending with _s will be indexed and stored as basic strings. Solr will not tokenise them and modify their contents. This allows us to safely use them for faceting without worrying about department titles being split on white spaces for example.</p>
<ol>
<li>Clear the index and restart Solr.<a href="http://blog.comperiosearch.com/wp-content/uploads/2022/04/Screen-Shot-2015-04-13-at-17.06.22.png"><img class="alignright wp-image-3533 size-medium" src="http://blog.comperiosearch.com/wp-content/uploads/2022/04/Screen-Shot-2015-04-13-at-17.06.22-196x300.png" alt="Facet Query" width="196" height="300" /></a></li>
<li>Once Solr has restarted, reindex the database with<br />
our new SQL. Don&#8217;t be alarmed if this takes a bit longer<br />
than previously. It&#8217;s a bit more heavy weight and not<br />
very well optimised!</li>
<li>Once it&#8217;s done indexing, we can<br />
confirm it was successful by running the facet query via<br />
Postman or directly in our browser.</li>
<li>We should see two hits for the query &#8220;georgi&#8221; along with<br />
facets for their respective titles and department.</li>
</ol>
<p>&nbsp;</p>
<h2>The anatomy of a facet query</h2>
<p>Let&#8217;s take a closer look at the relevant request parameters of our facet query: <pre class="crayon-plain-tag">http://localhost:8983/solr/employees/select?q=georgi&amp;wt=json&amp;qf=first_name%20last_name&amp;defType=edismax&amp;omitHeader=true&amp;facet=true&amp;facet.field=dept_s&amp;facet.field=title_s&amp;facet.mincount=1</pre></p>
<ul>
<li>facet &#8211; Tells Solr to enable or prevent faceting. Accepted values include yes,on and true to enable, no, off and false to disable</li>
<li>facet.field &#8211; Which field we want to facet on, can be defined multiple times</li>
<li>facet.mincount &#8211; The minimum number of values for a particular facet value the query results includes for it to be included in the facet result object. Can be defined per facet field with this syntax f.fieldName.facet.mincount=1</li>
</ul>
<p>There are many other facet parameters. I recommend taking a look at the Solr wiki pages on <a href="https://wiki.apache.org/solr/SolrFacetingOverview">faceting</a> and other <a href="https://wiki.apache.org/solr/SimpleFacetParameters">possible parameters</a>.</p>
<h2>Spellcheck</h2>
<p>Analysing query logs and focusing on those queries that gave zero hits is a quick and easy way to see what can and should be done to improve your search solution. More often than not you will come across a great deal of spelling errors. Adding spellcheck to a search solution gives such great value for a tiny bit of effort. This fruit is so low hanging it should hit you in the face!</p>
<p>To enable spellcheck, we need to make some configuration changes.</p>
<ol>
<li>In our schema.xml, add these two lines after the *_name dynamic field type we added earlier:
<div class="line">
<pre class="crayon-plain-tag">&lt;copyField source="*_name" dest="spellcheck" /&gt;
&lt;field name="spellcheck" type="text_general" indexed="true" stored="true" multiValued="true" /&gt;</pre>
</div>
<p>A copyField checks for fields whose names match the pattern defined in source and copies their destinations to the dest field. In our case, we will copy content from first_name and last_name to spellcheck. We then define the spellcheck field as multiValued to handle its multiple sources.</li>
<li>Add the following to our solrconfig.xml:
<div id="file-spellcheck-LC1" class="line">
</p><pre class="crayon-plain-tag">&lt;searchComponent name="spellcheck" class="solr.SpellCheckComponent"&gt;
&lt;str name="queryAnalyzerFieldType"&gt;text_general&lt;/str&gt;
&lt;!-- a spellchecker built from a field of the main index --&gt;
&lt;lst name="spellchecker"&gt;
&lt;str name="name"&gt;default&lt;/str&gt;
&lt;str name="field"&gt;spellcheck&lt;/str&gt;
&lt;str name="classname"&gt;solr.DirectSolrSpellChecker&lt;/str&gt;
&lt;!-- the spellcheck distance measure used, the default is the internal levenshtein --&gt;
&lt;str name="distanceMeasure"&gt;internal&lt;/str&gt;
&lt;!-- minimum accuracy needed to be considered a valid spellcheck suggestion --&gt;
&lt;float name="accuracy"&gt;0.5&lt;/float&gt;
&lt;!-- the maximum #edits we consider when enumerating terms: can be 1 or 2 --&gt;
&lt;int name="maxEdits"&gt;2&lt;/int&gt;
&lt;!-- the minimum shared prefix when enumerating terms --&gt;
&lt;int name="minPrefix"&gt;1&lt;/int&gt;
&lt;!-- maximum number of inspections per result. --&gt;
&lt;int name="maxInspections"&gt;5&lt;/int&gt;
&lt;!-- minimum length of a query term to be considered for correction --&gt;
&lt;int name="minQueryLength"&gt;4&lt;/int&gt;
&lt;!-- maximum threshold of documents a query term can appear to be considered for correction --&gt;
&lt;float name="maxQueryFrequency"&gt;0.01&lt;/float&gt;
&lt;!-- uncomment this to require suggestions to occur in 1% of the documents
&lt;float name="thresholdTokenFrequency"&gt;.01&lt;/float&gt;
--&gt;
&lt;/lst&gt;
&lt;/searchComponent&gt;</pre><p>
</div>
<p>This will create a spellchecker component that uses the spellcheck field as its dictionary source. The spellcheck field contains content copied from both first and last name fields.</li>
<li>In the same file, look for the select requestHandler and update it to include the spellcheck component:
<div id="file-select-LC1" class="line">
</p><pre class="crayon-plain-tag">&lt;requestHandler name="/select" class="solr.SearchHandler"&gt;
&lt;!-- default values for query parameters can be specified, these
will be overridden by parameters in the request
--&gt;
&lt;lst name="defaults"&gt;
&lt;str name="echoParams"&gt;explicit&lt;/str&gt;
&lt;int name="rows"&gt;10&lt;/int&gt;
&lt;str name="spellcheck"&gt;on&lt;/str&gt;
&lt;str name="spellcheck.dictionary"&gt;default&lt;/str&gt;
&lt;/lst&gt;
&lt;!-- Add this to enable spellcheck --&gt;
&lt;arr name="last-components"&gt;
&lt;str&gt;spellcheck&lt;/str&gt;
&lt;/arr&gt;
&lt;/requestHandler&gt;</pre><p>
</div>
</li>
</ol>
<p>The defaults list in a requestHandler defines which default parameters to add to each request made using the chosen request handler. You could, for example, define which fields to query. In this case we&#8217;re enabling spellcheck and using the default dictionary as defined in our solrconfig.xml. All values in the defaults list can be overwritten per request. To include request parameters that cannot be overwritten, we would need to use an invariants list instead:</p><pre class="crayon-plain-tag">&lt;lst name="invariants"&gt;
&lt;str name="defType"&gt;edismax&lt;/str&gt;
&lt;/lst&gt;</pre><p>Both lists can be used simultaneously. When duplicate keys are present the values in the invariants list will take precedence.</p>
<p>Once we&#8217;ve made all our configuration changes, let&#8217;s restart Solr and reindex. To verify the changes worked, do a basic retrieve all query and check the resulting documents for the spellcheck field. Its contents should be the same as the document&#8217;s first_name and last_name fields.</p>
<p>Because we have enabled spellcheck by default,<a href="http://blog.comperiosearch.com/wp-content/uploads/2022/04/Screen-Shot-2015-04-14-at-15.20.45.png"><img class="alignright size-medium wp-image-3575" src="http://blog.comperiosearch.com/wp-content/uploads/2022/04/Screen-Shot-2015-04-14-at-15.20.45-174x300.png" alt="Gorgi" width="174" height="300" /></a> queries with possible suggestions will include contents in the spellcheck response object.</p>
<p>Try the Gorgi spellcheck query and experiment with different queries. To query the last_name field as well, change the qf parameter to <pre class="crayon-plain-tag">qf=first_name last_name</pre>.</p>
<p>The qf parameter defines which fields to use as the search domain.</p>
<p>When the spellcheck response object has content, you can easily use it to implement a basic &#8220;did you mean&#8221; feature. This will vastly improve your zero hit page.</p>
<h2>Phonetic Search</h2>
<p>Now that we have a basic spellcheck component in place, the next best feature that easily creates value in a people search system is phonetics. Solr ships with some <a href="https://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters#solr.PhoneticFilterFactory">basic</a> <a href="https://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters#solr.DoubleMetaphoneFilterFactor">phonetic</a> <a href="https://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters#solr.BeiderMorseFilterFactory">tokenisers</a>. The most commonly used out of the box phonetic tokeniser is the DoubleMetaphoneFilterFactory. It will suffice for most use cases. It does, however, have some weaknesses, which we will go into briefly in the next section.</p>
<p>We need to once again modify our schema.xml to take advantage of Solr&#8217;s phonetic capabilities. Add the following:</p><pre class="crayon-plain-tag">&lt;fieldType name="phonetic" class="solr.TextField" &gt;
 &lt;analyzer&gt;
 &lt;tokenizer class="solr.StandardTokenizerFactory"/&gt;
 &lt;filter class="solr.DoubleMetaphoneFilterFactory" inject="true" maxCodeLength="4"/&gt;
 &lt;/analyzer&gt;
 &lt;/fieldType&gt;

 &lt;copyField source="*_name" dest="phonetic" /&gt;
 &lt;field name="phonetic" type="phonetic" indexed="true" stored="false" multiValued="true" /&gt;</pre><p>Similar to spellcheck, we copy contents from the name fields into a phonetic field. Here we define a phonetic field, whose values will not be stored as we don&#8217;t need to return them in search results. It is, however, indexed so we can actually include it in the search domain. Finally, like spellcheck, it is multivalued to handle multiple potential sources. The reason we create an additional search field is so we can apply different weightings to exact matches and phonetic matches.</p>
<p>Restart Solr, clear the index and reindex.</p>
<p>Running the Georgi Phonetic search request should now returns hits based on exact and phonetic matches. To ensure that exact matches are ranked higher, we can add a query time boost to our query fields: <pre class="crayon-plain-tag">&amp;qf=first_name last_name phonetic^0.5</pre></p>
<p>Rather than apply boosts to fields we want to rank higher, it&#8217;s usually simpler to apply a punitive boost to fields we wish to rank lower. Replace the qf parameter in the Georgi Phonetic request and see how the first few results all have an exact match for georgi in the first_name field.</p>
<h2>Query Analysis</h2>
<p>As we look further down the result set, you will notice some strange matches. One employee, called Kirk Kalsbeek, is apparently a match for &#8220;georgi&#8221;. To understand why this is a match, we can use Solr&#8217;s analysis tool.<br />
<a href="http://blog.comperiosearch.com/wp-content/uploads/2022/04/Screen-Shot-2015-04-14-at-17.09.01.png"><img src="http://blog.comperiosearch.com/wp-content/uploads/2022/04/Screen-Shot-2015-04-14-at-17.09.01-300x247.png" alt="Solr Analysis" width="300" height="247" class="aligncenter size-medium wp-image-3585" /></a><br />
It allows use to define an indexed value, a query value and the field type to use and then demonstrate how each value is tokenised and whether or not the query would result in a match.</p>
<p>With the values Kirk Kalsbeek, georgi and phonetic respectively, the analysis tool shows us that Kirk gets tokenised to KRK by our phonetic field type. Georgi is also tokenised to KRK, which results in a match.</p>
<p>To create a better phonetic search solution, we would have to implement a custom phonetic tokeniser. I came across <a href="https://github.com/kvalle/norphoname"> an example</a>, which has helped me enormously in improving phonetic search for Norwegian names on a project.</p>
<h2>Conclusion</h2>
<p>We should now be able to </p>
<ul>
<li>Implement index field based spellcheck</li>
<li>Use basic faceting</li>
<li>Implement Solr&#8217;s out of the box phonetic capabilities</li>
</ul>
<p>Query completion I will leave for the next time. I promise you won&#8217;t have to wait as long between posts as last time :)</p>
<p>Let me know how you get on in the comments below!</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.comperiosearch.com/blog/2015/04/14/solr-indexing-index-sql-databases-made-easier-part-2/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>
