2015-01-21 12 views
6

Zadałem pytanie earlier o agregowanie ilości wzdłuż wykresu. Dwie podane odpowiedzi działały dobrze, ale teraz próbuję rozszerzyć zapytanie Cyphera do wykresu o zmiennej głębokości.Zapytanie rekurencyjne z agregacją podageksową (dowolna głębokość)

Podsumowując, zaczynamy od kilku magazynów liści, z których wszystkie są powiązane z konkretnym dostawcą, który jest własnością węzła Store. Zapasy są następnie przenoszone do innych sklepów, a udział poszczególnych dostawców odpowiada ich udziałowi w pierwotnym sklepie.

Więc dla węzła B02, S2 przyczyniły 750/1250 = 60% i S3 przyczyniły 40%. Przesuwamy następnie 600 jednostek naszych z B02, z których 60% należy do S2 i 40% do S3 i tak dalej.

enter image description here

Co chcemy wiedzieć, jaki procent końcowych 700 jednostek w D01 należeć do każdego dostawcy. Gdzie dostawcy o tej samej nazwie są tym samym dostawcą. Tak na powyższym wykresie oczekujemy:

S1, S2 38,09
, 27.61
S3 34,28

Mam przygotował wykres za pomocą tego skryptu Cypher:

CREATE (A01:Store {Name: 'A01', Supplier: 'S1'}) 
CREATE (A02:Store {Name: 'A02', Supplier: 'S1'}) 
CREATE (A03:Store {Name: 'A03', Supplier: 'S2'}) 
CREATE (A04:Store {Name: 'A04', Supplier: 'S3'}) 
CREATE (A05:Store {Name: 'A05', Supplier: 'S1'}) 
CREATE (A06:Store {Name: 'A06', Supplier: 'S1'}) 
CREATE (A07:Store {Name: 'A07', Supplier: 'S2'}) 
CREATE (A08:Store {Name: 'A08', Supplier: 'S3'}) 

CREATE (B01:Store {Name: 'B01'}) 
CREATE (B02:Store {Name: 'B02'}) 
CREATE (B03:Store {Name: 'B03'}) 
CREATE (B04:Store {Name: 'B04'}) 

CREATE (C01:Store {Name: 'C01'}) 
CREATE (C02:Store {Name: 'C02'}) 

CREATE (D01:Store {Name: 'D01'}) 

CREATE (A01)-[:MOVE_TO {Quantity: 750}]->(B01) 
CREATE (A02)-[:MOVE_TO {Quantity: 500}]->(B01) 
CREATE (A03)-[:MOVE_TO {Quantity: 750}]->(B02) 
CREATE (A04)-[:MOVE_TO {Quantity: 500}]->(B02) 
CREATE (A05)-[:MOVE_TO {Quantity: 100}]->(B03) 
CREATE (A06)-[:MOVE_TO {Quantity: 200}]->(B03) 
CREATE (A07)-[:MOVE_TO {Quantity: 50}]->(B04) 
CREATE (A08)-[:MOVE_TO {Quantity: 450}]->(B04) 

CREATE (B01)-[:MOVE_TO {Quantity: 400}]->(C01) 
CREATE (B02)-[:MOVE_TO {Quantity: 600}]->(C01) 
CREATE (B03)-[:MOVE_TO {Quantity: 100}]->(C02) 
CREATE (B04)-[:MOVE_TO {Quantity: 200}]->(C02) 

CREATE (C01)-[:MOVE_TO {Quantity: 500}]->(D01) 
CREATE (C02)-[:MOVE_TO {Quantity: 200}]->(D01) 

Obecne zapytanie to:

MATCH (s:Store { Name:'D01' }) 
MATCH (s)<-[t:MOVE_TO]-()<-[r:MOVE_TO]-(supp) 
WITH t.Quantity as total, collect(r) as movements 
WITH total, movements, reduce(totalSupplier = 0, r IN movements | totalSupplier + r.Quantity) as supCount 
UNWIND movements as movement 
RETURN startNode(movement).Supplier as Supplier, round(100.0*movement.Quantity/supCount) as pct 

Próbuję użyć rekurencyjnych relacje, coś na wzór tego:

MATCH (s)<-[t:MOVE_TO]-()<-[r:MOVE_TO*]-(supp) 

jednak, że daje wiele ścieżek do węzła końcowego i muszę agregować inwentaryzacji w każdym węźle myślę.

+0

myślę o tym, choć problemem jest to, że nie sądzę, Cypher naprawdę rekursji. Cypher ocenia jeden subgraph na raz za pomocą 'MATCH', który w tym przypadku jest jedną ścieżką w głąb drzewa. Ale chcesz porównać ścieżki ze sobą –

+1

Także, jeśli chcesz tylko ścieżek ze sklepu do oryginalnych węzłów dostawców, chcesz coś jak 'MATCH (cel: Store {Nazwa: 'D01'}) <- [ r: MOVE_TO *] - (źródło: Store) WHERE source.Supplier IS NOT NULL' –

+0

Oprócz sugestii Briana, podobnie możesz użyć "WHERE NOT (source) <- [: MOVE_TO] -()' – JohnMark13

Odpowiedz

2

To zapytanie generuje poprawne wyniki dla dowolnego wykresu zgodnego z modelem opisanym w pytaniu. (Gdy Store X porusza towaru do Store y, to zakłada się, że Supplier procenty przenoszonego towaru jest taki sam jak dla Store x).

Jednakże, to rozwiązanie nie składa się z tylko jednej zapytania Cypher (ponieważ może nie być możliwe). Zamiast tego zawiera wiele zapytań, z których jeden musi być iterowany do momentu, aż obliczenia będą kaskadować się przez cały wykres węzłów Store. To iterowane zapytanie jasno określi, kiedy zatrzymać iterowanie. Pozostałe zapytania cyprowe są potrzebne do: przygotowania wykresu dla iteracji, podania procentów dostawcy dla węzłów "końcowych" i oczyszczenia wykresu (tak, aby był przywracany do stanu sprzed etapu 1, poniżej) .

Zapytania te prawdopodobnie mogłyby zostać dodatkowo zoptymalizowane.

Oto wymagane kroki:

  1. sporządzenia krzywej dla zapytania iteracyjnie (inicjuje czasowego pcts tablicy dla wszystkich węzłów wyjściowych Store). Obejmuje to utworzenie węzła singleton Suppliers z tablicą zawierającą wszystkie nazwy dostawców. Służy do ustalenia kolejności elementów tymczasowych macierzy i odwzorowania tych elementów z powrotem na prawidłową nazwę dostawcy.

    MATCH (store:Store) 
    WHERE HAS (store.Supplier) 
    WITH COLLECT(store) AS stores, COLLECT(DISTINCT store.Supplier) AS csup 
    CREATE (sups:Suppliers { names: csup }) 
    WITH stores, sups 
    UNWIND stores AS store 
    SET store.pcts = 
        EXTRACT(i IN RANGE(0,LENGTH(sups.names)-1,1) | 
        CASE WHEN store.Supplier = sups.names[i] THEN 1.0 ELSE 0.0 END) 
    RETURN store.Name, store.Supplier, store.pcts; 
    

    Oto wynik z danymi pytaniem jest:

    +---------------------------------------------+ 
    | store.Name | store.Supplier | store.pcts | 
    +---------------------------------------------+ 
    | "A01"  | "S1"   | [1.0,0.0,0.0] | 
    | "A02"  | "S1"   | [1.0,0.0,0.0] | 
    | "A03"  | "S2"   | [0.0,1.0,0.0] | 
    | "A04"  | "S3"   | [0.0,0.0,1.0] | 
    | "A05"  | "S1"   | [1.0,0.0,0.0] | 
    | "A06"  | "S1"   | [1.0,0.0,0.0] | 
    | "A07"  | "S2"   | [0.0,1.0,0.0] | 
    | "A08"  | "S3"   | [0.0,0.0,1.0] | 
    +---------------------------------------------+ 
    8 rows 
    83 ms 
    Nodes created: 1 
    Properties set: 9 
    
  2. zapytanie iteracyjne (uruchomić, dopóki nie zostaną zwrócone 0 rzędy)

    MATCH p=(s1:Store)-[m:MOVE_TO]->(s2:Store) 
    WHERE HAS(s1.pcts) AND NOT HAS(s2.pcts) 
    SET s2.pcts = EXTRACT(i IN RANGE(1,LENGTH(s1.pcts),1) | 0) 
    WITH s2, COLLECT(p) AS ps 
    WITH s2, ps, REDUCE(s=0, p IN ps | s + HEAD(RELATIONSHIPS(p)).Quantity) AS total 
    FOREACH(p IN ps | 
        SET HEAD(RELATIONSHIPS(p)).pcts = EXTRACT(parentPct IN HEAD(NODES(p)).pcts | parentPct * HEAD(RELATIONSHIPS(p)).Quantity/total) 
    ) 
    FOREACH(p IN ps | 
        SET s2.pcts = EXTRACT(i IN RANGE(0,LENGTH(s2.pcts)-1,1) | s2.pcts[i] + HEAD(RELATIONSHIPS(p)).pcts[i]) 
    ) 
    RETURN s2.Name, s2.pcts, total, EXTRACT(p IN ps | HEAD(RELATIONSHIPS(p)).pcts) AS rel_pcts; 
    

    Iteracja 1 wynik:

    +-----------------------------------------------------------------------------------------------+ 
    | s2.Name | s2.pcts  | total | rel_pcts             | 
    +-----------------------------------------------------------------------------------------------+ 
    | "B04" | [0.0,0.1,0.9] | 500 | [[0.0,0.1,0.0],[0.0,0.0,0.9]]        | 
    | "B01" | [1.0,0.0,0.0] | 1250 | [[0.6,0.0,0.0],[0.4,0.0,0.0]]        | 
    | "B03" | [1.0,0.0,0.0] | 300 | [[0.3333333333333333,0.0,0.0],[0.6666666666666666,0.0,0.0]] | 
    | "B02" | [0.0,0.6,0.4] | 1250 | [[0.0,0.6,0.0],[0.0,0.0,0.4]]        | 
    +-----------------------------------------------------------------------------------------------+ 
    4 rows 
    288 ms 
    Properties set: 24 
    

    iteracji 2 Wynik:

    +-------------------------------------------------------------------------------------------------------------------------------+ 
    | s2.Name | s2.pcts          | total | rel_pcts              | 
    +-------------------------------------------------------------------------------------------------------------------------------+ 
    | "C02" | [0.3333333333333333,0.06666666666666667,0.6] | 300 | [[0.3333333333333333,0.0,0.0],[0.0,0.06666666666666667,0.6]] | 
    | "C01" | [0.4,0.36,0.24]        | 1000 | [[0.4,0.0,0.0],[0.0,0.36,0.24]]        | 
    +-------------------------------------------------------------------------------------------------------------------------------+ 
    2 rows 
    193 ms 
    Properties set: 12 
    

    iteracji 3 Wynik:

    +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
    | s2.Name | s2.pcts              | total | rel_pcts                             | 
    +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
    | "D01" | [0.38095238095238093,0.27619047619047615,0.34285714285714286] | 700 | [[0.2857142857142857,0.2571428571428571,0.17142857142857143],[0.09523809523809522,0.01904761904761905,0.17142857142857143]] | 
    +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
    1 row 
    40 ms 
    Properties set: 6 
    

    iteracji 4 Wynik:

    +--------------------------------------+ 
    | s2.Name | s2.pcts | total | rel_pcts | 
    +--------------------------------------+ 
    +--------------------------------------+ 
    0 rows 
    69 ms 
    
  3. Lista niezerowa Supplier procentowe kończącego Store węzła (S) .

    MATCH (store:Store), (sups:Suppliers) 
    WHERE NOT (store:Store)-[:MOVE_TO]->(:Store) AND HAS(store.pcts) 
    RETURN store.Name, [i IN RANGE(0,LENGTH(sups.names)-1,1) WHERE store.pcts[i] > 0 | {supplier: sups.names[i], pct: store.pcts[i] * 100}] AS pcts; 
    

    Wynik:

    +----------------------------------------------------------------------------------------------------------------------------------+ 
    | store.Name | pcts                            | 
    +----------------------------------------------------------------------------------------------------------------------------------+ 
    | "D01"  | [{supplier=S1, pct=38.095238095238095},{supplier=S2, pct=27.619047619047617},{supplier=S3, pct=34.285714285714285}] | 
    +----------------------------------------------------------------------------------------------------------------------------------+ 
    1 row 
    293 ms 
    
  4. Clean up (usunięcie wszystkich tymczasowych pcts rekwizyty i węzeł Suppliers).

    MATCH (s:Store), (sups:Suppliers) 
    OPTIONAL MATCH (s)-[m:MOVE_TO]-() 
    REMOVE m.pcts, s.pcts 
    DELETE sups; 
    

    Wynik:

    0 rows 
    203 ms 
    +-------------------+ 
    | No data returned. | 
    +-------------------+ 
    Properties set: 29 
    Nodes deleted: 1 
    
+0

@NedStoyanov: Czy to działa dla Ciebie? – cybersam

+0

Dzięki @cubersam, to zapytanie otrzyma właściwy wynik. Miałem niewłaściwy oczekiwany wynik w pytaniu. Dzięki za twój wysiłek. –

+0

@Ned Stoyanov dać człowiekowi nagrodę! –

2

Nie mogę wymyślić, jak rozwiązać rozwiązanie w czystej cyfr, ponieważ nie sądzę, że można zrobić rekursję w ten sposób w cypher. Możesz użyć cyphera, aby w prosty sposób przywrócić wszystkie dane w drzewie, aby można je było obliczyć w swoim ulubionym języku programowania. Coś takiego:

MATCH path=(source:Store)-[move:MOVE_TO*]->(target:Store {Name: 'D01'}) 
WHERE source.Supplier IS NOT NULL 
RETURN 
    source.Supplier, 
    reduce(a=[], move IN relationships(path)| a + [{id: ID(move), Quantity: move.Quantity}]) 

Spowoduje to wyświetlenie identyfikatora i ilości dla każdej relacji na każdej ścieżce. Następnie mógłbyś przetworzyć tę stronę klienta (być może najpierw przekształcając ją w strukturę danych zagnieżdżonych?)

+0

dzięki za odpowiedź, podoba mi się twoja technika gromadzenia ruchów w tablicy. Mogę poczekać trochę dłużej, aby zobaczyć, czy są inne odpowiedzi. –

+0

Wystarczająco fair;) Zdecydowanie chciałbym zobaczyć inną odpowiedź. Ponadto nie wiem, jakiego języka używasz, ale powinienem wspomnieć, jeśli używasz java lub czegoś, co integruje się z interfejsami API Java, możesz alternatywnie uzyskać dostęp do bazy danych za pomocą interfejsów API neo4j java. Trzeba jednak działać w trybie wbudowanym, który ma swoje własne komplikacje. –

+0

Korzystamy z C#, więc najlepiej byłoby, gdybyśmy nie pisali kodu java –

3

Jak już powiedziałem, zanim się to podobało.Wiem, że już zaakceptowałeś odpowiedź, ale zdecydowałem się opublikować moją ostateczną odpowiedź, ponieważ również zwraca percentyl bez wysiłku klienta (co oznacza, że ​​możesz także wykonać SET na węzłach, aby zaktualizować wartość w db, gdy potrzebujesz) i oczywiście jeśli z jakiegokolwiek innego powodu, jak jeden można wrócić do :) tutaj jest link do console example

zwraca wiersz z nazwą sklepu, kwota przeniesiona do niego od wszystkich dostawców i percentyla każdego dostawcy

MATCH p =s<-[:MOVE_TO*]-sup 
WHERE HAS (sup.Supplier) AND NOT HAS (s.Supplier) 
WITH s,sup,reduce(totalSupplier = 0, r IN relationships(p)| totalSupplier + r.Quantity) AS TotalAmountMoved 
WITH sum(TotalAmountMoved) AS sumMoved, collect(DISTINCT ([sup.Supplier, TotalAmountMoved])) AS MyDataPart1,s 
WITH reduce(b=[], c IN MyDataPart1| b +[{ Supplier: c[0], Quantity: c[1], Percentile: ((c[1]*1.00))/(sumMoved*1.00)*100.00 }]) AS MyData, s, sumMoved 
RETURN s.Name, sumMoved, MyData 
+0

Interesujące. Czy to jest uogólnione? Czy nadal by działało, gdyby był jeszcze jeden poziom? –

+0

powinno to działać z tylu poziomów, ile chcesz. Oczywiście można również ograniczyć go do sklepu lub dostawcy, dodając filtr do s lub sup meczu – cechode

+1

Niestety, matematyka używana przez to zapytanie jest niepoprawna. Rzeczywiste obliczenia dla ostatecznych wartości procentowych nie obejmują dodania wszystkich wartości w każdej ścieżce do "D01", a następnie użycie sumy całkowitej jako mianownika dla obliczeń procentowych. Zamiast tego należy obliczyć wartości procentowe w każdym węźle Sklepu, a następnie pomnożyć razem odpowiednie wartości procentowe wzdłuż każdej ścieżki. Będę tworzył odpowiedź, która generuje właściwą odpowiedź (ale wymaga wielokrotnych połączeń). – cybersam