2011-11-05 39 views
7

Trước tiên, tôi xin lỗi nếu điều này đã được hỏi trước đó - thực sự tôi chắc chắn nó có, nhưng tôi không thể tìm thấy nó/không thể tìm ra để tìm kiếm nó.Cách nhận ID alpha-số tiếp theo dựa trên giá trị hiện tại từ MySQL

Tôi cần tạo id tham chiếu nhanh duy nhất, dựa trên tên công ty. Ví dụ:

 
Company Name    Reference 
Smiths Joinery    smit0001 
Smith and Jones Consulting smit0002 
Smithsons Carpets   smit0003 

Tất cả sẽ được lưu trữ trong cột varchar trong bảng MySQL. Dữ liệu sẽ được thu thập, thoát và chèn như 'HTML -> PHP -> MySQL'. ID phải ở định dạng được mô tả ở trên, bốn chữ cái, sau đó là bốn chữ số (ban đầu ít nhất - khi tôi đạt đến smit9999 nó sẽ chỉ tràn qua thành 5 chữ số).

Tôi có thể xử lý việc tạo ra 4 chữ cái từ tên công ty, tôi sẽ chỉ cần bước qua tên cho đến khi tôi đã thu thập 4 ký tự alpha và strtolower() - nhưng sau đó tôi cần có số tiếp theo.

Cách tốt nhất/dễ nhất để thực hiện việc này là gì, để khả năng loại bỏ trùng lặp?

Tại thời điểm tôi đang nghĩ:

$fourLetters = 'smit'; 
$query = "SELECT `company_ref` 
      FROM `companies` 
      WHERE 
      `company_ref` LIKE '$fourLetters%' 
      ORDER BY `company_ref` DESC 
      LIMIT 1"; 
$last = mysqli_fetch_assoc(mysqli_query($link, $query)); 
$newNum = ((int) ltrim(substr($last['company_ref'],4),'0')) + 1; 
$newRef = $fourLetters.str_pad($newNum, 4, '0', STR_PAD_LEFT); 

Nhưng tôi có thể thấy điều này gây ra một vấn đề nếu hai người dùng cố gắng nhập tên công ty mà sẽ cho kết quả trong cùng một ID cùng một lúc. Tôi sẽ sử dụng một chỉ mục duy nhất trên cột, vì vậy nó sẽ không dẫn đến trùng lặp trong cơ sở dữ liệu, nhưng nó vẫn sẽ gây ra một vấn đề.

Bất cứ ai có thể nghĩ ra một cách để MySQL làm việc này cho tôi khi tôi chèn, thay vì tính toán nó trong PHP trước? Lưu ý rằng mã thực tế sẽ là OO và sẽ xử lý lỗi vv - Tôi chỉ đang tìm kiếm những suy nghĩ về việc liệu có cách nào tốt hơn để thực hiện tác vụ cụ thể này hay không, nó còn nhiều hơn về SQL so với bất kỳ thứ gì khác.

EDIT

Tôi nghĩ rằng @ đề nghị sử dụng một kích hoạt MySQL EmmanuelN của có thể là cách để xử lý này, nhưng:

  • Tôi không đủ tốt với MySQL, đặc biệt là gây nên, để có được điều này để làm việc, và muốn một ví dụ từng bước tạo, thêm và sử dụng một kích hoạt.
  • Tôi vẫn không chắc liệu điều này sẽ loại bỏ khả năng tạo ra hai ID giống hệt nhau hay không. Xem what happens if two rows are inserted at the same time that result in the trigger running simultaneously, and produce the same reference? Is there any way to lock the trigger (or a UDF) in such a way that it can only have one concurrent instance?.

Hoặc tôi sẽ mở cho bất kỳ phương pháp tiếp cận được đề xuất nào khác cho vấn đề này.

+0

Bạn có nghĩ về việc sử dụng trình kích hoạt và tạo id đó bên trong trình kích hoạt không? –

+0

Không, nhưng SQL chắc chắn là đối tượng yếu nhất của tôi về loại dự án này, tôi thực sự không biết cách sử dụng các trình kích hoạt một cách hiệu quả - bạn có thể cho tôi chạy cơ bản về cách thực hiện điều này không? Nếu bạn có thể chỉ cho tôi theo hướng của một số tài liệu đọc có liên quan tôi sẽ được hạnh phúc :-) – DaveRandom

+1

[Bài viết này] (http://www.sugarcrm.com/forums/f6/developed-auto-generate-ids-guids -within-mysql-2895 /) nói về việc tạo tự động trên GUIDs bằng cách sử dụng mysql, tôi nghĩ nó sẽ cung cấp cho bạn một nơi nào đó để bắt đầu –

Trả lời

7

Nếu bạn đang sử dụng MyISAM, bạn có thể tạo khóa chính ghép trên trường văn bản + trường tăng tự động. MySQL sẽ xử lý tăng số tự động. Họ là những lĩnh vực riêng biệt, nhưng bạn có thể có được hiệu quả tương tự.

CREATE TABLE example (
company_name varchar(100), 
key_prefix char(4) not null, 
key_increment int unsigned auto_increment, 
primary key co_key (key_prefix,key_increment) 
) ENGINE=MYISAM; 

Khi bạn làm một chèn vào bảng, lĩnh vực key_increment sẽ tăng dựa trên giá trị cao nhất dựa trên key_prefix. Vì vậy, chèn với key_prefix "rèn" sẽ bắt đầu với 1 trong key_inrement, key_prefix "Jone" sẽ bắt đầu với 1 trong key_inrement vv

Ưu điểm:

  • Bạn không cần phải làm bất cứ điều gì với những con số tính toán .

Nhược điểm:

  • Bạn không có một sự chia rẽ quan trọng trên 2 cột.
  • Nó không hoạt động với InnoDB.
+0

Điều này có vẻ là một lựa chọn tốt và một ứng cử viên tốt cho câu trả lời được chấp nhận - có lẽ làm điều này tôi chỉ 'INSERT' các ký tự chữ cái vào cột' key_prefix' và 'key_increment' tạo một phần số? Nó sẽ làm cho logic 'SELECT' * hơi phức tạp hơn khi tìm kiếm theo tham chiếu do người dùng nhập vào, nhưng ngoài một điểm nhỏ, điều này hoàn thành tất cả các yêu cầu ... cảm ơn nhiều! – DaveRandom

+1

Công cụ lưu trữ của tôi là InnoDB hiện tại (mặc dù tôi không đặc biệt gắn liền với điều này) - không quan tâm, tại sao bạn chỉ có thể làm điều này với MyISAM? – DaveRandom

+0

Tôi cũng tự hỏi rằng - từ sách hướng dẫn mysql trong Giới hạn Innodb "Đối với cột AUTO_INCREMENT, bạn phải luôn định nghĩa một chỉ mục cho bảng, và chỉ mục đó phải chứa cột AUTO_INCREMENT. Trong các bảng MyISAM, cột AUTO_INCREMENT có thể là một phần của một chỉ mục nhiều cột. " –

1
  1. Đảm bảo bạn có ràng buộc duy nhất trên cột Tham chiếu.
  2. Tìm nạp tham chiếu tuần tự tối đa hiện tại giống như cách bạn thực hiện trong mã mẫu của mình. Bạn không thực sự cần phải cắt zeroes trước khi bạn cast sang (int), '0001' là một số nguyên hợp lệ.
  3. Cuộn vòng và chèn bên trong.
  4. Kiểm tra các hàng bị ảnh hưởng sau khi chèn. Bạn cũng có thể kiểm tra trạng thái SQL cho một lỗi khóa trùng lặp, nhưng không có hàng bị ảnh hưởng là một dấu hiệu tốt cho thấy chèn của bạn không thành công do chèn một giá trị tham chiếu hiện có.
  5. Nếu bạn không có hàng bị ảnh hưởng, hãy tăng số thứ tự liên tục và cuộn lại vòng lặp. Nếu bạn có hàng không bị ảnh hưởng, bạn đã hoàn tất và đã nhập mã nhận dạng duy nhất.
+0

Cách tiếp cận này hơi không hiệu quả, vì bạn sẽ chèn lại chèn cho đến khi cuối cùng bạn xoay xở để tránh xung đột. Tuy nhiên, bạn đang bắt đầu từ giá trị tham chiếu tối đa hiện tại + 1 và trừ khi bạn có nhiều lần chèn, bạn rất có khả năng thành công trong lần thử đầu tiên và được đảm bảo thành công ở lần thử thứ hai. – lanzz

+0

Đây là những gì tôi nghĩ tôi sẽ làm cho đến khi câu trả lời của Brent Baisley, tôi không thích nó vì khả năng rất xa của một vòng lặp dài cố gắng tìm ID tiếp theo có sẵn trong trường hợp có khối lượng độc lập cao chèn, như bạn đề cập trong bình luận của bạn. Tôi biết điều này là rất nhiều lập trình xung quanh một tình huống mà * nên * không bao giờ xảy ra, nhưng tôi vẫn muốn tránh nếu có thể. – DaveRandom

+0

Giải pháp của Brent thực sự tốt hơn so với tôi, tôi thực sự không biết myisam theo dõi sự tự động cho toàn bộ khóa chính thay vì cột thực tế. – lanzz

0

Cách dễ nhất để tránh giá trị trùng lặp cho cột tham chiếu là thêm ràng buộc duy nhất. Vì vậy, nếu nhiều quá trình cố gắng đặt cùng một giá trị, MySQL sẽ từ chối lần thử thứ hai và ném một lỗi.

ALTER TABLE table_name ADD UNIQUE KEY (`company_ref`); 

Nếu tôi gặp phải trường hợp của bạn, tôi sẽ xử lý việc tạo id tham chiếu của công ty trong lớp ứng dụng, trình kích hoạt có thể lộn xộn nếu không được thiết lập chính xác.

3

Làm thế nào về giải pháp này với trình kích hoạt và bảng để giữ duy nhất công ty. Thực hiện một sửa chữa - bảng tham chiếu phải là MyISAM nếu bạn muốn đánh số bắt đầu từ 1 cho mỗi chuỗi 4char duy nhất.

DROP TABLE IF EXISTS company; 
CREATE TABLE company (
    company_name varchar(100) DEFAULT NULL, 
    company_ref char(8) DEFAULT NULL 
) ENGINE=InnoDB 

DELIMITER ;; 
CREATE TRIGGER company_reference BEFORE INSERT ON company 
FOR EACH ROW BEGIN 
    INSERT INTO reference SET company_ref=SUBSTRING(LOWER(NEW.company_name), 1, 4), numeric_ref=NULL; 
    SET NEW.company_ref=CONCAT(SUBSTRING(LOWER(NEW.company_name), 1, 4), LPAD(CAST(LAST_INSERT_ID() AS CHAR(10)), 4, '0')); 
END ;; 
DELIMITER ; 

DROP TABLE IF EXISTS reference; 
CREATE TABLE reference (
company_ref char(4) NOT NULL DEFAULT '', 
numeric_ref int(11) NOT NULL AUTO_INCREMENT, 
PRIMARY KEY (company_ref, numeric_ref) 
) ENGINE=MyISAM; 

Và để hoàn thành ở đây, trình kích hoạt sẽ tạo tham chiếu mới nếu tên công ty bị thay đổi.

DROP TRIGGER IF EXISTS company_reference_up; 
DELIMITER ;; 
CREATE TRIGGER company_reference_up BEFORE UPDATE ON company 
FOR EACH ROW BEGIN 
    IF NEW.company_name <> OLD.company_name THEN 
     DELETE FROM reference WHERE company_ref=SUBSTRING(LOWER(OLD.company_ref), 1, 4) AND numeric_ref=SUBSTRING(OLD.company_ref, 5, 4); 
     INSERT INTO reference SET company_ref=SUBSTRING(LOWER(NEW.company_name), 1, 4), numeric_ref=NULL; 
     SET NEW.company_ref=CONCAT(SUBSTRING(LOWER(NEW.company_name), 1, 4), LPAD(CAST(LAST_INSERT_ID() AS CHAR(10)), 4, '0')); 
    END IF; 
END; 
;; 
DELIMITER ; 
+0

Tôi sẽ chấp nhận câu trả lời của Brent vì ông đã cung cấp một giải thích tốt về lý do tại sao đây là cách tiếp cận đúng, nhưng nếu Tôi có thể chấp nhận 2 câu trả lời bạn sẽ nhận được sự chấp nhận khác vì nó là một sự kết hợp các câu trả lời của bạn đã giúp tôi tìm ra giải pháp đúng cho vấn đề của tôi, như bạn đã cung cấp một ví dụ tốt đẹp về việc tạo ra kích hoạt.Vấn đề tôi gặp phải là thay đổi 'DELIMITER' trong khi chạy truy vấn tạo - tôi bằng cách nào đó đã bỏ lỡ điều đó khi ban đầu tôi cố gắng thực hiện nó. Bạn chắc chắn xứng đáng (ít nhất) +1 tôi đã cho bạn ... – DaveRandom

+0

@Dave :-) cool - vui vì nó đã giúp bạn –

3

Cho dù bạn đang sử dụng InnoDB, tại sao không sử dụng giao dịch rõ ràng để lấy khóa hàng độc quyền và ngăn kết nối khác đọc cùng hàng trước khi bạn hoàn tất cài đặt ID mới dựa trên nó?

(Đương nhiên, làm việc tính toán trong một kích hoạt sẽ giữ khóa cho thời gian ít hơn.)

mysqli_query($link, "BEGIN TRANSACTION"); 
$query = "SELECT `company_ref` 
      FROM `companies` 
      WHERE 
      `company_ref` LIKE '$fourLetters%' 
      ORDER BY `company_ref` DESC 
      LIMIT 1 
      FOR UPDATE"; 
$last = mysqli_fetch_assoc(mysqli_query($link, $query)); 
$newNum = ((int) ltrim(substr($last['company_ref'],4),'0')) + 1; 
$newRef = $fourLetters.str_pad($newNum, 4, '0', STR_PAD_LEFT); 
mysqli_query($link, "INSERT INTO companies . . . (new row using $newref)"); 
mysqli_commit($link); 

Chỉnh sửa: Chỉ cần chắc chắn 100% tôi chạy một kiểm tra bằng tay để xác nhận rằng giao dịch thứ hai sẽ trả lại hàng mới được chèn sau khi chờ đợi thay vì hàng bị khóa ban đầu.

Chỉnh sửa2: Cũng đã kiểm tra trường hợp không có hàng đầu tiên được trả lại (Nơi bạn nghĩ rằng không có hàng đầu tiên để đặt khóa) và hoạt động như vậy.

0

Phiên bản hacky cũng hoạt động cho InnoDB.

Thay chèn để companies với hai chèn trong một giao dịch:

INSERT INTO __keys 
     VALUES (LEFT(LOWER('Smiths Joinery'),4), LAST_INSERT_ID(1)) 
    ON DUPLICATE KEY UPDATE 
     num = LAST_INSERT_ID(num+1); 

    INSERT INTO __companies (comp_name, reference) 
    VALUES ('Smiths Joinery', 
      CONCAT(LEFT(LOWER(comp_name),4), LPAD(LAST_INSERT_ID(), 4, '0'))); 

nơi:

CREATE TABLE `__keys` (
     `prefix` char(4) NOT NULL, 
     `num` smallint(5) unsigned NOT NULL, 
     PRIMARY KEY (`prefix`) 
    ) ENGINE=InnoDB COLLATE latin1_general_ci; 

    CREATE TABLE `__companies` (
     `comp_id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
     `comp_name` varchar(45) NOT NULL, 
     `reference` char(8) NOT NULL, 
     PRIMARY KEY (`comp_id`) 
    ) ENGINE=InnoDB COLLATE latin1_general_ci; 

Chú ý:

  • latin1_general_ci có thể được thay thế bằng utf8_general_ci,
  • LEFT(LOWER('Smiths Joinery'),4) tốt hơn sẽ trở thành một hàm trong PHP
Các vấn đề liên quan