Integracja Oracle Apex z Neo4j - użycie grafowej bazy danych do wyszukania podobnych artykułów w bazie danych.
Neo4j oferuje niesamowitą prędkość odczytu informacji. Dlaczego więc nie wykorzystać tej bazy danych np. do wyszukania podobnych produktów w bazie danych. Silnik wyszukiwarki możemy zbudować w SQL ale biblioteka Lucene z której korzysta Neo4j da nam lepsze rezultaty uzyskane dużo niższym wysiłkiem. Wyszukiwarka oparta o Neo4j zwróci rządaną liczbę rezultatów oznaczonych współczynnikiem podobieństwa do wyszukiwanej frazy (scoring). Więcej o Fulltext search dowiesz się z tej strony.
Z naszych poprzednich artykułów na blogu dowiesz się jak szybko uruchomić grafową bazę danych Neo4j, jak połączyć się z nią poprzez API i jak skonfigurować index fulltext. Dzisiaj zajmiemy się połączeniem do Neo4j z Oracle Apex.
Neo4j - uruchomienie bazy z produktami
Po zainstalowaniu i uruchomieniu serwera Ne4j skonfiguruj bazę danych. Zacznij od utworzenia indeksów. Jeśli nody przechowujące produkty będą nazywać sie 'product' a unikalny numer produktu do 'product_id', komenda tworząca indeks bedzie mieć postać:
CREATE CONSTRAINT product_id ON (p:product) ASSERT p.product_id IS UNIQUE
Produkty możesz utworzyć na wiele sposobów. Ładując je poleceniem 'LOAD CSV' bazy danych, używając HTTP API lub wykorzystując komponenty Pentaho PDI (jeden z łatwiejszych sposobów). Polecenie zapewniajace sprawdzenie przed załadowaniem produktu czy nie znajduje się już podobny może mieć postać:
MERGE (p:product { product_id: 100, product_name: "opis artykułu" })
Ostatnią czynnością jest utworzenie indeksu typu fulltext na polu 'product_name'. Tworzymy go poleceniem:
CALL db.index.fulltext.createNodeIndex("ProductNameIndex",["product"],["product_name"])
Więcej o przygotowaniu bazy danych Ne4j dowiesz się z tego artykułu.
Oracle Apex - połączenie do HTTP API
Nasza procedura PL/SQL prześle do grafowej bazy Neo4j zapytanie o wyszukanie 12 podobnych opisów produktów - prześlemy opis produktu. W rezultacie Neo4j zwróci 12 maksymalnie 12 wierszy podobnych fraz + numer produktu + scoring każdego z wierszy (wyższa liczba oznacza lepszy traf).
Rezulat zostanie przesłany w formacie JSON ktory poddamy parsowaniu a następnie umieścimy rekordy w tabeli. Wcześniej, przed operacja parsowania JSON wykasujemy zawartość tabeli która tymczasowo przechowuje rezulat wyszukiwania.
DECLARE
v_file_name VARCHAR2 (25);
v_json VARCHAR2(2000);
v_clob CLOB;
l_num_cols NUMBER;
l_num_data NUMBER;
l_col VARCHAR2(80);
l_var VARCHAR2(80);
l_sql VARCHAR2(200);
l_desc VARCHAR2(200);
l_score VARCHAR2(50);
l_product_id NUMBER(8);
v_test varchar(400);
v_test_num number(8);
v_article_description VARCHAR2(80) := :P51_ARTICLE_DESCRIPTION; -- your article desc field item
v_hostname VARCHAR2(30) := 'localhost'; -- your hostname, eg: localhost
v_port NUMBER := 7474; -- port for your Ne04j server, eg: 7474
v_username VARCHAR2(50) := 'neo4j'; --:Neo4j server username
v_password VARCHAR2(50) := 'some_password'; -- :Neo4j server password
v_authorization_string VARCHAR2(30) := v_username || ':' || v_password;
v_rest_url VARCHAR2(300) := 'http://' || v_authorization_string || '@' || v_hostname || ':' || v_port || '/db/neo4j/tx/commit';
BEGIN
--delete old content from table
l_sql := 'DELETE FROM similar';
execute immediate l_sql;
commit;
l_sql := '';
-- prepare JSON:
v_json := '{"statements" : [ {"statement" : "CALL db.index.fulltext.queryNodes(\"ProductNameIndex\", \"product_name: '|| v_article_description ||'\") YIELD node, score RETURN node.product_name, node.product_id AS product_id, score LIMIT 12"} ]}';
apex_web_service.g_request_headers(1).name := 'Content-Type';
apex_web_service.g_request_headers(1).value := 'application/json';
-- POST JSON to server
v_clob := apex_web_service.make_rest_request(
p_url => v_rest_url,
p_http_method => 'POST',
p_body => v_json
);
apex_json.parse(v_clob);
l_num_cols := apex_json.get_count('results[1].columns');
l_num_data := apex_json.get_count('results[1].data');
FOR l_idx IN 1..l_num_data LOOP
-- each column in rows[]
FOR l_idx_cols IN 1..l_num_cols LOOP
l_col := apex_json.get_varchar2('results[1].columns[%d]', l_idx_cols);
l_var := apex_json.get_varchar2('results[1].data[%d].row[%d]', l_idx, l_idx_cols);
--description column:
if l_col = 'node.product_name' then l_desc := l_var;
end if;
--product_id
if l_col = 'product_id' then l_product_id := l_var;
end if;
--score column:
if l_col = 'score' then l_score := TO_NUMBER(l_var);
end if;
END LOOP;
l_sql := 'INSERT INTO similar (product_id, product_name, score) VALUES (' || l_product_id || ',''' || l_desc || ''', ''' || ROUND(l_score, 4) ||''')';
execute immediate l_sql;
commit;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END;
Wyszukanie 12 podobnych produktów w bazie danych Neo4j w której znajduje się 50 tysięcy produktów zajmuje mniej niż sekundę. Zdecydowanie dłużej parsuje i wczytuje się w przeglądarce strona Oracle Apex. Więcej o HTTP API Neo4j przeczytasz tutaj.