2010-03-12 4 views
6

Ok voici mon problème:paramètres de recherche en option dans la requête SQL et les lignes avec des valeurs nulles

Avant de commencer la description, permettez-moi de vous dire que je l'ai googlé jusqu'à beaucoup et je poste cette question pour une bonne solution :)

Je construis un service de repos sur WCF pour obtenir UserProfiles ... l'utilisateur peut filtrer UserProfiles en donnant quelque chose comme UserProfiles? location = Londres

maintenant j'ai la méthode suivante

GetUserProfiles(string firstname, string lastname, string age, string location) 

la chaîne de requête sql que j'ai créée est: select firstname, lastname, .... from profiles where (firstName like '%{firstname}%') AND (lastName like '%{lastName}%') .... et ainsi de suite toutes les variables étant remplacées par un formateur de chaîne.

problème est qu'il filtre toute ligne ayant prenom, nom, l'âge ou l'emplacement ayant une valeur nulle ....

faire quelque chose comme (firstName like '%{firstName}%' OR firstName IS NULL) serait fastidieux et la déclaration deviendrait unmaintanable! (dans cet exemple il n'y a que 4 arguments, mais dans ma méthode actuelle il y en a 10)

Quelle serait la meilleure solution pour cela? .... Comment cette situation est-elle habituellement gérée?

Base de données utilisée: MySql

+0

Avez-vous envisagé d'utiliser LINQ-to-SQL? –

+0

non je n'ai pas encore considéré linq à sql ... –

Répondre

1

Vous pouvez utiliser COALESCE(value,...)

coalesce(firstName, '') like '%{firstname}%' 
+1

'COALESCE()' est essentiellement une recherche O (n) car aucun index ne sera utilisé. – cletus

2

Il est une propriété fondamentale de NULL que lorsque vous comparez à quoi que ce soit, y comprisNULL — retourne false, ce qui explique pourquoi ce que vous faites ne fonctionne pas.

Donc la première question à laquelle il faut répondre est la suivante: pourquoi voulez-vous qu'une ligne avec NULLlastname soit retournée quand elle entre lastname de "smith"? Peut-être que vous voulez dire que le prénom ou le nom de famille doit correspondre à être retourné, auquel cas vous n'exécutez pas la bonne requête. La solution la plus naïve est:

SELECT firstname, lastname, .... 
FROM profiles 
WHERE IFNULL(firstName LIKE'%{firstname}%' 
OR lastName LIKE '%{lastName}%' 

Maintenant, cela fonctionnera pour plusieurs centaines et peut-être plusieurs milliers de lignes, mais ne sera pas l'échelle au-delà de cela pour plusieurs raisons:

  1. OR s sont généralement affligeant en termes de performance. Évitez-les si possible. Si vous regardez les applications de base de données écrites par des programmeurs expérimentés, vous ne trouverez probablement pas une seule condition OR (excepté ceux qui font du prosélytisme sur les vertus de OR en ayant écrit une application guestbook qui a 3 utilisateurs et 2 hits par mois) ;
  2. Faire LIKE avec un % à l'avant signifiera qu'aucun index ne sera poursuivi.

Il existe plusieurs solutions pour (2). Probablement le plus simple dans MySQL est d'utiliser full text searching sur les tables MyISAM. Il ne trouvera pas de correspondance comme "Johannes" si vous tapez "han" mais cela suffit généralement.

Souvent, vous gérez les conditions OR en utilisant UNION ou (de préférence) UNION ALL sur plusieurs requêtes. Par exemple:

SELECT firstname, lastname, .... 
FROM profiles 
WHERE firstName LIKE'%{firstname}%' 
AND lastName LIKE '%{lastName}%' 
UNION ALL 
SELECT firstname, lastname, .... 
FROM profiles 
WHERE firstName LIKE'%{firstname}%' 
AND lastname IS NULL 
UNION ALL 
SELECT firstname, lastname, .... 
FROM profiles 
WHERE firstName IS NULL 
AND lastName LIKE '%{lastName}%' 
UNION ALL 
SELECT firstname, lastname, .... 
FROM profiles 
WHERE firstName IS NULL 
AND lastName IS NULL 

est assez grosse et laide, mais UNION ALL échelles très bien (en ignorant le % au début des LIKE critères). Il s'agit essentiellement de concaténer (dans ce cas) quatre requêtes. Un UNION fera un DISTINCT implicite sur les lignes de résultats mais nous savons qu'il n'y aura pas de chevauchement ici parce que nous varions en vérifiant NULL.

Une autre possibilité est de ne pas traiter NULL comme quelque chose que vous voulez rechercher. C'est une solution bien meilleure et plus intuitive (imho). Si quelqu'un tape un nom de famille "Smith", voulez-vous vraiment des lignes avec un nom de famille NULL? Ou voulez-vous qu'ils apparaissent parce que vous pourriez avoir une correspondance sur le prénom? Si oui, vous voulez une requête légèrement différente. Par exemple:

SELECT firstname, lastname, .... 
FROM profiles 
WHERE id IN (
    SELECT id 
    FROM profiles 
    WHERE firstName LIKE'%{firstname}%' 
    UNION ALL 
    SELECT id 
    FROM profiles 
    WHERE lastName LIKE '%{lastName}%' 
) 
+0

performance avec tant de syndicats ?? –

+0

@glenn: comme je l'ai dit, la variante des quatre unions va être pour la plupart inutile ** dans ce cas ** parce que les critères n'utiliseront pas d'index de toute façon mais si vous utilisiez des index, alors c'est une histoire différente. Le plus gros problème est de savoir si vous exécutez la bonne requête et si votre modèle de données est sous-optimal ou non. – cletus

0

Vous pouvez utiliser COALESCE:

select firstname, lastname, .... 
from profiles 
where (coalesce(firstName, '') like '%{firstname}%') 
AND (coalesce(lastName, '') like '%{lastName}%') 
+1

'COALESCE()' est essentiellement une recherche O (n) car aucun index ne sera utilisé. – cletus

+1

Oui, évidemment, mais vous ne pouvez pas utiliser les index de toute façon à cause du 'LIKE '% foo%'', donc ça semble être un point discutable ici. L'index peut être utilisé avec 'LIKE 'foo%' mais pas s'il y a un caractère générique au début. Si la performance est un problème ici, un index de texte intégral devrait être utilisé. –

0

Vous pouvez toujours désavouer les valeurs nulles en premier lieu - après tout, tout le monde doit être vivant pour un certain nombre d'années positives et être quelque part géographiquement.

Si vous devez absolument autoriser les valeurs nulles, alors utilisez COALESCE, comme d'autres l'ont suggéré, ou éventuellement IFNULL qui est spécifique à MySQL et peut être légèrement plus optimal puisqu'il prend exactement 2 paramètres.

Questions connexes