Tôi không tin rằng một GROUP BY sẽ mang lại cho bạn kết quả như bạn mong muốn. Và rất tiếc, MySQL không hỗ trợ các hàm phân tích (đó là cách chúng tôi giải quyết vấn đề này trong Oracle hoặc SQL Server.)
Có thể mô phỏng một số hàm phân tích thô sơ bằng cách sử dụng các biến do người dùng xác định.
Trong trường hợp này, chúng tôi muốn mô phỏng:
ROW_NUMBER() OVER(PARTITION BY doctor_id ORDER BY distance ASC) AS seq
Vì vậy, bắt đầu với truy vấn ban đầu, tôi đã thay đổi ORDER BY để nó sắp xếp theo doctor_id
đầu tiên, sau đó đến distance
được tính toán . (Cho đến khi chúng tôi biết những khoảng cách đó, chúng tôi không biết cái nào là "gần nhất".)
Với kết quả được sắp xếp này, về cơ bản chúng tôi "đánh số" các hàng cho mỗi doctor_id, hàng gần nhất là 1, hàng gần thứ hai là 2, v.v. Khi chúng tôi nhận được một doctor_id mới, chúng tôi bắt đầu lại với giá trị gần nhất là 1.
Để thực hiện điều này, chúng tôi sử dụng các biến do người dùng xác định. Chúng tôi sử dụng một để gán số hàng (tên biến là @i và cột trả về có bí danh seq). Biến khác mà chúng tôi sử dụng để "ghi nhớ" bác sĩ_id từ hàng trước đó, vì vậy chúng tôi có thể phát hiện ra "ngắt" trong bác sĩ_id, vì vậy chúng tôi có thể biết khi nào bắt đầu lại việc đánh số hàng ở 1 lần nữa.
Đây là truy vấn:
SELECT z.*
, @i := CASE WHEN z.doctor_id = @prev_doctor_id THEN @i + 1 ELSE 1 END AS seq
, @prev_doctor_id := z.doctor_id AS prev_doctor_id
FROM
(
/* original query, ordered by doctor_id and then by distance */
SELECT zip,
( 3959 * acos( cos( radians(34.12520) ) * cos( radians( zip_info.latitude ) ) * cos(radians( zip_info.longitude ) - radians(-118.29200) ) + sin( radians(34.12520) ) * sin( radians( zip_info.latitude ) ) ) ) AS distance,
user_info.*, office_locations.*
FROM zip_info
RIGHT JOIN office_locations ON office_locations.zipcode = zip_info.zip
RIGHT JOIN user_info ON office_locations.doctor_id = user_info.id
WHERE user_info.status='yes'
ORDER BY user_info.doctor_id ASC, distance ASC
) z JOIN (SELECT @i := 0, @prev_doctor_id := NULL) i
HAVING seq = 1 ORDER BY z.distance
Tôi đang đưa ra giả định rằng truy vấn ban đầu đang trả về tập kết quả mà bạn cần, nó chỉ có quá nhiều hàng và bạn muốn loại bỏ tất cả trừ "gần nhất" (hàng có giá trị tối thiểu của khoảng cách) cho mỗi doctor_id.
Tôi đã gói truy vấn ban đầu của bạn trong một truy vấn khác; những thay đổi duy nhất mà tôi đã thực hiện đối với truy vấn ban đầu là sắp xếp kết quả theo doctor_id rồi theo khoảng cách và loại bỏ HAVING distance < 50
mệnh đề. (Nếu bạn chỉ muốn trả về khoảng cách nhỏ hơn 50, thì hãy tiếp tục và để điều khoản đó ở đó. Không rõ đó là ý định của bạn hay điều đó được chỉ định nhằm cố gắng giới hạn các hàng ở mức một trên mỗi doctor_id.)
Một số vấn đề cần lưu ý:
Truy vấn thay thế trả về hai cột bổ sung; những thứ này không thực sự cần thiết trong tập kết quả, ngoại trừ phương tiện để tạo tập kết quả. (Có thể gói lại toàn bộ CHỌN này trong một CHỌN khác để bỏ qua các cột đó, nhưng điều đó thực sự lộn xộn hơn giá trị của nó. Tôi chỉ lấy lại các cột và biết rằng tôi có thể bỏ qua chúng.)
Vấn đề khác là việc sử dụng .*
trong truy vấn bên trong là một chút nguy hiểm, ở chỗ chúng tôi thực sự cần đảm bảo rằng các tên cột được trả về bởi truy vấn đó là duy nhất. (Ngay cả khi các tên cột hiện tại đã khác biệt, việc thêm cột vào một trong các bảng đó có thể tạo ra ngoại lệ cột "không rõ ràng" trong truy vấn. Tốt nhất là nên tránh điều đó và dễ dàng giải quyết bằng cách thay thế .*
với danh sách các cột được trả về và chỉ định bí danh cho bất kỳ tên cột "trùng lặp" nào. (Việc sử dụng z.*
trong truy vấn bên ngoài không phải là điều đáng lo ngại, miễn là chúng tôi kiểm soát được các cột được trả về bởi z
.)
Phụ lục:
Tôi lưu ý rằng GROUP BY sẽ không cung cấp cho bạn tập kết quả mà bạn cần. Mặc dù có thể nhận được tập hợp kết quả bằng một truy vấn sử dụng GROUP BY, nhưng một câu lệnh trả về tập kết quả ĐÚNG sẽ rất tẻ nhạt. Bạn có thể chỉ định MIN(distance) ... GROUP BY doctor_id
và điều đó sẽ giúp bạn có khoảng cách nhỏ nhất, NHƯNG không có gì đảm bảo rằng các biểu thức không tổng hợp khác trong danh sách CHỌN sẽ là từ hàng có khoảng cách tối thiểu, chứ không phải từ một số hàng khác. (MySQL tự do một cách nguy hiểm đối với GROUP BY và các tổng hợp. Để công cụ MySQL thận trọng hơn (và phù hợp với các công cụ cơ sở dữ liệu quan hệ khác), SET sql_mode = ONLY_FULL_GROUP_BY
Phụ lục 2:
Các vấn đề về hiệu suất được Darious báo cáo "một số truy vấn mất 7 giây".
Để tăng tốc độ, bạn có thể muốn lưu kết quả của hàm vào bộ nhớ cache. Về cơ bản, hãy xây dựng một bảng tra cứu. ví dụ:
CREATE TABLE office_location_distance
( office_location_id INT UNSIGNED NOT NULL COMMENT 'PK, FK to office_location.id'
, zipcode_id INT UNSIGNED NOT NULL COMMENT 'PK, FK to zipcode.id'
, gc_distance DECIMAL(18,2) COMMENT 'calculated gc distance, in miles'
, PRIMARY KEY (office_location_id, zipcode_id)
, KEY (zipcode_id, gc_distance, office_location_id)
, CONSTRAINT distance_lookup_office_FK
FOREIGN KEY (office_location_id) REFERENCES office_location(id)
ON UPDATE CASCADE ON DELETE CASCADE
, CONSTRAINT distance_lookup_zipcode_FK
FOREIGN KEY (zipcode_id) REFERENCES zipcode(id)
ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=InnoDB
Đó chỉ là một ý tưởng. (Tôi cho rằng bạn đang tìm kiếm khoảng cách office_location từ một mã zip cụ thể, vì vậy chỉ mục trên (zipcode, gc_distance, office_location_id) là chỉ mục bao hàm mà truy vấn của bạn sẽ cần. (Tôi sẽ tránh lưu trữ khoảng cách được tính toán dưới dạng FLOAT, do kém hiệu suất truy vấn với kiểu dữ liệu FLOAT)
INSERT INTO office_location_distance (office_location_id, zipcode_id, gc_distance)
SELECT d.office_location_id
, d.zipcode_id
, d.gc_distance
FROM (
SELECT l.id AS office_location_id
, z.id AS zipcode_id
, ROUND( <glorious_great_circle_calculation> ,2) AS gc_distance
FROM office_location l
CROSS
JOIN zipcode z
ORDER BY 1,3
) d
ON DUPLICATE KEY UPDATE gc_distance = VALUES(gc_distance)
Với các kết quả hàm được lưu vào bộ nhớ cache và lập chỉ mục, các truy vấn của bạn sẽ nhanh hơn nhiều.
SELECT d.gc_distance, o.*
FROM office_location o
JOIN office_location_distance d ON d.office_location_id = o.id
WHERE d.zipcode_id = 63101
AND d.gc_distance <= 100.00
ORDER BY d.zipcode_id, d.gc_distance
Tôi đang do dự về việc thêm một vị từ HAVING trên INSERT / UPDATE vào bảng cache; (nếu bạn đã sai vĩ độ / kinh độ và đã tính toán sai khoảng cách dưới 100 dặm; một lần chạy tiếp theo sau khi vĩ độ / kinh độ được cố định và khoảng cách tính đến 1000 dặm ... nếu hàng bị loại khỏi truy vấn, thì hàng hiện có trong bảng bộ nhớ cache sẽ không được cập nhật. (Bạn có thể xóa bảng bộ nhớ cache, nhưng điều đó không thực sự cần thiết, đó chỉ là rất nhiều công việc bổ sung cho cơ sở dữ liệu và nhật ký. Nếu tập hợp kết quả của truy vấn bảo trì quá lớn, nó có thể được chia nhỏ để chạy lặp đi lặp lại cho từng mã zip hoặc từng office_location.)
Mặt khác, nếu bạn không quan tâm đến bất kỳ khoảng cách nào trên một giá trị nhất định, bạn có thể thêm HAVING gc_distance <
và cắt giảm đáng kể kích thước của bảng bộ nhớ cache.