

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

# Best practice di Neptune per l'utilizzo di openCypher e Bolt
<a name="best-practices-opencypher"></a>

È necessario seguire queste best practice per usare il linguaggio di query openCypher e il protocollo Bolt con Neptune. Per informazioni sull'utilizzo di openCypher in Neptune, consulta [Accesso al grafo di Neptune con openCypher](access-graph-opencypher.md).

**Topics**
+ [Creazione di una nuova connessione dopo il failover](#best-practices-opencypher-renew-connection)
+ [Gestione delle connessioni per applicazioni di lunga durata](#best-practices-opencypher-long-connections)
+ [Gestione della connessione per AWS Lambda](#best-practices-opencypher-lambda-connections)
+ [Preferire archi orientati a quelli bidirezionali nelle query](best-practices-opencypher-directed-edges.md)
+ [Neptune non supporta più query simultanee in una transazione](best-practices-opencypher-multiple-queries.md)
+ [Chiusura degli oggetti driver al termine dell'utilizzo](best-practices-opencypher-close-driver.md)
+ [Utilizzo di modalità di transazione esplicite per la lettura e la scrittura](best-practices-opencypher-use-explicit-txs.md)
+ [Logica di ripetizione dei tentativi per le eccezioni](best-practices-opencypher-retry-logic.md)
+ [Imposta più proprietà contemporaneamente utilizzando una singola clausola SET](best-practices-content-0.md)
+ [Utilizzare query parametrizzate](best-practices-content-2.md)
+ [Usa mappe appiattite anziché mappe annidate nella clausola UNWIND](best-practices-content-3.md)
+ [Posiziona nodi più restrittivi sul lato sinistro nelle espressioni VLP (Variable-Length Path)](best-practices-content-4.md)
+ [Evita controlli ridondanti delle etichette dei nodi utilizzando nomi di relazione granulari](best-practices-content-5.md)
+ [Specificate le etichette dei bordi, ove possibile](best-practices-content-6.md)
+ [Evita di utilizzare la clausola WITH quando possibile](best-practices-content-7.md)
+ [Posiziona filtri restrittivi il più presto possibile nella query](best-practices-content-8.md)
+ [Controlla esplicitamente se esistono delle proprietà](best-practices-content-9.md)
+ [Non utilizzate il percorso denominato (a meno che non sia richiesto)](best-practices-content-10.md)
+ [Evita COLLECT (DISTINCT ())](best-practices-content-11.md)
+ [Preferisci la funzione delle proprietà alla ricerca delle singole proprietà quando recuperi tutti i valori delle proprietà](best-practices-content-12.md)
+ [Esegui calcoli statici all'esterno della query](best-practices-content-13.md)
+ [Input in batch che utilizzano UNWIND anziché singole istruzioni](best-practices-content-14.md)
+ [Preferisci usare custom IDs per nodo/relazione](best-practices-content-15.md)
+ [Evita di eseguire calcoli \$1id nella query](best-practices-content-16.md)
+ [Aggiornamento/unione di più nodi](best-practices-merge-multiple-nodes.md)

## Creazione di una nuova connessione dopo il failover
<a name="best-practices-opencypher-renew-connection"></a>

In caso di failover, il driver Bolt può continuare a connettersi all'istanza di scrittura precedente anziché alla nuova istanza attiva, poiché il nome DNS è stato risolto in un indirizzo IP specifico.

Per evitare questo scenario, chiudi e riconnetti l'oggetto `Driver` dopo ogni failover.

## Gestione delle connessioni per applicazioni di lunga durata
<a name="best-practices-opencypher-long-connections"></a>

Quando crei applicazioni di lunga durata, come quelle in esecuzione all'interno di container o su istanze Amazon EC2, crea un'istanza di un oggetto `Driver` una volta e poi riutilizza tale oggetto per tutta la durata dell'applicazione. L'oggetto `Driver` è thread-safe e l'overhead di inizializzazione è notevole.

## Gestione della connessione per AWS Lambda
<a name="best-practices-opencypher-lambda-connections"></a>

I driver Bolt non sono consigliati per l'uso all'interno AWS Lambda delle funzioni, a causa del sovraccarico di connessione e dei requisiti di gestione. Usa invece l'[endpoint HTTPS](access-graph-opencypher-queries.md).

# Preferire archi orientati a quelli bidirezionali nelle query
<a name="best-practices-opencypher-directed-edges"></a>

Quando Neptune esegue ottimizzazioni delle query, gli archi bidirezionali complicano la creazione di piani di query ottimali. I piani non ottimali richiedono al motore di svolgere attività non necessarie riducendo così le prestazioni.

Pertanto, è consigliabile usare archi orientati anziché bidirezionali quando possibile. Usa ad esempio:

```
MATCH p=(:airport {code: 'ANC'})-[:route]->(d) RETURN p)
```

anziché:

```
MATCH p=(:airport {code: 'ANC'})-[:route]-(d) RETURN p)
```

La maggior parte dei modelli di dati non necessita di attraversare gli archi in entrambe le direzioni, quindi è possibile migliorare le prestazioni delle query in modo significativo passando all'utilizzo di archi orientati

Se il tuo modello di dati richiede l'attraversamento di archi bidirezionali, imposta il primo nodo (a sinistra) nel modello `MATCH` come nodo con il filtro più restrittivo.

Prendiamo questo esempio: "Trova tutti gli itinerari (`routes`) da e per l'aeroporto `ANC`". Scrivi questa query per iniziare dall'aeroporto `ANC`, in questo modo:

```
MATCH p=(src:airport {code: 'ANC'})-[:route]-(d) RETURN p
```

Il motore può eseguire la quantità minima di lavoro necessaria per soddisfare la query, perché il nodo con più restrizioni è posizionato come primo nodo (lato sinistro) nel modello e può quindi ottimizzare la query.

Questo approccio è di gran lunga preferibile rispetto all'applicazione del filtro per l'aeroporto `ANC` alla fine del modello, come illustrato di seguito:

```
MATCH p=(d)-[:route]-(src:airport {code: 'ANC'}) RETURN p
```

Quando il nodo con più restrizioni non viene posizionato per primo nel modello, il motore deve eseguire attività aggiuntive perché non può ottimizzare la query e deve eseguire ulteriori ricerche per generare i risultati.

# Neptune non supporta più query simultanee in una transazione
<a name="best-practices-opencypher-multiple-queries"></a>

Sebbene il driver Bolt stesso consenta query simultanee in una transazione, Neptune non supporta l'esecuzione simultanea di più query in una transazione. Neptune richiede invece che più query in una transazione vengano eseguite in sequenza e che i risultati di ciascuna query vengano utilizzati completamente prima che venga avviata la query successiva.

L'esempio seguente mostra come usare Bolt per eseguire più query in sequenza in una transazione, affinché i risultati di ognuna di esse vengano utilizzati completamente prima dell'inizio di quella successiva:

```
final String query = "MATCH (n) RETURN n";

try (Driver driver = getDriver(HOST_BOLT, getDefaultConfig())) {
  try (Session session = driver.session(readSessionConfig)) {
    try (Transaction trx = session.beginTransaction()) {
      final Result res_1 = trx.run(query);
      Assert.assertEquals(10000, res_1.list().size());
      final Result res_2 = trx.run(query);
      Assert.assertEquals(10000, res_2.list().size());
    }
  }
}
```

# Chiusura degli oggetti driver al termine dell'utilizzo
<a name="best-practices-opencypher-close-driver"></a>

È importante chiudere il client al termine dell'utilizzo per assicurarsi che le connessioni Bolt vengano chiuse dal server e che tutte le risorse associate alle connessioni vengano rilasciate. Ciò avviene automaticamente se si chiude il driver utilizzando `driver.close()`.

Se il driver non viene chiuso correttamente, Neptune interrompe tutte le connessioni Bolt inattive dopo 20 minuti o dopo 10 giorni se si utilizza l'autenticazione IAM.

Neptune non supporta più di 1.000 connessioni Bolt simultanee. Se non chiudi esplicitamente le connessioni quando hai finito di usarle e il numero di connessioni live raggiunge il limite di 1.000, qualsiasi nuovo tentativo di connessione avrà esito negativo.

# Utilizzo di modalità di transazione esplicite per la lettura e la scrittura
<a name="best-practices-opencypher-use-explicit-txs"></a>

Quando si utilizzano transazioni con Neptune e il driver Bolt, è preferibile impostare esplicitamente la modalità di accesso per le transazioni di lettura e scrittura sui valori corretti.

## Transazioni di sola lettura
<a name="best-practices-opencypher-read-txs"></a>

Per le transazioni di sola lettura, se non si passa la configurazione della modalità di accesso appropriata durante la creazione della sessione, viene utilizzato il livello di isolamento predefinito, ovvero l'isolamento delle query di mutazione. Di conseguenza, è importante impostare la modalità di accesso a `read` in modo esplicito per le transazioni di sola lettura.

**Esempio di transazione di lettura con commit automatico:**

```
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withFetchSize(1000)
  .withDefaultAccessMode(AccessMode.READ)
  .build();
Session session = driver.session(sessionConfig);
try {
  (Add your application code here)
} catch (final Exception e) {
  throw e;
} finally {
  driver.close()
}
```

**Esempio di transazione di lettura:**

```
Driver driver = GraphDatabase.driver(url, auth, config);
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withDefaultAccessMode(AccessMode.READ)
  .build();
driver.session(sessionConfig).readTransaction(
  new TransactionWork<List<String>>() {
    @Override
    public List<String> execute(org.neo4j.driver.Transaction tx) {
      (Add your application code here)
    }
  }
);
```

In entrambi i casi, l'[isolamento `SNAPSHOT`](transactions-isolation-levels.md) viene ottenuto utilizzando la [semantica delle transazioni di sola lettura di Neptune](transactions-neptune.md#transactions-neptune-read-only).

Poiché le repliche di lettura accettano solo query di sola lettura, qualsiasi query inviata a una replica di lettura viene eseguita in base alla semantica di isolamento `SNAPSHOT`.

Non esistono letture dirty o letture non ripetibili per le transazioni di sola lettura.

## Transazioni di mutazione
<a name="best-practices-opencypher-mutation-txs"></a>

Per le query di mutazione, esistono tre diversi meccanismi per creare una transazione di scrittura, ognuno dei quali è illustrato di seguito:

**Esempio di transazione di scrittura implicita:**

```
Driver driver = GraphDatabase.driver(url, auth, config);
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withDefaultAccessMode(AccessMode.WRITE)
  .build();
driver.session(sessionConfig).writeTransaction(
  new TransactionWork<List<String>>() {
    @Override
    public List<String> execute(org.neo4j.driver.Transaction tx) {
      (Add your application code here)
    }
  }
);
```

**Esempio di transazione di scrittura con commit automatico:**

```
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withFetchSize(1000)
  .withDefaultAccessMode(AccessMode.Write)
  .build();
Session session = driver.session(sessionConfig);
try {
  (Add your application code here)
} catch (final Exception e) {
    throw e;
} finally {
    driver.close()
}
```

**Esempio di transazione di scrittura esplicita:**

```
Driver driver = GraphDatabase.driver(url, auth, config);
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withFetchSize(1000)
  .withDefaultAccessMode(AccessMode.WRITE)
  .build();
Transaction beginWriteTransaction = driver.session(sessionConfig).beginTransaction();
  (Add your application code here)
beginWriteTransaction.commit();
driver.close();
```

**Livelli di isolamento per le transazioni di scrittura**
+ Le letture effettuate come parte delle query di mutazione vengono eseguite nell'isolamento delle transazioni `READ COMMITTED`.
+ Non esistono letture dirty per le letture effettuate come parte delle query di mutazione.
+ I record e gli intervalli di record vengono bloccati durante la lettura in una query di mutazione.
+ Quando un intervallo dell'indice è stato letto da una transazione di mutazione, esiste una forte garanzia che questo intervallo non verrà modificato da alcuna transazione simultanea fino al termine della lettura.

Le query di mutazione non sono thread-safe.

Per i conflitti, consulta [Risoluzione dei conflitti tramite timeout di attesa di blocco](transactions-neptune.md#transactions-neptune-conflicts).

Per le query di mutazione i tentativi non vengono ripetuti automaticamente in caso di errore.

# Logica di ripetizione dei tentativi per le eccezioni
<a name="best-practices-opencypher-retry-logic"></a>

Per tutte le eccezioni che consentono un nuovo tentativo, in genere è preferibile utilizzare una [strategia di ripetizione dei tentativi e backoff esponenziale](https://docs.aws.amazon.com/general/latest/gr/api-retries.html) che preveda tempi di attesa progressivamente più lunghi tra un tentativo e l'altro, in modo da gestire al meglio i problemi temporanei, come gli errori `ConcurrentModificationException`. Di seguito viene illustrato un esempio di modello di ripetizione dei tentativi e backoff esponenziale:

```
public static void main() {
  try (Driver driver = getDriver(HOST_BOLT, getDefaultConfig())) {
    retriableOperation(driver, "CREATE (n {prop:'1'})")
        .withRetries(5)
        .withExponentialBackoff(true)
        .maxWaitTimeInMilliSec(500)
        .call();
  }
}

protected RetryableWrapper retriableOperation(final Driver driver, final String query){
  return new RetryableWrapper<Void>() {
    @Override
    public Void submit() {
      log.info("Performing graph Operation in a retry manner......");
      try (Session session = driver.session(writeSessionConfig)) {
        try (Transaction trx =  session.beginTransaction()) {
            trx.run(query).consume();
            trx.commit();
        }
      }
      return null;
    }

    @Override
    public boolean isRetryable(Exception e) {
      if (isCME(e)) {
        log.debug("Retrying on exception.... {}", e);
        return true;
      }
      return false;
    }

    private boolean isCME(Exception ex) {
      return ex.getMessage().contains("Operation failed due to conflicting concurrent operations");
    }
  };
}



/**
 * Wrapper which can retry on certain condition. Client can retry operation using this class.
 */
@Log4j2
@Getter
public abstract class RetryableWrapper<T> {

  private long retries = 5;
  private long maxWaitTimeInSec = 1;
  private boolean exponentialBackoff = true;

  /**
   * Override the method with custom implementation, which will be called in retryable block.
   */
  public abstract T submit() throws Exception;

  /**
   * Override with custom logic, on which exception to retry with.
   */
  public abstract boolean isRetryable(final Exception e);

  /**
   * Define the number of retries.
   *
   * @param retries -no of retries.
   */
  public RetryableWrapper<T> withRetries(final long retries) {
    this.retries = retries;
    return this;
  }

  /**
   * Max wait time before making the next call.
   *
   * @param time - max polling interval.
   */
  public RetryableWrapper<T> maxWaitTimeInMilliSec(final long time) {
    this.maxWaitTimeInSec = time;
    return this;
  }

  /**
   * ExponentialBackoff coefficient.
   */
  public RetryableWrapper<T> withExponentialBackoff(final boolean expo) {
    this.exponentialBackoff = expo;
    return this;
  }

  /**
   * Call client method which is wrapped in submit method.
   */
  public T call() throws Exception {
    int count = 0;
    Exception exceptionForMitigationPurpose = null;
    do {
      final long waitTime = exponentialBackoff ? Math.min(getWaitTimeExp(retries), maxWaitTimeInSec) : 0;
      try {
          return submit();
      } catch (Exception e) {
        exceptionForMitigationPurpose = e;
        if (isRetryable(e) && count < retries) {
          Thread.sleep(waitTime);
          log.debug("Retrying on exception attempt - {} on exception cause - {}", count, e.getMessage());
        } else if (!isRetryable(e)) {
          log.error(e.getMessage());
          throw new RuntimeException(e);
        }
      }
    } while (++count < retries);

    throw new IOException(String.format(
          "Retry was unsuccessful.... attempts %d. Hence throwing exception " + "back to the caller...", count),
          exceptionForMitigationPurpose);
  }

  /*
   * Returns the next wait interval, in milliseconds, using an exponential backoff
   * algorithm.
   */
  private long getWaitTimeExp(final long retryCount) {
    if (0 == retryCount) {
      return 0;
    }
    return ((long) Math.pow(2, retryCount) * 100L);
  }
}
```

# Imposta più proprietà contemporaneamente utilizzando una singola clausola SET
<a name="best-practices-content-0"></a>

 Invece di utilizzare più clausole SET per impostare singole proprietà, utilizzate una mappa per impostare più proprietà per un'entità contemporaneamente. 

 È possibile utilizzare: 

```
MATCH (n:SomeLabel {`~id`: 'id1'})
SET n += {property1 : 'value1',
property2 : 'value2',
property3 : 'value3'}
```

 Invece di: 

```
MATCH (n:SomeLabel {`~id`: 'id1'})
SET n.property1 = 'value1'
SET n.property2 = 'value2'
SET n.property3 = 'value3'
```

 La clausola SET accetta una singola proprietà o una mappa. Se si aggiornano più proprietà su una singola entità, l'utilizzo di una singola clausola SET con una mappa consente di eseguire gli aggiornamenti in un'unica operazione anziché in più operazioni, che possono essere eseguite in modo più efficiente. 

## Utilizzate la clausola SET per rimuovere più proprietà contemporaneamente
<a name="best-practices-content-1"></a>

 Quando si utilizza il linguaggio OpenCypher, REMOVE viene utilizzato per rimuovere le proprietà da un'entità. In Neptune, ogni proprietà rimossa richiede un'operazione separata, che aggiunge la latenza delle query. Puoi invece usare SET con una mappa su cui impostare tutti i valori delle proprietà`null`, che in Neptune equivale a rimuovere le proprietà. Neptune avrà prestazioni migliori quando è necessario rimuovere più proprietà su una singola entità. 

Utilizza:

```
WITH {prop1: null, prop2: null, prop3: null} as propertiesToRemove 
MATCH (n) 
SET n += propertiesToRemove
```

Invece di:

```
MATCH (n) 
REMOVE n.prop1, n.prop2, n.prop3
```

# Utilizzare query parametrizzate
<a name="best-practices-content-2"></a>

 Si consiglia di utilizzare sempre query con parametri quando si esegue una query utilizzando OpenCypher. Il motore di query può sfruttare query parametrizzate ripetute per funzionalità come la cache del piano di query, in cui l'invocazione ripetuta della stessa struttura parametrizzata con parametri diversi può sfruttare i piani memorizzati nella cache. Il piano di query generato per le query con parametri viene memorizzato nella cache e riutilizzato solo quando viene completato entro 100 ms e i tipi di parametro sono NUMBER, BOOLEAN o STRING. 

Utilizza:

```
MATCH (n:foo) WHERE id(n) = $id RETURN n
```

Con parametri:

```
parameters={"id": "first"}
parameters={"id": "second"}
parameters={"id": "third"}
```

Invece di:

```
MATCH (n:foo) WHERE id(n) = "first" RETURN n
MATCH (n:foo) WHERE id(n) = "second" RETURN n
MATCH (n:foo) WHERE id(n) = "third" RETURN n
```

# Usa mappe appiattite anziché mappe annidate nella clausola UNWIND
<a name="best-practices-content-3"></a>

 Una struttura profondamente annidata può limitare la capacità del motore di query di generare un piano di query ottimale. Per alleviare parzialmente questo problema, i seguenti schemi definiti creeranno piani ottimali per i seguenti scenari: 
+  Scenario 1: UNWIND con un elenco di valori letterali cifrati, che include NUMBER, STRING e BOOLEAN. 
+  Scenario 2: UNWIND con un elenco di mappe appiattite, che include solo valori letterali cifrati (NUMBER, STRING, BOOLEAN). 

 Quando si scrive una query contenente la clausola UNWIND, utilizzare la raccomandazione precedente per migliorare le prestazioni. 

Esempio di scenario 1:

```
UNWIND $ids as x
MATCH(t:ticket {`~id`: x})
```

Con parametri:

```
parameters={
  "ids": [1, 2, 3]
}
```

 Un esempio per lo Scenario 2 consiste nel generare un elenco di nodi da CREARE o UNIRE. Invece di emettere più istruzioni, utilizzate lo schema seguente per definire le proprietà come un insieme di mappe appiattite: 

```
UNWIND $props as p
CREATE(t:ticket {title: p.title, severity:p.severity})
```

Con parametri:

```
parameters={
  "props": [
    {"title": "food poisoning", "severity": "2"},
    {"title": "Simone is in office", "severity": "3"}
  ]
}
```

Invece di oggetti nodali annidati come:

```
UNWIND $nodes as n
CREATE(t:ticket n.properties)
```

Con parametri:

```
parameters={
  "nodes": [
    {"id": "ticket1", "properties": {"title": "food poisoning", "severity": "2"}},
    {"id": "ticket2", "properties": {"title": "Simone is in office", "severity": "3"}}
  ]
}
```

# Posiziona nodi più restrittivi sul lato sinistro nelle espressioni VLP (Variable-Length Path)
<a name="best-practices-content-4"></a>

 Nelle query VLP (Variable-Length Path), il motore di query ottimizza la valutazione scegliendo di avviare l'attraversamento sul lato sinistro o destro dell'espressione. La decisione si basa sulla cardinalità dei modelli sul lato sinistro e destro. La cardinalità è il numero di nodi che corrispondono allo schema specificato. 
+  Se il pattern giusto ha una cardinalità pari a uno, il lato destro sarà il punto di partenza. 
+  Se il lato sinistro e quello destro hanno una cardinalità pari a uno, l'espansione viene controllata su entrambi i lati e inizia dal lato con l'espansione minore. L'espansione è il numero di bordi in uscita o in entrata per il nodo a sinistra e il nodo sul lato destro dell'espressione VLP. Questa parte dell'ottimizzazione viene utilizzata solo se la relazione VLP è unidirezionale e viene fornito il tipo di relazione. 
+  Altrimenti, il lato sinistro sarà il punto di partenza. 

 Per una catena di espressioni VLP, questa ottimizzazione può essere applicata solo alla prima espressione. Le altre VLPs vengono valutate a partire dal lato sinistro. Ad esempio, lasciamo che la cardinalità di (a), (b) sia una e la cardinalità di (c) sia maggiore di uno. 
+  `(a)-[*1..]->(c)`: La valutazione inizia con (a). 
+  `(c)-[*1..]->(a)`: La valutazione inizia con (a). 
+  `(a)-[*1..]-(c)`: La valutazione inizia con (a). 
+  `(c)-[*1..]-(a)`: La valutazione inizia con (a). 

 Ora lasciamo che gli spigoli in entrata di (a) siano due e gli spigoli in uscita di (a) siano tre, gli spigoli in entrata di (b) siano quattro e gli spigoli in uscita di (b) siano cinque. 
+  `(a)-[*1..]->(b)`: La valutazione inizia con (a) poiché gli spigoli in uscita di (a) sono inferiori agli spigoli in entrata di (b). 
+  `(a)<-[*1..]-(b)`: La valutazione inizia con (a) poiché i bordi in entrata di (a) sono inferiori agli spigoli in uscita di (b). 

 Come regola generale, posizionate il pattern più restrittivo sul lato sinistro di un'espressione VLP. 

# Evita controlli ridondanti delle etichette dei nodi utilizzando nomi di relazione granulari
<a name="best-practices-content-5"></a>

 Quando si ottimizzano le prestazioni, l'utilizzo di etichette di relazione esclusive per i modelli di nodi consente di rimuovere il filtraggio delle etichette sui nodi. Consideriamo un modello grafico in cui la relazione `likes` viene utilizzata solo per definire una relazione tra due `person` nodi. Potremmo scrivere la seguente query per trovare questo modello: 

```
MATCH (n:person)-[:likes]->(m:person)
RETURN n, m
```

 Il controllo dell'`person`etichetta su n e m è ridondante, poiché abbiamo definito la relazione in modo che appaia solo quando entrambi sono dello stesso tipo. `person` Per ottimizzare le prestazioni, possiamo scrivere la query come segue: 

```
MATCH (n)-[:likes]->(m)
RETURN n, m
```

 Questo modello può essere applicato anche quando le proprietà sono esclusive di un'etichetta a singolo nodo. Supponiamo che solo `person` i nodi abbiano la proprietà`email`, quindi verificare che l'etichetta del nodo corrisponda `person` è ridondante. Scrivere questa query come: 

```
MATCH (n:person)
WHERE n.email = 'xxx@gmail.com'
RETURN n
```

 È meno efficiente che scrivere questa query come: 

```
MATCH (n)
WHERE n.email = 'xxx@gmail.com'
RETURN n
```

 È consigliabile adottare questo modello solo quando le prestazioni sono importanti e il processo di modellazione deve essere sottoposto a controlli per garantire che le etichette dei bordi non vengano riutilizzate per modelli che coinvolgono altre etichette di nodi. Se successivamente si introduce una `email` proprietà su un'altra etichetta di nodo`company`, ad esempio, i risultati saranno diversi tra queste due versioni della query. 

# Specificate le etichette dei bordi, ove possibile
<a name="best-practices-content-6"></a>

 Si consiglia di fornire un'etichetta sul bordo, ove possibile, quando si specifica uno spigolo in un motivo. Considerate la seguente query di esempio, che viene utilizzata per collegare tutte le persone che vivono in una città con tutte le persone che hanno visitato quella città. 

```
MATCH (person)-->(city {country: "US"})-->(anotherPerson)
RETURN person, anotherPerson
```

 Se il tuo modello grafico collega le persone a nodi diversi dalle semplici città utilizzando etichette laterali multiple, non specificando l'etichetta finale, Neptune dovrà valutare percorsi aggiuntivi che verranno successivamente scartati. Nella query precedente, poiché non è stata fornita un'etichetta laterale, il motore prima esegue più lavoro e poi filtra i valori per ottenere il risultato corretto. Una versione migliore della query precedente potrebbe essere: 

```
MATCH (person)-[:livesIn]->(city {country: "US"})-[:visitedBy]->(anotherPerson)
RETURN person, anotherPerson
```

 Ciò non solo aiuta nella valutazione, ma consente al pianificatore di query di creare piani migliori. Puoi anche combinare questa best practice con controlli ridondanti delle etichette dei nodi per rimuovere il segno di spunta sull'etichetta della città e scrivere la query come: 

```
MATCH (person)-[:livesIn]->({country: "US"})-[:visitedBy]->(anotherPerson)
RETURN person, anotherPerson
```

# Evita di utilizzare la clausola WITH quando possibile
<a name="best-practices-content-7"></a>

 La clausola WITH in OpenCypher funge da limite in cui tutto ciò che viene eseguito prima di essere eseguito, quindi i valori risultanti vengono passati alle parti rimanenti della query. La clausola WITH è necessaria quando si richiede un'aggregazione provvisoria o si desidera limitare il numero di risultati, ma a parte questo si dovrebbe cercare di evitare l'uso della clausola WITH. La guida generale è quella di rimuovere queste semplici clausole WITH (senza aggregazione, ordine o limite) per consentire al pianificatore di query di lavorare sull'intera query per creare un piano globale ottimale. Ad esempio, supponiamo di aver scritto una query per restituire tutte le persone che vivono in: `India` 

```
MATCH (person)-[:lives_in]->(city)
WITH person, city
MATCH (city)-[:part_of]->(country {name: 'India'})
RETURN collect(person) AS result
```

 Nella versione precedente, la clausola WITH limita la posizione del pattern `(city)-[:part_of]->(country {name: 'India'})` (che è più restrittivo) precedente. `(person)-[:lives_in]->(city)` Ciò rende il piano non ottimale. Un'ottimizzazione di questa query consisterebbe nel rimuovere la clausola WITH e lasciare che il pianificatore calcoli il piano migliore. 

```
MATCH (person)-[:lives_in]->(city)
MATCH (city)-[:part_of]->(country {name: 'India'})
RETURN collect(person) AS result
```

# Posiziona filtri restrittivi il più presto possibile nella query
<a name="best-practices-content-8"></a>

 In tutti gli scenari, il posizionamento precoce dei filtri nella query aiuta a ridurre le soluzioni intermedie che un piano di query deve prendere in considerazione. Ciò significa che sono necessarie meno memoria e meno risorse di elaborazione per eseguire la query. 

 L'esempio seguente aiuta a comprendere questi impatti. Supponiamo di scrivere una query per restituire tutte le persone che vivono in`India`. Una versione della query potrebbe essere: 

```
MATCH (n)-[:lives_in]->(city)-[:part_of]->(country)
WITH country, collect(n.firstName + " "  + n.lastName) AS result
WHERE country.name = 'India'
RETURN result
```

 La versione precedente della query non è il modo migliore per ottenere questo caso d'uso. Il filtro `country.name = 'India'` viene visualizzato più avanti nel modello di interrogazione. Innanzitutto raccoglierà tutte le persone e il luogo in cui vivono, le raggrupperà per paese, quindi filtrerà solo per il gruppo di`country.name = India`. Il modo ottimale per eseguire query solo per le persone che vivono in zona `India` e quindi eseguire l'aggregazione di raccolta. 

```
MATCH (n)-[:lives_in]->(city)-[:part_of]->(country)
WHERE country.name = 'India'
RETURN collect(n.firstName + " "  + n.lastName) AS result
```

 Una regola generale consiste nell'inserire un filtro il prima possibile dopo l'introduzione della variabile. 

# Controlla esplicitamente se esistono delle proprietà
<a name="best-practices-content-9"></a>

 In base alla semantica di OpenCypher, quando si accede a una proprietà è equivalente a un join opzionale e deve conservare tutte le righe anche se la proprietà non esiste. Se in base allo schema grafico si sa che una particolare proprietà esisterà sempre per quell'entità, la verifica esplicita dell'esistenza di tale proprietà consente al motore di query di creare piani ottimali e migliorare le prestazioni. 

 Prendiamo in considerazione un modello grafico in cui i nodi di tipo hanno `person` sempre una proprietà`name`. Invece di fare questo: 

```
MATCH (n:person)
RETURN n.name
```

 Verifica esplicitamente l'esistenza della proprietà nella query con un controllo IS NOT NULL: 

```
MATCH (n:person)
WHERE n.name IS NOT NULL
RETURN n.name
```

# Non utilizzate il percorso denominato (a meno che non sia richiesto)
<a name="best-practices-content-10"></a>

 Il percorso denominato in una query comporta sempre un costo aggiuntivo, che può comportare penalità in termini di maggiore latenza e utilizzo della memoria. Considera la query seguente: 

```
MATCH p = (n)-[:commentedOn]->(m)
WITH p, m, n, n.score + m.score as total
WHERE total > 100 
MATCH (m)-[:commentedON]->(o)
WITH p, m, n, distinct(o) as o1
RETURN p, m.name, n.name, o1.name
```

 Nella query precedente, supponendo di voler conoscere solo le proprietà dei nodi, l'uso del percorso «p» non è necessario. Specificando il percorso denominato come variabile, l'operazione di aggregazione che utilizza DISTINCT diventerà costosa sia in termini di tempo che di utilizzo della memoria. Una versione più ottimizzata della query precedente potrebbe essere: 

```
MATCH (n)-[:commentedOn]->(m)
WITH m, n, n.score + m.score as total
WHERE total > 100 
MATCH (m)-[:commentedON]->(o)
WITH m, n, distinct(o) as o1
RETURN m.name, n.name, o1.name
```

# Evita COLLECT (DISTINCT ())
<a name="best-practices-content-11"></a>

**Nota**  
A partire dalla versione [1.4.7.0](engine-releases-1.4.7.0.md) del motore, questa riscrittura consigliata non è più necessaria.

 COLLECT (DISTINCT ()) viene utilizzato ogni volta che si deve formare una lista contenente valori distinti. COLLECT è una funzione di aggregazione e il raggruppamento viene eseguito in base a chiavi aggiuntive proiettate nella stessa istruzione. Quando viene utilizzato distinct, l'input viene suddiviso in più blocchi in cui ogni blocco indica un gruppo da ridurre. Le prestazioni saranno influenzate dall'aumento del numero di gruppi. In Neptune, è molto più efficiente eseguire DISTINCT prima dell' collecting/forming elenco. Ciò consente di eseguire il raggruppamento direttamente sulle chiavi di raggruppamento per l'intero blocco. 

 Considera la query seguente: 

```
MATCH (n:Person)-[:commented_on]->(p:Post)
WITH n, collect(distinct(p.post_id)) as post_list
RETURN n, post_list
```

 Un modo più ottimale per scrivere questa query è: 

```
MATCH (n:Person)-[:commented_on]->(p:Post)
WITH DISTINCT n, p.post_id as postId
WITH n, collect(postId) as post_list
RETURN n, post_list
```

# Preferisci la funzione delle proprietà alla ricerca delle singole proprietà quando recuperi tutti i valori delle proprietà
<a name="best-practices-content-12"></a>

 La `properties()` funzione viene utilizzata per restituire una mappa contenente tutte le proprietà di un'entità ed è molto più efficiente rispetto alla restituzione delle proprietà singolarmente. 

 Supponendo che i `Person` nodi contengano 5 proprietà `firstName``lastName`,`age`,`dept`,, e`company`, sarebbe preferibile la seguente query: 

```
MATCH (n:Person)
WHERE n.dept = 'AWS'
RETURN properties(n) as personDetails
```

 Invece di usare: 

```
MATCH (n:Person)
WHERE n.dept = 'AWS'
RETURN n.firstName, n.lastName, n.age, n.dept, n.company
    
=== OR ===
    
MATCH (n:Person)
WHERE n.dept = 'AWS'
RETURN {firstName: n.firstName, lastName: n.lastName, age: n.age, 
department: n.dept, company: n.company} as personDetails
```

# Esegui calcoli statici all'esterno della query
<a name="best-practices-content-13"></a>

 Si consiglia di risolvere calcoli statici ( mathematical/string operazioni semplici) sul lato client. Considerate questo esempio in cui volete trovare tutte le persone che hanno un anno o meno rispetto all'autore: 

```
MATCH (m:Message)-[:HAS_CREATOR]->(p:person)
WHERE p.age <= ($age + 1)
RETURN m
```

 Qui, `$age` viene inserito nella query tramite parametri e quindi aggiunto a un valore fisso. Questo valore viene quindi confrontato con`p.age`. Invece, un approccio migliore sarebbe eseguire l'aggiunta sul lato client e passare il valore calcolato come parametro \$1ageplusone. Questo aiuta il motore di query a creare piani ottimizzati ed evita il calcolo statico per ogni riga in entrata. Seguendo queste linee guida, una versione più efficiente della query sarebbe: 

```
MATCH (m:Message)-[:HAS_CREATOR]->(p:person)
WHERE p.age <= $ageplusone
RETURN m
```

# Input in batch che utilizzano UNWIND anziché singole istruzioni
<a name="best-practices-content-14"></a>

 Ogni volta che è necessario eseguire la stessa query per input diversi, invece di eseguire una query per input, sarebbe molto più efficiente eseguire una query per un batch di input. 

 Se desideri eseguire l'unione su un set di nodi, un'opzione è eseguire una query di unione per input: 

```
MERGE (n:Person {`~id`: $id})
SET n.name = $name, n.age = $age, n.employer = $employer
```

 Con parametri: 

```
params = {id: '1', name: 'john', age: 25, employer: 'Amazon'}
```

 La query precedente deve essere eseguita per ogni input. Sebbene questo approccio funzioni, potrebbe richiedere l'esecuzione di molte query per un ampio set di input. In questo scenario, il batch può aiutare a ridurre il numero di query eseguite sul server e a migliorare la velocità effettiva complessiva. 

 Utilizza lo schema seguente: 

```
UNWIND $persons as person
MERGE (n:Person {`~id`: person.id})
SET n += person
```

 Con parametri: 

```
params = {persons: [{id: '1', name: 'john', age: 25, employer: 'Amazon'}, 
{id: '2', name: 'jack', age: 28, employer: 'Amazon'},
{id: '3', name: 'alice', age: 24, employer: 'Amazon'}...]}
```

 Si consiglia di sperimentare lotti di dimensioni diverse per determinare quale sia la soluzione migliore per il carico di lavoro. 

# Preferisci usare custom IDs per nodo/relazione
<a name="best-practices-content-15"></a>

 Neptune consente agli utenti di IDs assegnare in modo esplicito nodi e relazioni. L'ID deve essere univoco a livello globale nel set di dati e deterministico per essere utile. Un ID deterministico può essere utilizzato come meccanismo di ricerca o filtro proprio come le proprietà; tuttavia, l'utilizzo di un ID è molto più ottimizzato dal punto di vista dell'esecuzione delle query rispetto all'utilizzo delle proprietà. L'utilizzo di custom - IDs 
+  Le proprietà possono essere nulle per un'entità esistente, ma l'ID deve esistere. Ciò consente al motore di query di utilizzare un join ottimizzato durante l'esecuzione. 
+  Quando vengono eseguite query di mutazione simultanee, le possibilità di [modifiche simultanee delle eccezioni](https://docs.aws.amazon.com//neptune/latest/userguide/transactions-exceptions.html) (CMEs) si riducono significativamente quando IDs vengono utilizzate per accedere ai nodi perché si verificano meno blocchi IDs rispetto alle proprietà grazie alla loro unicità forzata. 
+  L'utilizzo IDs evita la possibilità di creare dati duplicati poiché Neptune impone l'unicità, a differenza delle proprietà. IDs 

 Il seguente esempio di query utilizza un ID personalizzato: 

**Nota**  
 La proprietà `~id` viene utilizzata per specificare l'ID, mentre `id` viene semplicemente memorizzata come qualsiasi altra proprietà. 

```
CREATE (n:Person {`~id`: '1', name: 'alice'})
```

 Senza utilizzare un ID personalizzato: 

```
CREATE (n:Person {id: '1', name: 'alice'})
```

 Se si utilizza quest'ultimo meccanismo, non viene applicata l'unicità e in seguito è possibile eseguire la query: 

```
CREATE (n:Person {id: '1', name: 'john'})
```

 Questo crea un secondo nodo con `id=1` named`john`. In questo scenario, ora avresti due nodi con`id=1`, ognuno con un nome diverso, (alice e john). 

# Evita di eseguire calcoli \$1id nella query
<a name="best-practices-content-16"></a>

 Quando usi custom IDs nelle query, esegui sempre calcoli statici all'esterno delle query e fornisci questi valori nei parametri. Quando vengono forniti valori statici, il motore è in grado di ottimizzare meglio le ricerche ed evitare la scansione e il filtraggio di questi valori. 

 Se desideri creare bordi tra i nodi esistenti nel database, un'opzione potrebbe essere: 

```
UNWIND $sections as section
MATCH (s:Section {`~id`: 'Sec-' + section.id})
MERGE (s)-[:IS_PART_OF]->(g:Group {`~id`: 'g1'})
```

 Con parametri: 

```
parameters={sections: [{id: '1'}, {id: '2'}]}
```

 Nella query precedente, il `id` valore della sezione viene calcolato nella query. Poiché il calcolo è dinamico, il motore non può incorporare staticamente gli id in linea e finisce per scansionare tutti i nodi della sezione. Il motore esegue quindi il post-filtraggio per i nodi richiesti. Questa operazione può essere costosa se nel database sono presenti molti nodi di sezione. 

 Un modo migliore per raggiungere questo obiettivo consiste nell'inserire gli ID che vengono passati al database: `Sec-` 

```
UNWIND $sections as section
MATCH (s:Section {`~id`: section.id})
MERGE (s)-[:IS_PART_OF]->(g:Group {`~id`: 'g1'})
```

 Con parametri: 

```
parameters={sections: [{id: 'Sec-1'}, {id: 'Sec-2'}]}
```

# Aggiornamento/unione di più nodi
<a name="best-practices-merge-multiple-nodes"></a>

 Durante l'esecuzione `MERGE` o `CREATE` le interrogazioni su più nodi, si consiglia di utilizzare una clausola `UNWIND` in combinazione con una singola MERGE/CREATE clausola anziché utilizzare una clausola per ogni nodo. MERGE/CREATE Le query che utilizzano una clausola per un nodo portano a un piano di esecuzione inefficiente a causa del fatto che ogni riga richiede un'ottimizzazione. Ciò comporta che la maggior parte del tempo di esecuzione della query venga impiegato nell'elaborazione statica anziché nell'aggiornamento effettivo. 

 Una clausola per nodo non è ottimale in quanto non si adatta all'aumentare del numero di nodi: 

```
MERGE (p1:Person {name: 'NameA'})
ON CREATE SET p1 += {prop1: 'prop1V1', prop2: 'prop2V1'}
MERGE (p2:Person {name: 'NameB'})
ON CREATE SET p2 += {prop1: 'prop1V2', prop2: 'prop2V2'}
MERGE (p3:Person {name: 'NameC'})
ON CREATE SET p3 += {prop1: 'prop1V3', prop2: 'prop1V3'}
```

 L'utilizzo di una `UNWIND` in combinazione con una MERGE/CREATE clausola consente lo stesso comportamento ma un piano di esecuzione più ottimale. Con questo in mente, la query modificata avrebbe il seguente aspetto: 

```
## If not using custom id for nodes/relationship
UNWIND [{name: 'NameA', prop1: 'prop1V1', prop2: 'prop2V1'}, {name: 'NameB', prop1: 'prop1V2', prop2: 'prop2V2'}, {name: 'NameC', prop1: 'prop1V3', prop2: 'prop1V3'}] AS props
MERGE (p:Person {name: props.name})
ON CREATE SET p = props

## If using custom id for nodes/relationship
UNWIND [{`~id`: '1', 'name': 'NameA', 'prop1: 'prop1V1', prop2: 'prop2V1'}, {`~id`: '2', name: 'NameB', prop1: 'prop1V2', prop2: 'prop2V2'}, {`~id`: '3', name: 'NameC', prop1: 'prop1V3', prop2: 'prop1V3'}] AS props
MERGE (p:Person {`~id`: props.id})
ON CREATE SET p = removeKeyFromMap(props, '~id')
```