2008-08-03 53 views
47

Tôi có một bảng cơ sở dữ liệu và một trong các trường (không phải khóa chính) đang có chỉ mục duy nhất trên đó. Bây giờ tôi muốn trao đổi các giá trị trong cột này cho hai hàng. Làm thế nào điều này có thể được thực hiện? Hai hacks tôi biết là:Hoán đổi các giá trị cột được lập chỉ mục duy nhất trong cơ sở dữ liệu

  1. Xóa cả dòng và tái chèn chúng
  2. Cập nhật hàng với một số giá trị khác và hoán đổi và sau đó cập nhật với giá trị thực tế.

Nhưng tôi không muốn sử dụng chúng vì chúng dường như không phải là giải pháp thích hợp cho vấn đề. Có ai giúp tôi không?

+0

Đổi tên các cột? – MatBailie

Trả lời

8

Tôi nghĩ bạn nên tìm giải pháp 2. Không có chức năng 'hoán đổi' trong bất kỳ biến thể SQL nào mà tôi biết.

Nếu bạn cần thực hiện việc này thường xuyên, tôi đề xuất giải pháp 1, tùy thuộc vào cách các phần khác của phần mềm đang sử dụng dữ liệu này. Bạn có thể gặp vấn đề về khóa nếu bạn không cẩn thận.

Nhưng tóm lại: không có giải pháp nào khác ngoài giải pháp bạn cung cấp.

2

Tôi cũng nghĩ rằng # 2 là đặt cược tốt nhất, mặc dù tôi chắc chắn sẽ bọc nó trong một giao dịch trong trường hợp có sự cố xảy ra giữa cập nhật.

Một giải pháp thay thế (kể từ khi bạn yêu cầu) để cập nhật các giá trị chỉ số duy nhất với các giá trị khác nhau sẽ là cập nhật tất cả các giá trị khác trong các hàng thành hàng khác. Làm điều này có nghĩa là bạn có thể để các giá trị Chỉ số duy nhất một mình và cuối cùng, bạn sẽ kết thúc với dữ liệu mà bạn muốn. Tuy nhiên, hãy cẩn thận, trong trường hợp một số bảng khác tham khảo bảng này trong mối quan hệ khóa ngoại, thì tất cả các mối quan hệ trong DB vẫn còn nguyên vẹn.

2

Giả sử bạn biết PK của hai hàng bạn muốn cập nhật ... Điều này hoạt động trong SQL Server, không thể nói cho các sản phẩm khác. SQL được (nghĩa vụ phải được) nguyên tử ở mức tuyên bố:

CREATE TABLE testing 
(
    cola int NOT NULL, 
    colb CHAR(1) NOT NULL 
); 

CREATE UNIQUE INDEX UIX_testing_a ON testing(colb); 

INSERT INTO testing VALUES (1, 'b'); 
INSERT INTO testing VALUES (2, 'a'); 

SELECT * FROM testing; 

UPDATE testing 
SET colb = CASE cola WHEN 1 THEN 'a' 
       WHEN 2 THEN 'b' 
       END 
WHERE cola IN (1,2); 

SELECT * FROM testing; 

do đó bạn sẽ đi từ:

cola colb 
------------ 
1  b 
2  a 

tới:

cola colb 
------------ 
1  a 
2  b 
+0

Điều này không làm việc cho tôi trong MySQL. –

5

Tiếp tục câu trả lời Andy Irving

điều này làm việc cho tôi (trên SQL Server 2005) trong một tình huống tương tự nơi tôi có một khóa tổng hợp và tôi n eed để hoán đổi một trường là một phần của ràng buộc duy nhất.

chính: PID, lnum rec1: 10, 0 rec2: 10, 1 rec3: 10, 2

và tôi cần phải trao đổi lnum để kết quả là

chính: PID, lnum rec1: 10, 1 rec2: 10, 2 rec3: 10, 0

SQL cần thiết:

UPDATE DOCDATA  
SET  LNUM = CASE LNUM 
       WHEN 0 THEN 1 
       WHEN 1 THEN 2 
       WHEN 2 THEN 0 
      END 
WHERE  (pID = 10) 
    AND  (LNUM IN (0, 1, 2)) 
+0

Nếu điều này hoạt động, điều đó có thể thực hiện trong một giao dịch – codeulike

2

Tôi có cùng một vấn đề.Đây là cách tiếp cận được đề xuất của tôi trong PostgreSQL. Trong trường hợp của tôi, chỉ mục duy nhất của tôi là một giá trị chuỗi, xác định thứ tự người dùng rõ ràng trên các hàng của tôi. Người dùng sẽ xáo trộn các hàng trong một ứng dụng web, sau đó gửi các thay đổi.

Tôi dự định thêm trình kích hoạt "trước". Trong kích hoạt đó, bất cứ khi nào giá trị chỉ mục duy nhất của tôi được cập nhật, tôi sẽ xem xét xem có hàng nào khác đã giữ giá trị mới của tôi không. Nếu vậy, tôi sẽ cung cấp cho họ giá trị cũ của tôi, và có hiệu quả ăn cắp giá trị ra khỏi chúng.

Tôi hy vọng PostgreSQL sẽ cho phép tôi thực hiện điều này trong trình kích hoạt trước.

Tôi sẽ đăng lại và cho bạn biết số dặm của tôi.

1

Oracle đã hoãn kiểm tra tính toàn vẹn để giải quyết chính xác điều này, nhưng không có sẵn trong SQL Server hoặc MySQL.

3

Có một cách tiếp cận khác hoạt động với SQL Server: sử dụng bảng tạm thời tham gia vào nó trong câu lệnh UPDATE của bạn.

Sự cố là do có hai hàng có cùng giá trị cùng một lúc, nhưng nếu bạn cập nhật cả hai hàng cùng một lúc (với giá trị mới, duy nhất), sẽ không có vi phạm ràng buộc.

Pseudo-code:

-- setup initial data values: 
insert into data_table(id, name) values(1, 'A') 
insert into data_table(id, name) values(2, 'B') 

-- create temp table that matches live table 
select top 0 * into #tmp_data_table from data_table 

-- insert records to be swapped 
insert into #tmp_data_table(id, name) values(1, 'B') 
insert into #tmp_data_table(id, name) values(2, 'A') 

-- update both rows at once! No index violations! 
update data_table set name = #tmp_data_table.name 
from data_table join #tmp_data_table on (data_table.id = #tmp_data_table.id) 

Nhờ Giàu H cho kỹ thuật này. - Đánh dấu

+1

Có thể hơi cũ nhưng tôi đã cố gắng thực hiện một trang 'sắp xếp lại' cho ứng dụng Silverlight của tôi vì khách hàng muốn sắp xếp báo cáo của họ bằng cách một thứ tự cụ thể - tôi đã thêm một cột sắp xếp nhưng vì đó là khóa duy nhất nên tôi gặp sự cố khi cập nhật nó. Tôi đã kết thúc bằng cách sử dụng một biến bảng nhưng nguyên tắc là như nhau (Tôi không thích bảng temp phải trung thực!). Cảm ơn ý tưởng :) – Charleh

1

Trong SQL Server, câu lệnh MERGE có thể cập nhật các hàng thường phá vỡ một phím UNIQUE/INDEX. (Chỉ cần thử nghiệm điều này vì tôi tò mò.)

Tuy nhiên, bạn phải sử dụng bảng/biến tạm thời để cung cấp MERGE với các hàng cần thiết.

20

Từ kỳ diệu là DEFERRABLE đây:

DROP TABLE ztable CASCADE; 
CREATE TABLE ztable 
    (id integer NOT NULL PRIMARY KEY 
    , payload varchar 
    ); 
INSERT INTO ztable(id,payload) VALUES (1,'one'), (2,'two'), (3,'three'); 
SELECT * FROM ztable; 


    -- This works, because there is no constraint 
UPDATE ztable t1 
SET payload=t2.payload 
FROM ztable t2 
WHERE t1.id IN (2,3) 
AND t2.id IN (2,3) 
AND t1.id <> t2.id 
    ; 
SELECT * FROM ztable; 

ALTER TABLE ztable ADD CONSTRAINT OMG_WTF UNIQUE (payload) 
    DEFERRABLE INITIALLY DEFERRED 
    ; 

    -- This should also work, because the constraint 
    -- is deferred until "commit time" 
UPDATE ztable t1 
SET payload=t2.payload 
FROM ztable t2 
WHERE t1.id IN (2,3) 
AND t2.id IN (2,3) 
AND t1.id <> t2.id 
    ; 
SELECT * FROM ztable; 

KẾT QUẢ:

DROP TABLE 
NOTICE: CREATE TABLE/PRIMARY KEY will create implicit index "ztable_pkey" for table "ztable" 
CREATE TABLE 
INSERT 0 3 
id | payload 
----+--------- 
    1 | one 
    2 | two 
    3 | three 
(3 rows) 

UPDATE 2 
id | payload 
----+--------- 
    1 | one 
    2 | three 
    3 | two 
(3 rows) 

NOTICE: ALTER TABLE/ADD UNIQUE will create implicit index "omg_wtf" for table "ztable" 
ALTER TABLE 
UPDATE 2 
id | payload 
----+--------- 
    1 | one 
    2 | two 
    3 | three 
(3 rows) 
+0

Tính năng này có hoạt động trong MySQL không? –

+0

@MarcoDemaio Tôi không biết. Tôi không sợ: vì mysql không cho phép cập nhật từ tự tham gia, tôi cho rằng nó không đọc được. Nhưng bạn có thể thử ... – wildplasser

+0

sạch sẽ, đơn giản và hoạt động tốt đẹp, cảm ơn người đàn ông :) – Aldos

1

Đối với Oracle có một tùy chọn, chậm, nhưng bạn phải thêm nó vào chế của bạn.

SET CONSTRAINT emp_no_fk_par DEFERRED; 

Để trì hoãn TẤT CẢ các ràng buộc bị trì hoãn trong suốt phiên, bạn có thể sử dụng câu lệnh ALTER SESSION SET constraints = DEFERRED.

Source

0

Tôi thường nghĩ về một giá trị hoàn toàn không có chỉ mục trong bảng của tôi có thể có. Thông thường - đối với các giá trị cột duy nhất - nó thực sự dễ dàng. Ví dụ, đối với các giá trị của cột 'position' (thông tin về thứ tự của một số phần tử) là 0.

Sau đó, bạn có thể sao chép giá trị A vào biến, cập nhật giá trị B và sau đó đặt giá trị B từ biến của bạn. Hai truy vấn, tôi biết không có giải pháp tốt hơn mặc dù.

Các vấn đề liên quan