Fractionner la valeur d'un champ à deux

125

J'ai un champ de table membernamequi contient à la fois le nom et le prénom des utilisateurs. Est - il possible de diviser les champs en 2 memberfirst, memberlast?

Tous les enregistrements ont ce format "Prénom Nom" (sans guillemets et espace entre les deux).

tsiger
la source
6
"Tous les enregistrements ont ce format" Prénom Nom "(sans guillemets et espace entre les deux)." ... miraculeusement ... S'il vous plaît , n'oubliez pas les gens comme moi lorsque vous prenez des décisions sur la base de données. Trop souvent, des sites Web me disent que mon nom de famille contient un caractère illégal (sic) ... :(
Stijn de Witt
@StijndeWitt Vous avez raison en général, mais il semble que cette base de données ne contienne pas votre nom, du moins pas dans sa forme officielle. Dans mon pays, les noms de famille sont écrits en premier, donc je serais également "discriminé" dans ce tableau de données. Il suffit de voir ceci ->
Dávid Horváth

Réponses:

226

Malheureusement, MySQL ne propose pas de fonction de chaîne fractionnée. Cependant, vous pouvez créer une fonction définie par l'utilisateur pour cela, telle que celle décrite dans l'article suivant:

Avec cette fonction:

DELIMITER $$

CREATE FUNCTION SPLIT_STR(
  x VARCHAR(255),
  delim VARCHAR(12),
  pos INT
)
RETURNS VARCHAR(255) DETERMINISTIC
BEGIN 
    RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos),
       LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1),
       delim, '');
END$$

DELIMITER ;

vous pourriez créer votre requête comme suit:

SELECT SPLIT_STR(membername, ' ', 1) as memberfirst,
       SPLIT_STR(membername, ' ', 2) as memberlast
FROM   users;

Si vous préférez ne pas utiliser de fonction définie par l'utilisateur et que la requête ne vous dérange pas d'être un peu plus verbeuse, vous pouvez également effectuer les opérations suivantes:

SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(membername, ' ', 1), ' ', -1) as memberfirst,
       SUBSTRING_INDEX(SUBSTRING_INDEX(membername, ' ', 2), ' ', -1) as memberlast
FROM   users;
Daniel Vassallo
la source
Excellente solution pour ce problème!
Bergkamp
vous ne pouvez toujours pas utiliser IN comme un "tableau de valeurs" à partir de cette opération de fractionnement?
Miguel
3
Votre utilisation du LENGTHmulti - octet est-elle sûre? "LENGTH (str): renvoie la longueur de la chaîne str, mesurée en octets. Un caractère multi-octets compte comme plusieurs octets. Cela signifie que pour une chaîne contenant cinq caractères de 2 octets, LENGTH () renvoie 10, tandis que CHAR_LENGTH () renvoie 5. "
Erk
Cela ne fonctionnera pas correctement avec des caractères multi-octets / utf8, comme @Erk l'a mentionné. Seule la solution simple avec les deux instructions SUBSTRING_INDEX fonctionne avec utf8 / multibyte
Michael
LENGTH (), LOCATE () ou tout ce qui repose sur un décompte de position échouera avec des caractères multi-octets.
Michael
68

Variante SELECT (ne crée pas de fonction définie par l'utilisateur):

SELECT IF(
        LOCATE(' ', `membername`) > 0,
        SUBSTRING(`membername`, 1, LOCATE(' ', `membername`) - 1),
        `membername`
    ) AS memberfirst,
    IF(
        LOCATE(' ', `membername`) > 0,
        SUBSTRING(`membername`, LOCATE(' ', `membername`) + 1),
        NULL
    ) AS memberlast
FROM `user`;

Cette approche prend également en charge:

  • les valeurs du nom de membre sans espace : il ajoutera la chaîne entière à memberfirst et définit memberlast à NULL.
  • les valeurs de nom de membre qui ont plusieurs espaces : il ajoutera tout avant le premier espace à memberfirst et le reste (y compris les espaces supplémentaires) à memberlast.

La version UPDATE serait:

UPDATE `user` SET
    `memberfirst` = IF(
        LOCATE(' ', `membername`) > 0,
        SUBSTRING(`membername`, 1, LOCATE(' ', `membername`) - 1),
        `membername`
    ),
    `memberlast` = IF(
        LOCATE(' ', `membername`) > 0,
        SUBSTRING(`membername`, LOCATE(' ', `membername`) + 1),
        NULL
    );
smhg
la source
Il serait également utile de voir comment couper uniquement le dernier mot pour le nom de famille, et tous les non derniers pour le prénom, par exemple: Mary A. Smith, qui sont les types que je dois gérer dans une ancienne table de base de données réparer. Je vais voir si je peux le comprendre et publier le résultat, sinon, si vous pouvez également publier cette option, cela rendrait votre réponse complète.
Lizardx
comment pouvons-nous le convertir en entier puisque le nom de membre est varchar .. laissez membre d'abord être de type int. Cela fonctionnera-t-il si j'utilise directement cast ()?
infinitywarior
Vous méritez une médaille, monsieur.
rpajaziti le
23

Il semble que les réponses existantes sont trop compliquées ou pas une réponse stricte à la question particulière.

Je pense que la réponse simple est la requête suivante:

SELECT
    SUBSTRING_INDEX(`membername`, ' ', 1) AS `memberfirst`,
    SUBSTRING_INDEX(`membername`, ' ', -1) AS `memberlast`
;

Je pense qu'il n'est pas nécessaire de traiter des noms de plus de deux mots dans cette situation particulière. Si vous voulez le faire correctement, le fractionnement peut être très difficile voire impossible dans certains cas:

  • Johann Sebastian Bach
  • Johann Wolfgang von Goethe
  • Edgar Allan Poe
  • Jakob Ludwig Felix Mendelssohn-Bartholdy
  • Petőfi Sándor
  • 澤黒

Dans une base de données bien conçue, les noms humains doivent être stockés à la fois en partie et en totalité. Ce n'est pas toujours possible, bien sûr.

Dávid Horváth
la source
20

Si vous prévoyez de le faire dans le cadre d'une requête, veuillez ne pas le faire (a) . Sérieusement, c'est un tueur de performances. Il peut y avoir des situations où vous ne vous souciez pas des performances (comme des tâches de migration ponctuelles pour diviser les champs permettant de meilleures performances à l'avenir) mais, si vous le faites régulièrement pour autre chose qu'une base de données mickey-mouse, vous gaspillent des ressources.

Si jamais vous ne devez traiter qu'une partie d'une colonne d'une manière ou d'une autre, la conception de votre base de données est défectueuse. Cela peut très bien fonctionner sur un carnet d'adresses personnel ou une application de recette ou sur l'une des myriades d'autres petites bases de données, mais il ne sera pas évolutif pour des systèmes «réels».

Stockez les composants du nom dans des colonnes séparées. Il est presque invariablement beaucoup plus rapide de joindre des colonnes avec une simple concaténation (lorsque vous avez besoin du nom complet) que de les séparer avec une recherche de caractères.

Si, pour une raison quelconque, vous ne pouvez pas diviser le champ, mettez au moins les colonnes supplémentaires et utilisez un déclencheur d'insertion / mise à jour pour les remplir. Bien que ce ne soit pas 3NF, cela garantira que les données sont toujours cohérentes et accélérera massivement vos requêtes. Vous pouvez également vous assurer que les colonnes supplémentaires sont en minuscules (et indexées si vous les recherchez) en même temps afin de ne pas avoir à manipuler les problèmes de cas.

Et, si vous ne pouvez même pas ajouter les colonnes et les déclencheurs, sachez (et informez votre client, si c'est pour un client) qu'il n'est pas évolutif.


(a) Bien sûr, si votre intention est d'utiliser cette requête pour corriger le schéma afin que les noms soient placés dans des colonnes distinctes dans la table plutôt que dans la requête, je considérerais cela comme une utilisation valide. Mais je le répète, le faire dans la requête n'est pas vraiment une bonne idée.

paxdiablo
la source
4
Parfois, vous devez le faire. Fe J'en ai besoin dans un script de migration, donc je me fiche des performances.
Matthieu Napoli
@dfmiller, oui, je l'ai fait, d'où ma réponse raisonnée et détaillée, et merci pour votre intérêt. Si vous avez un problème spécifique avec quelque chose que j'ai écrit, signalez-le et je verrai s'il peut être amélioré. Votre commentaire actuel est à peu près inutile pour améliorer la situation, si c'était effectivement votre intention. Ou peut-être que vous aimez simplement lancer des commentaires aléatoires sur le net, c'est difficile à dire :-) Je maintiens la réponse, bien sûr, l'accès aux sous-colonnes n'est pas évolutif et est presque toujours une mauvaise idée, à moins qu'il ne soit utilisé dans le but de effectivement fixer un accès sous-colonnes.
paxdiablo
3
La question est de savoir comment diviser la colonne unique en 2, puis vous répondez en disant "Ne faites pas cela" et ensuite expliquez pourquoi elles devraient être divisées. Votre premier paragraphe donne l'impression que vous êtes en faveur ou que vous les gardez dans une colonne, mais les autres paragraphes disent le contraire.
dfmiller le
@dfmiller, j'ai peut-être mal compris la question, je ne sais pas si la séparation devait être faite dans la requête ou dans la table. J'espère que j'ai clarifié la réponse pour la rendre plus claire.
paxdiablo
Bien mieux. Je n'ai jamais envisagé d'utiliser une requête de sélection sauf pour mettre à jour la base de données. Ce serait une idée terrible.
dfmiller le
7

utilisez ceci

SELECT SUBSTRING_INDEX(SUBSTRING_INDEX( `membername` , ' ', 2 ),' ',1) AS b, 
SUBSTRING_INDEX(SUBSTRING_INDEX( `membername` , ' ', -1 ),' ',2) AS c FROM `users` WHERE `userid`='1'
Karthik
la source
Cela récupérera la première et la dernière sous-chaîne délimitée par des espaces du champ, ce qui ne fonctionne pas dans toutes les circonstances. Par exemple, si le champ du nom est «Lilly von Schtupp», alors vous obtiendrez «Lilly», «Schtupp» comme prénom, nom de famille.
John Franklin
5

Je ne répondais pas exactement à la question, mais face au même problème, j'ai fini par faire ceci:

UPDATE people_exit SET last_name = SUBSTRING_INDEX(fullname,' ',-1)
UPDATE people_exit SET middle_name = TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(fullname,last_name,1),' ',-2))
UPDATE people_exit SET middle_name = '' WHERE CHAR_LENGTH(middle_name)>3 
UPDATE people_exit SET first_name = SUBSTRING_INDEX(fullname,concat(middle_name,' ',last_name),1)
UPDATE people_exit SET first_name = middle_name WHERE first_name = ''
UPDATE people_exit SET middle_name = '' WHERE first_name = middle_name
grand brochet
la source
4

Dans MySQL, cette option fonctionne:

SELECT Substring(nameandsurname, 1, Locate(' ', nameandsurname) - 1) AS 
       firstname, 
       Substring(nameandsurname, Locate(' ', nameandsurname) + 1)    AS lastname 
FROM   emp  
Irakli Kardava
la source
pour avoir pris le reste de la corde dans le deuxième champ
M. Faraz
3

Le seul cas où vous voudrez peut-être une telle fonction est une requête UPDATE qui modifiera votre table pour stocker le prénom et le nom dans des champs séparés.

La conception de la base de données doit suivre certaines règles, et la normalisation de la base de données est parmi les plus importantes

Votre bon sens
la source
Commentaire inutile car c'est exactement ce que l'affiche a demandé; également inexact car il est possible que vous deviez diviser une chaîne un million de fois pour une meilleure normalisation. Je ne sais pas pourquoi ni comment cela a été voté.
daticon
Utiliser des index sur des champs fractionnés est à peu près aussi impossible que de faire de MySQL un broyeur de feuilles, mais cela n'empêchera pas les gens de s'enquérir. Bonne réponse - la base de données DEVRAIT refléter les données, pas les spécifications de votre broyeur de feuilles.
HoldOffHunger
2

J'avais une colonne où le prénom et le nom étaient tous les deux dans une colonne. Le prénom et le nom étaient séparés par une virgule. Le code ci-dessous a fonctionné. Il n'y a AUCUNE vérification / correction d'erreur. Juste une scission stupide. Utilisé phpMyAdmin pour exécuter l'instruction SQL.

UPDATE tblAuthorList SET AuthorFirst = SUBSTRING_INDEX(AuthorLast,',',-1) , AuthorLast = SUBSTRING_INDEX(AuthorLast,',',1);

13.2.10 Syntaxe UPDATE

Steve R.
la source
1

Cela prend smhg d'ici et curt's from Last index d'une sous-chaîne donnée dans MySQL et les combine. Ceci est pour mysql, tout ce dont j'avais besoin était d'obtenir une répartition décente du nom en prénom nom_nom avec le nom de famille un seul mot, le prénom tout avant ce mot unique, où le nom pourrait être nul, 1 mot, 2 mots ou plus de 2 mots. Ie: Null; Marie; Mary Smith; Mary A. Smith; Mary Sue Ellen Smith;

Donc, si nom est un mot ou nul, last_name est nul. Si nom est> 1 mot, last_name est le dernier mot et first_name tous les mots avant le dernier mot.

Notez que j'ai déjà coupé des trucs comme Joe Smith Jr.; Joe Smith Esq. et ainsi de suite, manuellement, ce qui était douloureux, bien sûr, mais c'était assez petit pour le faire, vous voulez donc vous assurer de bien regarder les données dans le champ de nom avant de décider de la méthode à utiliser.

Notez que cela réduit également le résultat, de sorte que vous ne vous retrouvez pas avec des espaces devant ou après les noms.

Je publie juste ceci pour d'autres qui pourraient chercher ce dont j'avais besoin sur Google. Cela fonctionne, bien sûr, testez-le d'abord avec le select.

C'est une chose ponctuelle, donc je me fiche de l'efficacité.

SELECT TRIM( 
    IF(
        LOCATE(' ', `name`) > 0,
        LEFT(`name`, LENGTH(`name`) - LOCATE(' ', REVERSE(`name`))),
        `name`
    ) 
) AS first_name,
TRIM( 
    IF(
        LOCATE(' ', `name`) > 0,
        SUBSTRING_INDEX(`name`, ' ', -1) ,
        NULL
    ) 
) AS last_name
FROM `users`;


UPDATE `users` SET
`first_name` = TRIM( 
    IF(
        LOCATE(' ', `name`) > 0,
        LEFT(`name`, LENGTH(`name`) - LOCATE(' ', REVERSE(`name`))),
        `name`
    ) 
),
`last_name` = TRIM( 
    IF(
        LOCATE(' ', `name`) > 0,
        SUBSTRING_INDEX(`name`, ' ', -1) ,
        NULL
    ) 
);
Lizardx
la source
0

Méthode que j'avais utilisée pour diviser first_name en first_name et last_name lorsque les données arrivaient toutes dans le champ first_name. Ceci ne mettra que le dernier mot dans le champ du nom, donc "john phillips sousa" sera le prénom "john phillips" et le nom de famille "sousa". Cela évite également d'écraser les enregistrements qui ont déjà été corrigés.

set last_name=trim(SUBSTRING_INDEX(first_name, ' ', -1)), first_name=trim(SUBSTRING(first_name,1,length(first_name) - length(SUBSTRING_INDEX(first_name, ' ', -1)))) where list_id='$List_ID' and length(first_name)>0 and length(trim(last_name))=0
TheSatinKnight
la source
0
UPDATE `salary_generation_tbl` SET
    `modified_by` = IF(
        LOCATE('$', `other_salary_string`) > 0,
        SUBSTRING(`other_salary_string`, 1, LOCATE('$', `other_salary_string`) - 1),
        `other_salary_string`
    ),
    `other_salary` = IF(
        LOCATE('$', `other_salary_string`) > 0,
        SUBSTRING(`other_salary_string`, LOCATE('$', `other_salary_string`) + 1),
        NULL
    );
Aniket
la source
-3

mysql 5.4 fournit une fonction de partage native:

SPLIT_STR(<column>, '<delimiter>', <index>)
emfi
la source
1
Pouvez-vous fournir un lien vers la documentation. Une recherche sur dev.mysql.com arrive à sec. La section 12.5 a une suggestion de la communauté dans les commentaires pour cette fonction.
DRaehal