2016-08-11 25 views
5

Tôi đã chuẩn bị an SQL Fiddle cho câu hỏi của mình.Tìm tổng giá trị tối đa trong một truy vấn SELECT duy nhất

Trong một trò chơi chữ tôi chạy a custom function để tìm tất cả các từ được chơi bởi một người dùng trong lượt cuối cùng của cô:

app screenshot

từ không hợp lệ có score của NULL (có thể được thay đổi để -1 nếu cần thiết).

Từ hợp lệ có dương score và nếu có một số từ phù hợp, thì tôi chỉ cần điểm cao nhất (và loại bỏ điểm số khác). Ví dụ: nếu người chơi chơi từ ngang "ab" với điểm 8 và từ dọc "ab" với điểm số 2, thì cô ấy chỉ nhận được 8 điểm cho lượt đó.

Dưới đây là bảng thử nghiệm của tôi:

CREATE TABLE words(word varchar, score integer); 

Và ở đây tôi điền vào nó với dữ liệu thử nghiệm:

INSERT INTO words (word, score) VALUES 
('ab', 8), -- word with higher score should be taken 
('ab', 2), -- same word with lower score should be discarded 
('xy', 2), 
('zz', NULL); -- invalid word marked by NULL (or -1) 

tôi có thể thấy nếu một từ không hợp lệ đã được chơi bởi

IF EXISTS (SELECT 1 FROM words WHERE score IS NULL) THEN 
    RAISE EXCEPTION 'Invalid word played'; 
END IF; 

Và tôi có thể xóa các từ trùng lặp bằng GROUP BY:

SELECT word, max(score) as score FROM words GROUP BY word; 

Câu hỏi của tôi là tuy nhiên:

Làm thế nào để kết hợp cả hai câu lệnh trên để một SELECT tuyên bố duy nhất, vì vậy mà tôi:

  1. biết nếu một từ không hợp lệ đã được chơi
  2. Tổng số điểm đã chơi (để tôi có thể cập nhật điểm số của người chơi)

Tôi đang tìm một câu lệnh duy nhất, để the custom function không chạy nhiều lần và tốt nhất là không có bảng tạm thời.

Kết quả sẽ giống như thế (tôi sẽ gọi nó là từ tùy chỉnh khác PL/pgSQL chức năng):

DECLARE 
    total_user_score integer; 
    invalid_words_found boolean; 

SELECT 
    .....,    -- how to calculate this value please? 
    .....    -- how to calculate this value please? 
INTO STRICT 
    total_user_score, 
    invalid_words_found 
FROM words_check_words(....); -- avoid calling this function twice 

IF invalid_words_found THEN 
    RAISE EXCEPTION "Invalid words found"; 
ELSE 
    UPDATE games SET user_score = user_score + total_user_score; 
END IF; 

Trả lời

1

(chỉnh sửa để trả về một boolean cho invalid_words_found)
(chỉnh sửa để sử dụng bool_or chức năng tổng hợp)

Nếu tôi hiểu đúng:

with cte as (
    select max(score) as score, 
      bool_or(score is null) as has_invalid 
     from words_check_words(....) 
    group by word 
) 
select coalesce(sum(score), 0) as total_user_score, 
     bool_or(has_invalid) as invalid_words_found 
    from cte 

Chức năng tùy chỉnh của bạn sẽ chỉ được gọi một lần.

EDIT: Lồng ghép vào quy trình của bạn, nó sẽ giống như thế này:

DECLARE 
    total_user_score integer; 
    invalid_words_found boolean; 

with cte as (
    select max(score) as score, 
      bool_or(score is null) as has_invalid 
     from words_check_words(....) 
    group by word 
) 
select coalesce(sum(score), 0), 
     bool_or(has_invalid) 
INTO STRICT 
    total_user_score, 
    invalid_words_found 
FROM cte; 

IF invalid_words_found THEN 
    RAISE EXCEPTION "Invalid words found"; 
ELSE 
    UPDATE games SET user_score = user_score + total_user_score; 
END IF; 
1

Bạn có thể làm điều đó như thế này:

select coalesce(sum(max_score), 0) as total_user_score, 
     count(*) - count(max_score) as invalid_words_found 
from  (select max(score) as max_score 
      from  words 
      group by word) x 

Xem: Sql Fiddle

này giả định rằng nếu một từ không hợp lệ, nó cũng không thể xảy ra (trong một bản ghi khác) với một điểm không null.

Tuyên bố IF sẽ cần phải xem xét như sau:

IF invalid_words_found > 0 THEN 

... và bạn có thể hiển thị các số từ không hợp lệ trong thông báo lỗi:

RAISE EXCEPTION '% Invalid words found!', invalid_words_found; 
+0

Yes. từ không hợp lệ luôn có cùng số điểm NULL (hoặc -1) –

+1

OK, trong giải pháp này nó phải là NULL (không -1). Nếu -1 có thể xảy ra, SQL sẽ cần một chút thích ứng. Bạn có cần điều đó không? – trincot

+0

Không, tất cả các điểm tốt đều dương. Cảm ơn bạn vì câu trả lời! –

1
select 
    word, 
    max(
    case 
     when score is null then raise_error('Invalid word: ' || word)::int 
     else score 
    end) 
from words 
group by word; 

nơi Chức năng raise_error được khai báo như

create function raise_error(text) returns text language plpgsql volatile as 
$body$ 
begin 
    raise exception '%', $1; 
end $body$; 

Để thực hiện các truy vấn thêm thanh lịch có một chung mục đích chức năng có thể được tạo ra:

create function assert(
    p_cond boolean, 
    p_message text, 
    p_result anyelement) returns anyelement language plpgsql volatile as 
$body$ 
begin 
    if p_cond then 
    return p_result; 
    else 
    raise exception '%', p_message; 
    end if; 
end $body$; 

Và truy vấn trở nên nhỏ gọn hơn:

select 
    word, 
    max(assert(score is not null, 'Invalid word: ' || word, score)) 
from words 
group by word; 
Các vấn đề liên quan