แยกค่าจากเขตข้อมูลหนึ่งเป็นสอง


125

ฉันมีช่องตารางmembernameซึ่งมีทั้งนามสกุลและชื่อผู้ใช้ มันเป็นไปได้ที่จะแยกเหล่านั้นลงใน 2 สาขาmemberfirst, memberlast?

ระเบียนทั้งหมดมีรูปแบบ "Firstname Lastname" (ไม่มีเครื่องหมายอัญประกาศและเว้นวรรค)


6
"ระเบียนทั้งหมดมีรูปแบบ" Firstname Lastname "(ไม่มีเครื่องหมายอัญประกาศและเว้นวรรค)" ... อย่างน่าอัศจรรย์ ... โปรดกรุณาอย่าลืมเกี่ยวกับคนอย่างผมเมื่อการตัดสินใจฐานข้อมูล บ่อยครั้งที่ฉันได้รับเว็บไซต์ที่บอกว่านามสกุลของฉันมีตัวอักษร (sic) ที่ผิดกฎหมาย ... :(
Stijn de Witt

@StijndeWitt โดยทั่วไปคุณพูดถูก แต่ดูเหมือนว่าฐานข้อมูลนี้ไม่มีชื่อของคุณอย่างน้อยก็ไม่ได้อยู่ในรูปแบบทางการ นามสกุลในประเทศของฉันถูกเขียนขึ้นเป็นอันดับแรกดังนั้นฉันจะถูก "เลือกปฏิบัติ" ในตารางข้อมูลนี้ด้วย เห็นแค่นี้ ->
DávidHorváth

คำตอบ:


226

น่าเสียดายที่ MySQL ไม่มีฟังก์ชันสตริงแยก อย่างไรก็ตามคุณสามารถสร้างฟังก์ชันที่ผู้ใช้กำหนดสำหรับสิ่งนี้เช่นฟังก์ชันที่อธิบายไว้ในบทความต่อไปนี้:

ด้วยฟังก์ชั่นนั้น:

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 ;

คุณจะสามารถสร้างแบบสอบถามของคุณได้ดังนี้:

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

หากคุณไม่ต้องการใช้ฟังก์ชันที่ผู้ใช้กำหนดเองและคุณไม่คิดว่าคิวรีจะมีรายละเอียดมากขึ้นคุณสามารถทำสิ่งต่อไปนี้ได้:

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

ทางออกที่ดีสำหรับปัญหานี้!
Bergkamp

คุณยังไม่สามารถใช้ IN เป็น "อาร์เรย์ของค่า" จากการดำเนินการแยกนั้นได้หรือไม่?
มิเกล

3
การใช้งานLENGTHหลายไบต์ของคุณ ปลอดภัยหรือไม่? "LENGTH (str): ส่งกลับความยาวของสตริง str ที่วัดเป็นไบต์อักขระแบบหลายไบต์จะนับเป็นหลายไบต์ซึ่งหมายความว่าสำหรับสตริงที่มีอักขระ 2 ไบต์ห้าตัว LENGTH () จะส่งกลับ 10 ในขณะที่ CHAR_LENGTH () จะส่งกลับ 5."
เอิร์ก

สิ่งนี้จะทำงานไม่ถูกต้องเมื่อจัดการกับอักขระหลายไบต์ / utf8 ดังที่ @Erk กล่าวถึง เฉพาะวิธีแก้ปัญหาง่ายๆด้วยคำสั่ง SUBSTRING_INDEX สองรายการที่ใช้ได้กับ utf8 / multibyte
Michael

LENGTH (), LOCATE () หรือสิ่งใดก็ตามที่อาศัยการนับตำแหน่งจะล้มเหลวด้วยอักขระหลายไบต์
Michael

68

เลือกตัวแปร (ไม่ได้สร้างฟังก์ชันที่ผู้ใช้กำหนด):

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`;

แนวทางนี้ยังดูแล:

  • membername ค่าไม่ต้องเว้นวรรค : มันจะเพิ่มสตริงทั้งเพื่อ memberfirst และชุด memberlast โมฆะ
  • membername ค่าที่มีช่องว่างหลาย : มันจะเพิ่มทุกอย่างก่อนที่พื้นที่แรกที่จะ memberfirst และที่เหลือ (รวมถึงช่องว่างเพิ่มเติม) เพื่อ memberlast

เวอร์ชัน UPDATE จะเป็น:

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
    );

นอกจากนี้ยังมีประโยชน์ในการดูวิธีการตัดคำสุดท้ายสำหรับนามสกุลและคำที่ไม่ใช่นามสกุลทั้งหมดเช่น Mary A. Smith ซึ่งเป็นประเภทที่ฉันต้องจัดการกับสิ่งนี้ในตารางฐานข้อมูลเก่า แก้ไข ฉันจะดูว่าฉันสามารถคิดออกและโพสต์ผลลัพธ์ได้หรือไม่ถ้าไม่หากคุณสามารถโพสต์ตัวเลือกนั้นด้วยนั่นจะทำให้คำตอบของคุณสมบูรณ์
Lizardx

เราจะแคสมันให้เป็นจำนวนเต็มได้อย่างไรเนื่องจากชื่อสมาชิกคือ varchar .. จะใช้งานได้หรือไม่ถ้าฉันใช้ cast () โดยตรง
infinitywarior

คุณสมควรได้รับเหรียญ
rpajaziti

23

ดูเหมือนว่าคำตอบที่มีอยู่จะซับซ้อนหรือไม่ใช่คำตอบที่เข้มงวดสำหรับคำถามนั้น ๆ

ฉันคิดว่าคำตอบง่ายๆคือคำถามต่อไปนี้:

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

ฉันคิดว่ามันไม่จำเป็นต้องจัดการกับชื่อมากกว่าสองคำในสถานการณ์นี้ หากคุณต้องการทำอย่างถูกต้องการแยกอาจทำได้ยากมากหรือเป็นไปไม่ได้ในบางกรณี:

  • โยฮันน์เซบาสเตียนบา
  • โยฮันน์โวล์ฟกังฟอนเกอเธ่
  • เอ็ดการ์อัลลันโพ
  • Jakob Ludwig Felix Mendelssohn-Bartholdy
  • Petőfi Sándor
  • 澤黒

ในฐานข้อมูลที่ออกแบบอย่างถูกต้องควรจัดเก็บชื่อมนุษย์ไว้ทั้งในส่วนและทั้งหมด สิ่งนี้ไม่สามารถทำได้เสมอไปแน่นอน


20

ถ้าแผนของคุณคือการทำเช่นนี้เป็นส่วนหนึ่งของแบบสอบถามโปรดอย่าทำอย่างนั้น(ก) อย่างจริงจังมันเป็นนักฆ่าประสิทธิภาพ อาจมีสถานการณ์ที่คุณไม่สนใจเกี่ยวกับประสิทธิภาพ (เช่นงานโยกย้ายครั้งเดียวเพื่อแบ่งเขตข้อมูลเพื่อให้ได้ประสิทธิภาพที่ดีขึ้นในอนาคต) แต่ถ้าคุณทำสิ่งนี้เป็นประจำสำหรับสิ่งอื่นที่ไม่ใช่ฐานข้อมูลมิกกี้เมาส์คุณ เป็นการสิ้นเปลืองทรัพยากร

หากคุณเคยพบว่าตัวเองต้องประมวลผลเพียงบางส่วนของคอลัมน์ไม่ทางใดก็ทางหนึ่งการออกแบบฐานข้อมูลของคุณมีข้อบกพร่อง อาจใช้งานได้ดีกับสมุดที่อยู่ที่บ้านหรือแอปพลิเคชันสูตรอาหารหรือฐานข้อมูลขนาดเล็กอื่น ๆ มากมาย แต่จะไม่สามารถปรับขนาดให้เป็นระบบ "จริง" ได้

จัดเก็บส่วนประกอบของชื่อในคอลัมน์ที่แยกจากกัน เกือบจะเร็วกว่ามากในการรวมคอลัมน์เข้าด้วยกันโดยใช้การเรียงต่อกันอย่างง่าย (เมื่อคุณต้องการชื่อเต็ม) มากกว่าการแยกออกจากกันด้วยการค้นหาอักขระ

หากด้วยเหตุผลบางประการคุณไม่สามารถแยกฟิลด์ได้อย่างน้อยก็ใส่คอลัมน์พิเศษและใช้ทริกเกอร์แทรก / อัปเดตเพื่อเติมข้อมูล แม้ว่าจะไม่ใช่ 3NF แต่จะรับประกันได้ว่าข้อมูลจะยังคงมีความสม่ำเสมอและจะเร่งความเร็วในการสืบค้นของคุณอย่างมาก นอกจากนี้คุณยังสามารถตรวจสอบให้แน่ใจว่าคอลัมน์พิเศษนั้นมีขนาดเล็กลง (และจัดทำดัชนีหากคุณกำลังค้นหา) ในเวลาเดียวกันเพื่อที่จะได้ไม่ต้องวุ่นวายกับปัญหาทางคดี

และหากคุณไม่สามารถเพิ่มคอลัมน์และทริกเกอร์ได้โปรดระวัง (และแจ้งให้ลูกค้าของคุณทราบหากเป็นของลูกค้า) ว่าไม่สามารถปรับขนาดได้


(a)แน่นอนว่าหากคุณตั้งใจจะใช้แบบสอบถามนี้เพื่อแก้ไขสคีมาเพื่อให้ชื่อถูกวางไว้ในคอลัมน์แยกต่างหากในตารางแทนที่จะเป็นแบบสอบถามฉันจะถือว่าเป็นการใช้งานที่ถูกต้อง แต่ขอย้ำอีกครั้งว่าการทำในแบบสอบถามไม่ใช่ความคิดที่ดีจริงๆ


4
บางครั้งคุณต้องทำ ฉันต้องการมันในสคริปต์การย้ายข้อมูลดังนั้นฉันจึงไม่สนใจเรื่องการแสดง
Matthieu Napoli

@dfmiller ใช่ฉันทำดังนั้นคำตอบที่มีเหตุผลและรายละเอียดของฉันและขอบคุณที่ให้ความสนใจ หากคุณมีปัญหาเฉพาะเกี่ยวกับสิ่งที่ฉันเขียนชี้ให้เห็นแล้วฉันจะดูว่าสามารถปรับปรุงได้หรือไม่ ความคิดเห็นปัจจุบันของคุณค่อนข้างไร้ประโยชน์ในการปรับปรุงสถานการณ์หากนั่นเป็นความตั้งใจของคุณ หรือบางทีคุณอาจจะชอบแสดงความคิดเห็นแบบสุ่มบนเน็ตมันยากที่จะบอก :-) ฉันยืนตามคำตอบแน่นอนว่าการเข้าถึงคอลัมน์ย่อยไม่สามารถปรับขนาดได้และมักจะเป็นความคิดที่ไม่ดีเว้นแต่จะใช้เพื่อจุดประสงค์ จริงการแก้ไขการเข้าถึงย่อยคอลัมน์
paxdiablo

3
คำถามคือจะแบ่งคอลัมน์เดี่ยวออกเป็น 2 คอลัมน์ได้อย่างไรแล้วตอบกลับโดยพูดว่า "อย่าทำอย่างนั้น" จากนั้นจึงอธิบายว่าเหตุใดจึงควรแยกคอลัมน์ ย่อหน้าแรกของคุณดูเหมือนว่าคุณกำลังโต้เถียงเพื่อประโยชน์หรือเก็บไว้เป็นคอลัมน์เดียว แต่อีกย่อหน้าหนึ่งพูดตรงกันข้าม
dfmiller

@dfmiller บางทีฉันอาจเข้าใจคำถามผิดตอนนี้ฉันไม่แน่ใจว่าตอนนี้ต้องแยกในแบบสอบถามหรือตาราง ฉันได้ชี้แจงคำตอบแล้วหวังว่าจะทำให้ชัดเจนขึ้น
paxdiablo

ดีขึ้นมาก. ฉันไม่เคยพิจารณาใช้แบบสอบถามแบบเลือกยกเว้นเพื่ออัปเดตฐานข้อมูล นั่นจะเป็นความคิดที่ผิดมหันต์
dfmiller

7

ใช้สิ่งนี้

SELECT SUBSTRING_INDEX(SUBSTRING_INDEX( `membername` , ' ', 2 ),' ',1) AS b, 
SUBSTRING_INDEX(SUBSTRING_INDEX( `membername` , ' ', -1 ),' ',2) AS c FROM `users` WHERE `userid`='1'

สิ่งนี้จะดึงสตริงย่อยที่คั่นด้วยช่องว่างแรกและช่องสุดท้ายจากฟิลด์ซึ่งใช้ไม่ได้ในทุกสถานการณ์ ตัวอย่างเช่นหากช่องชื่อคือ "Lilly von Schtupp" คุณจะได้รับ "Lilly", "Schtupp" เป็นชื่อนามสกุล
John Franklin

5

ไม่ได้ตอบคำถามอย่างตรงไปตรงมา แต่ต้องเผชิญกับปัญหาเดียวกันกับที่ฉันทำสิ่งนี้:

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

4

ใน MySQL สิ่งนี้ใช้งานตัวเลือกนี้:

SELECT Substring(nameandsurname, 1, Locate(' ', nameandsurname) - 1) AS 
       firstname, 
       Substring(nameandsurname, Locate(' ', nameandsurname) + 1)    AS lastname 
FROM   emp  

สำหรับการพักสตริงในสนามที่สอง
M. Faraz

3

กรณีเดียวที่คุณอาจต้องการฟังก์ชันดังกล่าวคือแบบสอบถาม UPDATE ซึ่งจะเปลี่ยนตารางของคุณเพื่อเก็บ Firstname และ Lastname ไว้ในฟิลด์แยกกัน

การออกแบบฐานข้อมูลต้องเป็นไปตามกฎเกณฑ์บางประการและการปรับมาตรฐานฐานข้อมูลเป็นหนึ่งในสิ่งที่สำคัญที่สุด


ความคิดเห็นที่ไม่จำเป็นเพราะนี่คือสิ่งที่ผู้โพสต์ขอ; ยังไม่ถูกต้องเนื่องจากมีหลายล้านครั้งที่คุณอาจต้องแยกสตริงเพื่อให้เป็นมาตรฐานที่ดีที่สุด ไม่แน่ใจว่าเหตุใดจึงได้รับการโหวตขึ้น
daticon

การใช้ดัชนีในช่องแยกเป็นเรื่องที่เป็นไปไม่ได้เท่ากับการทำให้ MySQL กลายเป็น Leaf Mulcher แต่จะไม่หยุดไม่ให้ผู้คนถามเกี่ยวกับเรื่องนี้ คำตอบที่ดี - ฐานข้อมูลควรสะท้อนข้อมูลไม่ใช่ข้อมูลจำเพาะของ Leaf mulcher ของคุณ
HoldOffHunger

2

ฉันมีคอลัมน์ที่ชื่อและนามสกุลอยู่ในคอลัมน์เดียว ชื่อและนามสกุลถูกคั่นด้วยลูกน้ำ รหัสด้านล่างใช้งานได้ ไม่มีการตรวจสอบ / แก้ไขข้อผิดพลาด เพียงแค่แยกใบ้ ใช้ phpMyAdmin เพื่อดำเนินการคำสั่ง SQL

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

13.2.10 อัปเดตไวยากรณ์


1

สิ่งนี้ใช้เวลา smhg จากที่นี่และ curt มาจากดัชนีสุดท้ายของสตริงย่อยที่กำหนดใน MySQLและรวมเข้าด้วยกัน นี่คือ mysql สิ่งที่ฉันต้องการคือแยกชื่อที่เหมาะสมเป็น first_name last_name โดยนามสกุลเป็นคำเดียวชื่อแรกทุกอย่างก่อนคำเดียวโดยที่ชื่ออาจเป็นโมฆะ 1 คำ 2 คำหรือ มากกว่า 2 คำ ได้แก่ Null; แมรี่; แมรี่สมิ ธ ; แมรี่เอ. สมิ ธ ; แมรี่ซูเอลเลนสมิ ธ ;

ดังนั้นถ้า name เป็นคำเดียวหรือ null last_name จะเป็นโมฆะ ถ้าชื่อ> 1 คำ last_name คือคำสุดท้ายและ first_name ทุกคำก่อนคำสุดท้าย

โปรดทราบว่าฉันได้ตัดสิ่งต่างๆเช่น Joe Smith Jr. แล้ว โจสมิ ธ เอสไควร์ และอื่น ๆ ด้วยตนเองซึ่งแน่นอนว่าเจ็บปวด แต่ก็เล็กพอที่จะทำเช่นนั้นดังนั้นคุณต้องตรวจสอบให้แน่ใจว่าได้ดูข้อมูลในช่องชื่ออย่างแท้จริงก่อนที่จะตัดสินใจว่าจะใช้วิธีใด

โปรดทราบว่าสิ่งนี้จะตัดทอนผลลัพธ์ด้วยดังนั้นคุณจึงไม่ต้องเว้นวรรคหน้าหรือหลังชื่อ

ฉันแค่โพสต์สิ่งนี้สำหรับคนอื่น ๆ ที่อาจใช้ Google มาที่นี่เพื่อค้นหาสิ่งที่ฉันต้องการ วิธีนี้ได้ผลแน่นอนให้ทดสอบด้วยการเลือกก่อน

เป็นครั้งเดียวดังนั้นฉันไม่สนใจเรื่องประสิทธิภาพ

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
    ) 
);

0

วิธีที่ฉันใช้ในการแยก first_name เป็น first_name และ last_name เมื่อข้อมูลมาถึงทั้งหมดในฟิลด์ first_name ซึ่งจะใส่เฉพาะคำสุดท้ายในช่องนามสกุลดังนั้น "john phillips sousa" จะเป็น "john phillips" ชื่อและนามสกุล "sousa" นอกจากนี้ยังหลีกเลี่ยงการเขียนทับบันทึกใด ๆ ที่ได้รับการแก้ไขแล้ว

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

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
    );

-3

mysql 5.4 มีฟังก์ชันแยกเนทีฟ:

SPLIT_STR(<column>, '<delimiter>', <index>)

1
คุณสามารถให้ลิงค์ไปยังเอกสารประกอบ การค้นหา dev.mysql.com แห้งขึ้น ส่วน 12.5 มีข้อเสนอแนะของชุมชนในความคิดเห็นสำหรับฟังก์ชันนี้
DRaehal
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.