2012-08-03 58 views
5

Bảng là như thế nàyTìm tên cột có giá trị không phải là null cuối cùng trong một hàng

 
ID A1 A2 A3 A4 A5 A6 A7 A8 A9 
1 YE YE YE NULL YE YE YE NULL NULL 
2 YE YE YE NULL NULL NULL NULL NULL NULL 
3 YE YE YE YE YE YE YE YE NULL 

đâu ID là khóa chính.
Tôi muốn lấy tên cột có giá trị không phải là null cuối cùng trong một hàng, kết quả là như thế này

 
ID LAST 
1 A7 
2 A3 
3 A8 

Bất kỳ giúp đỡ về vấn đề này?

+0

Tôi có thể nói "giản đồ đó trông khủng khiếp" không? :-) Nó có thể được thực hiện với một ưu tiên đảo ngược có điều kiện. –

Trả lời

2

Mặc dù mối nghi ngại của tôi về giản đồ này, xem xét việc này "ngược ưu tiên" có điều kiện:

select 
    id, 
    case 
    -- first match terminates search 
    when A9 is not null then 'A9' 
    when A8 is not null then 'A8' 
    when A7 is not null then 'A7' 
    .. 
    else null 
    as lastNonNullColumn 
from .. 

Trình tự đánh giá được đảm bảo trong TSQL (xem CASE) vì vậy chúng tôi chỉ inchworm ngược :)

Đánh giá, theo thứ tự được chỉ định, Boolean_expression cho mỗi mệnh đề WHEN.

Ngoài ra, có lẽ UNPIVOT (hoặc ROLLUP [?] Hoặc thủ công UNION) có thể được sử dụng. Đó là, xoay tập cố định các tên cột vào giá trị, thì đó là một truy vấn đơn giản .. nghĩa là, nếu bảng là bình thường, điều này có thể được thực hiện dễ dàng :-)

select 
    id, 
    max(colName) as lastNonNullColumn 
from <<normalized_derived_table>> 
where colValue is not null 
group by id 
+1

Biểu thức CASE của bạn có kế hoạch thực thi tốt nhất và có ít CPU nhất. Các hoạt động UNPIVOT yêu cầu SORT – ErikE

+0

tốn kém làm việc của nó. Cảm ơn tất cả. – user1574813

2

Làm thế nào về điều này? Nó sử dụng UNPIVOT để chuyển đổi dữ liệu và sau đó bạn sẽ chọn giá trị cuối cùng tối đa không phải là rỗng/trống.

;with cte as 
(
    select id 
    , last 
    , value 
    , row_number() over(partition by id order by last) rn 
    from 
    (
     select id, 
      isnull(a1, '') as a1, 
      isnull(a2, '') as a2, 
      isnull(a3, '') as a3, 
      isnull(a4, '') as a4, 
      isnull(a5, '') as a5, 
      isnull(a6, '') as a6, 
      isnull(a7, '') as a7, 
      isnull(a8, '') as a8, 
      isnull(a9, '') as a9 
     from t 
) x 
    unpivot 
    (
     value 
     for last in (a1, a2, a3, a4, a5, a6, a7, a8, a9) 
) u 
) 
select id, max(last) as last 
from cte 
where value != '' 
group by id 

Xem SQL Fiddle with Demo

Chỉnh sửa, thực sự nó không cần phải là phức tạp:

select id 
    , max(last) last 
from 
(
    select id, a1, a2, a3, a4, a5, a6, a7, a8, a9 
    from t 
) x 
unpivot 
(
    value 
    for last in (a1, a2, a3, a4, a5, a6, a7, a8, a9) 
) u 
group by id 

Xem SQL Fiddle with Demo

+0

Cả hai phiên bản của bạn đều hoạt động nhưng yêu cầu tên cột có thể sắp xếp (bao gồm cả không có A10 trở lên). Tôi khá chắc chắn các cột của OP không thực sự được đặt tên là A1 - A9. – ErikE

+0

@ErikE đúng, nhưng dựa trên các yêu cầu đã được đăng, điều này sẽ hoạt động. Nếu họ mở rộng theo nhu cầu của họ sẽ rất hữu ích. OP cho biết bảng bao gồm các cột a1-a9. – Taryn

+0

Đủ công bằng! Đó là một câu trả lời tốt cho vấn đề như được đưa ra. – ErikE

1

Dưới đây là một phiên bản giả UNPIVOT cho phép bạn chỉ định thứ tự của các cột (nếu các tên cột không sắp xếp theo vị trí của chúng).

SELECT 
    T.ID, 
    X.Name 
FROM 
    T 
    CROSS APPLY (
     SELECT TOP 1 Name FROM (
     VALUES (1, 'A1', T.A1), (2, 'A2', T.A2), (3, 'A3', T.A3), (4, 'A4', T.A4), 
     (5, 'A5', T.A5), (6, 'A6', T.A6), (7, 'A7', T.A7), (8, 'A8', T.A8), 
     (9, 'A9', T.A9) 
    ) X (Pos, Name, Col) 
     WHERE Col IS NOT NULL 
     ORDER BY X.Pos DESC 
    ) X; 

Tuy nhiên, trong khi thực tế IO và CPU không phải là tồi tệ hơn nhiều so với phương pháp UNPIVOT tự nhiên (kế hoạch thực hiện trông xấu, nhưng tác động máy chủ thực sự không phải là tồi tệ hơn nhiều), đây không phải là người biểu diễn tốt nhất. Biểu thức CASE đơn giản được đưa ra bởi @pst là.

Giả sử các tên cột thể được sắp xếp như là, UNPIVOT có thể được đơn giản hóa hơn nữa:

SELECT ID, Max(Last) 
FROM T UNPIVOT (Value FOR Last IN (A1, A2, A3, A4, A5, A6, A7, A8, A9)) U 
GROUP BY ID; 

Cuối cùng, đây là một phiên bản điên Tôi nghĩ về điều đó không may thực hiện tồi tệ hơn những người khác:

SELECT 
    T.ID, 
    Coalesce(
     (SELECT 'A9' WHERE T.A9 IS NOT NULL), 
     (SELECT 'A8' WHERE T.A8 IS NOT NULL), 
     (SELECT 'A7' WHERE T.A7 IS NOT NULL), 
     (SELECT 'A6' WHERE T.A6 IS NOT NULL), 
     (SELECT 'A5' WHERE T.A5 IS NOT NULL), 
     (SELECT 'A4' WHERE T.A4 IS NOT NULL), 
     (SELECT 'A3' WHERE T.A3 IS NOT NULL), 
     (SELECT 'A2' WHERE T.A2 IS NOT NULL), 
     (SELECT 'A1' WHERE T.A1 IS NOT NULL) 
    ) LastNotNullColumn 
FROM T 
ORDER BY ID 

Về mặt lý thuyết, động cơ có thể đưa ra một kế hoạch trông rất giống với phiên bản biểu thức CASE, nhưng không. Kế hoạch này trông hoàn toàn điên rồ, với một đối tượng bảng cho mỗi câu lệnh select, và mất khoảng hai lần CPU như là biểu thức CASE.

Tất cả các phiên bản tôi đã thử nghiệm sử dụng cùng một số lần đọc logic, chỉ thay đổi trong CPU. Tôi đã sử dụng 15.000 hàng để kiểm tra.

Cuối cùng, tôi không thể trong lương tâm tốt không cảnh báo bạn rằng lược đồ của bạn có lẽ không tốt nhất. Mặc dù tôi không thể biết dữ liệu của bạn là gì, nhưng bạn đang cố gắng tìm dữ liệu cuối cùng có thể gợi ý các cột đại diện cho thời gian hoặc giai đoạn của một số vòng đời - và đó không phải là thiết kế cơ sở dữ liệu chính xác. Thay vào đó, lưu trữ dữ liệu không được bỏ phiếu. Khi thời gian đến mà bạn cần một tập kết quả được xoay vòng, bạn có thể PIVOT. Và, việc truy vấn giá trị gần đây nhất cho mỗi ID trở nên đơn giản hơn một chút!

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