2012-10-18 16 views
26

Ho una tabella di circa 100 utenti e ho anche una matrice di ID utente. Quello che volevo fare è mostrare tutti gli utenti che non fanno parte di questo array di ID utente. Quando faccio qualcosa del genereProblema durante il recupero di record con array vuoto

User.where('id NOT IN (?)', [9, 2, 3, 4]) 

Restituisce correttamente i record in cui l'id dell'utente non appartiene a tale array. Tuttavia, se tale matrice è vuota in questo modo

User.where('id NOT IN (?)', []) 

Esso non restituisce alcun utente indietro e la query SQL assomiglia a questo

SELECT "users".* FROM "users" WHERE (id NOT IN (NULL)) 

Qualcuno sa perché questo accade o questo potrebbe essere un bug? Sto usando Rails 3.2.5 con PostgreSQL.

+2

Che assomiglia a un problema di Rails o forse un problema di gemma 'Pg'; tratta un array vuoto come 'NULL'. Molto strano - e cattivo comportamento. Puoi testare una dichiarazione preparata con la gemma 'Pg' direttamente per vedere se tratta i parametri dell'array in quel modo o se è il livello Rails/ActiveRecord a farlo? –

+3

@CraigRinger: Sarà un problema Rails/ActiveRecord e non un problema 'pg'. AR gestisce i segnaposti stessi. –

+2

@CraigRinger: a volte le persone AR sono sorprendenti in modo anomalo in SQL, lo fanno apposta. Inserisci qui l'immagine "figlio, mi delude". –

risposta

21

ActiveRecord (almeno 3.2.1) tratta gli array vuoti come NULL. I segnaposto in una chiamata where vengono gestiti da sanitize_sql. Se si traccia attraverso il codice per un po ', si arriva a replace_bind_variables:

def replace_bind_variables(statement, values) #:nodoc: 
    raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size) 
    bound = values.dup 
    c = connection 
    statement.gsub('?') { quote_bound_value(bound.shift, c) } 
end 

e poi quote_bound_value:

def quote_bound_value(value, c = connection) #:nodoc: 
    if value.respond_to?(:map) && !value.acts_like?(:string) 
    if value.respond_to?(:empty?) && value.empty? 
     c.quote(nil) 
    else 
     value.map { |v| c.quote(v) }.join(',') 
    end 
    else 
    c.quote(value) 
    end 
end 

un array vuoto in grado di soddisfare tutte le quattro condizioni per arrivare a c.quote(nil) e questo è da dove proviene il tuo NULL. Tutta la logica speciale che conduce a c.quote(nil) indica che si tratta di un comportamento intenzionale.

dicendo in (o NOT IN) con una lista vuota:

where c in() 

dovrebbe produrre un errore di SQL così forse la gente AR stanno cercando di impedire che, tranquillamente girando così male SQL in c in (null). Si noti che nessuno di questi:

select ... from t where c in (null); 
select ... from t where c not in (null); 

dovrebbe mai produrre alcun risultato a causa del comportamento di NULL di SQL. Questo è un classico errore newbie e le persone AR dovrebbero davvero saperlo meglio.

Preferirei anch'io un'eccezione: dirmi che sto per lanciare un proiettile per i piedi sarebbe molto più amichevole di una semplice pistola.


Sintesi:

  1. Questa "array vuoto significa NULL" comportamento è intenzionale.
  2. Non dovresti mai provare mai where('c in (?)', []) o where('c not in (?)', []) poiché nessuna delle due affermazioni ha molto senso.
  3. Aggiorna il tuo codice Ruby per verificare gli array vuoti e fai tutto il necessario per ottenere i risultati che ti aspetti.
+2

Sì, la cosa sana sarebbe * certamente * un'eccezione qui. –

+1

@CraigRinger: Io, come al solito, incolpare MySQL :) –

+0

Ho capito ora perché i miei test stanno fallendo. :) –

30

In Rails 4 è possibile utilizzare User.where.not(id: []) che fornirà il risultato corretto.Produce:

SELECT "users".* FROM "users" WHERE (1 = 1) 

Purtroppo User.where('id NOT IN (?)', []) dovrebbe essere equivalente, ma non lo è. Ti dà ancora il risultato sbagliato:

SELECT "users".* FROM "users" WHERE (id NOT IN (NULL)) 

Riferimenti:

+0

Su una nota vagamente correlata, User.where (: id => [[], [3]]) fornisce sql non valido: "SELECT" users'. * FROM 'users' WHERE' users' .id' IN (, 3) " –

6
User.where('id NOT IN (?)', ids+[0]) 
record attivo involucro
+1

Si prega di considerare l'aggiunta di un commento chiarificatore sul *** perché *** questa potrebbe essere la risposta corretta. –

+0

@DavidL: ciò garantisce che l'array non sia vuoto. Questo presume anche che non ci sia id con valore = 0 (in Rails è spesso il caso). – bragboy

2

Uso di ruby:

User.where.not(id: []) 

Questo gestisce la questione array vuoto per voi.

1

Non so se questo è il problema richiesto, ma sono venuto qui per trovare tutti i record con un attributo di array vuoto (serializzato). Ho risolto per Rails 5.0 in questo modo:

User.where(my_array_attribute: nil) 

O per l'inverso:

User.where.not(my_array_attribute: nil) 
Problemi correlati