5

Tôi hiện đang viết một ứng dụng web phù hợp với người dùng dựa trên câu hỏi được trả lời. Tôi đã nhận ra thuật toán đối sánh của tôi chỉ trong một truy vấn và đã điều chỉnh nó cho đến giờ phải mất 8,2ms để tính toán tỷ lệ phần trăm phù hợp giữa 2 người dùng. Nhưng webapp của tôi phải lấy danh sách người dùng và lặp qua danh sách thực hiện truy vấn này. Đối với 5000 người dùng, nó mất 50 giây trên máy cục bộ của tôi. Có thể đặt mọi thứ trong một truy vấn trả về một cột với cột user_id và một cột có đối sánh được tính không? Hoặc là một thủ tục lưu trữ một tùy chọn?SQL: trả về bảng người dùng với cột được tính toán cho phần trăm so khớp?

Tôi hiện đang làm việc với MySQL nhưng sẵn sàng chuyển đổi cơ sở dữ liệu nếu cần.

Đối với bất cứ ai quan tâm đến các lược đồ và dữ liệu, tôi đã tạo ra một SQLFiddle: http://sqlfiddle.com/#!2/84233/1

và truy vấn phù hợp với tôi:

SELECT COALESCE(SQRT((100.0*as1.actual_score/ps1.possible_score) * (100.0*as2.actual_score/ps2.possible_score)) - (100/ps1.commonquestions), 0) AS perc 
    FROM (SELECT SUM(imp.value) AS actual_score 
     FROM user_questions AS uq1 
     INNER JOIN importances imp ON imp.id = uq1.importance 
     INNER JOIN user_questions uq2 ON uq2.question_id = uq1.question_id AND uq2.user_id = 101 
     AND (uq1.accans1 = uq2.answer_id 
      OR uq1.accans2 = uq2.answer_id 
      OR uq1.accans3 = uq2.answer_id 
      OR uq1.accans4 = uq2.answer_id) 
     WHERE uq1.user_id = 1) AS as1, 
    (SELECT SUM(value) AS possible_score, COUNT(*) AS commonquestions 
     FROM user_questions AS uq1 
     INNER JOIN importances ON importances.id = uq1.importance 
     INNER JOIN user_questions uq2 ON uq1.question_id = uq2.question_id AND uq2.user_id = 101 
     WHERE uq1.user_id = 1) AS ps1, 
    (SELECT SUM(imp.value) AS actual_score 
     FROM user_questions AS uq1 
     INNER JOIN importances imp ON imp.id = uq1.importance 
     INNER JOIN user_questions uq2 ON uq2.question_id = uq1.question_id AND uq2.user_id = 1 
     AND (uq1.accans1 = uq2.answer_id 
      OR uq1.accans2 = uq2.answer_id 
      OR uq1.accans3 = uq2.answer_id 
      OR uq1.accans4 = uq2.answer_id) 
     WHERE uq1.user_id = 101) AS as2, 
    (SELECT SUM(value) AS possible_score 
     FROM user_questions AS uq1 
     INNER JOIN importances ON importances.id = uq1.importance 
     INNER JOIN user_questions uq2 ON uq1.question_id = uq2.question_id AND uq2.user_id = 1 
     WHERE uq1.user_id = 101) AS ps2 
+1

Bạn có thể kết hợp biểu thức phụ "câu hỏi thường gặp" của hai "chân" của truy vấn. Bạn cũng có thể khái quát hóa các truy vấn phụ cho người dùng = 1 và người dùng = 101 thành một truy vấn CTE tổng quát (nếu DBMS của bạn hỗ trợ chúng. Nhưng trước tiên: Hãy hiển thị cho chúng tôi định nghĩa bảng và có thể một số dữ liệu. – wildplasser

+0

Có, dữ liệu với đầu ra mong muốn tương ứng –

+1

Tôi đã tạo một SQLFiddle để chơi với :) Khi tôi kết hợp người dùng 1 và 5, kết quả sẽ là '43 .678 'http://sqlfiddle.com/#!2/84233/1 – Mexxer

Trả lời

1

tôi đã chán, vì vậy: Đây là một phiên bản viết lại truy vấn của bạn - dựa trên một cổng PostgreSQL của giản đồ của bạn - cho phép tính các trận đấu cho tất cả các cặp sử dụng cùng một lúc:

http://sqlfiddle.com/#!12/30524/6

Tôi đã kiểm tra và nó tạo ra kết quả tương tự cho cặp người dùng (1,5).

WITH 
userids(uid) AS (
    select distinct user_id from user_questions 
), 
users(u1,u2) AS (
    SELECT u1.uid, u2.uid FROM userids u1 CROSS JOIN userids u2 WHERE u1 <> u2 
), 
scores AS (
     SELECT 
      sum(CASE WHEN uq2.answer_id IN (uq1.accans1, uq1.accans2, uq1.accans3, uq1.accans4) THEN imp.value ELSE 0 END) AS actual_score, 
      sum(imp.value) AS potential_score, 
      count(1) AS common_questions, 
      users.u1, 
      users.u2 
     FROM user_questions AS uq1 
     INNER JOIN importances imp ON imp.id = uq1.importance 
     INNER JOIN user_questions uq2 ON uq2.question_id = uq1.question_id 
     INNER JOIN users ON (uq1.user_id=users.u1 AND uq2.user_id=users.u2) 
     GROUP BY u1, u2 
), 
score_pairs(u1,u2,u1_actual,u2_actual,u1_potential,u2_potential,common_questions) AS (
    SELECT s1.u1, s1.u2, s1.actual_score, s2.actual_score, s1.potential_score, s2.potential_score, s1.common_questions 
    FROM scores s1 INNER JOIN scores s2 ON (s1.u1 = s2.u2 AND s1.u2 = s2.u1) 
    WHERE s1.u1 < s1.u2 
) 
SELECT 
    u1, u2, 
    COALESCE(SQRT((100.0*u1_actual/u1_potential) * (100.0*u2_actual/u2_potential)) - (100/common_questions), 0) AS "match" 
FROM score_pairs; 

Không có lý do bạn không thể cảng này trở lại để MySQL, như CTE chỉ có để có thể đọc và không làm bất cứ điều gì bạn không thể làm với FROM (SELECT ...). Không có mệnh đề WITH RECURSIVE và CTE không được tham chiếu từ nhiều CTE khác. Bạn sẽ có một chút truy vấn lồng nhau đáng sợ, nhưng đó chỉ là một thách thức về định dạng.

Thay đổi:

  • Tạo một tập hợp các người dùng riêng biệt
  • Tự tham gia mà bộ người dùng riêng biệt để tạo ra một tập hợp các cặp dùng
  • và sau đó tham gia vào đó danh sách các cặp trong điểm số truy vấn để tạo một bảng điểm số
  • Tạo bảng điểm bằng cách kết hợp các truy vấn trùng lặp lớn cho possiblescore1 và possiblescore2, actualscore1 và actualscore2.
  • sau đó tóm tắt nó trong truy vấn bên ngoài cuối cùng

Tôi chưa tối ưu hóa truy vấn; như được viết nó chạy trong 5ms trên hệ thống của tôi. Trên dữ liệu lớn hơn, có thể bạn cần phải tái cấu trúc một số hoặc sử dụng các thủ thuật như chuyển đổi một số mệnh đề CTE thành các câu lệnh tạo bảng tạm thời SELECT ... INTO TEMPORARY TABLE mà sau đó bạn lập chỉ mục trước khi truy vấn.

Cũng có thể bạn sẽ muốn di chuyển thế hệ của các hàng users ra khỏi CTE và thành mệnh đề câu hỏi con FROM của scores. Đó là vì yêu cầu WITH hoạt động như một hàng rào tối ưu hóa giữa các mệnh đề, do đó cơ sở dữ liệu phải thực hiện các hàng và không thể sử dụng các thủ thuật như đẩy mệnh đề lên hoặc xuống.

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