2011-07-27 36 views
5

Với các hồ sơ sau (hàng đầu tiên là tên cột):Làm thế nào để chọn bản ghi duy nhất theo cột với ActiveRecord và PostgreSQL

name    platform   other_columns  date 
Eric    Ruby    something   somedate 
Eric    Objective-C  something   somedate 
Joe    Ruby    something   somedate 

Làm thế nào để lấy một kỷ lục số ít với tất cả các cột, chẳng hạn rằng cột tên luôn luôn duy nhất trong tập kết quả? Tôi muốn truy vấn trong ví dụ này để trả về bản ghi Eric (w/Ruby) đầu tiên.

Tôi nghĩ rằng gần nhất tôi đã nhận là sử dụng "chọn riêng biệt trên (tên) * ...", nhưng điều đó yêu cầu tôi đặt hàng theo tên trước, khi tôi thực sự muốn đặt hàng các bản ghi theo cột ngày .

  • thứ tự hồ sơ theo ngày
  • Nếu có nhiều hồ sơ có cùng tên, chọn một (mà không quan trọng)
  • Chọn tất cả các cột

Làm thế nào để đạt được điều này trong Rails trên PostgreSQL?

Trả lời

0

Nhận danh sách tên và ngày tối thiểu và tham gia lại vào bảng gốc để lấy hàng bạn đang tìm kiếm.

select 
    b.* 
from 
    (select name, min(date) as mindate from table group by name) a 
    inner join table b 
     on a.name = b.name and a.mindate = b.date 
+0

Điều này có các vấn đề về tính duy nhất nếu cặp 'name, min (date)' xuất hiện hai lần trong bảng. –

2

tôi bạn không quan tâm mà hàng được lấy ra khi nhiều tên đang có (điều này sẽ đúng đối với tất cả các cột) và bảng có cấu trúc đơn giản bạn chỉ có thể làm một truy vấn như

SELECT * FROM table_name GROUP BY `name` ORDER BY `date` 

hoặc trong Rails

TableClass.group(:name).order(:date) 
+0

Khi tôi làm phương pháp đó, tôi nhận được lỗi sau: cột "games.id" phải xuất hiện trong mệnh đề GROUP BY hoặc được sử dụng trong hàm tổng hợp –

+1

Thay vì downvoting, giải thích cấu trúc của bạn tốt hơn, từ câu hỏi của bạn có vẻ như bạn có một bảng duy nhất, điều này không đúng vì thông báo lỗi đó. Thay vào đó, hãy đăng cấu trúc hoàn chỉnh của bạn. – Fabio

+0

+1, để xóa điều này không thể hiểu được -1 – apneadiving

7

bạn không thể làm một cách đơn giản .group(:name) vì đó tạo ra một GROUP BY name trong SQL của bạn khi bạn sẽ chọn ungrouped và unaggregate d cột, rằng lá mơ hồ như mà hàng để chọn và PostgreSQL (rightly IMHO) complains:

When GROUP BY is present, it is not valid for the SELECT list expressions to refer to ungrouped columns except within aggregate functions, since there would be more than one possible value to return for an ungrouped column.

Nếu bạn bắt đầu thêm nhiều cột để nhóm của bạn với một cái gì đó như thế này:

T.group(T.columns.collect(&:name)) 

sau đó bạn sẽ được nhóm bởi những điều bạn không muốn và cuối cùng bạn sẽ rút toàn bộ bảng và đó không phải là điều bạn muốn. Nếu bạn cố gắng tổng hợp để tránh vấn đề nhóm, bạn sẽ kết thúc trộn các hàng khác nhau (tức là một cột sẽ đến từ một hàng trong khi một cột khác sẽ đến từ một số hàng khác) và đó không phải là thứ bạn muốn.

ActiveRecord thực sự không được xây dựng cho loại điều này nhưng bạn có thể uốn cong nó theo ý muốn của bạn với một số nỗ lực.

Bạn đang sử dụng AR để bạn có thể có cột id. Nếu bạn có PostgreSQL 8.4 hoặc cao hơn, sau đó bạn có thể sử dụng window functions làm một loại GROUP BY được bản địa hoá; bạn sẽ cần phải cửa sổ hai lần: một lần để tìm ra các cặp name/thedate và một lần nữa để chỉ chọn một id (chỉ trong trường hợp bạn có nhiều hàng với cùng một namethedate khớp với sớm nhất thedate) và do đó nhận được một hàng duy nhất :

select your_table.* 
from your_table 
where id in (
    -- You don't need DISTINCT here as the IN will take care of collapsing duplicates. 
    select min(yt.id) over (partition by yt.name) 
    from (
     select distinct name, min(thedate) over (partition by name) as thedate 
     from your_table 
    ) as dt 
    join your_table as yt 
     on yt.name = dt.name and yt.thedate = dt.thedate 
) 

Sau đó, bọc trong find_by_sql và bạn có đối tượng của mình.

Nếu bạn đang sử dụng Heroku với cơ sở dữ liệu được chia sẻ (hoặc một số môi trường khác không có 8.4 hoặc cao hơn), thì bạn bị kẹt với PostgreSQL 8.3 và bạn sẽ không có chức năng cửa sổ. Trong trường hợp đó, bạn có thể muốn để lọc ra các bản sao trong Ruby-đất:

with_dups = YourTable.find_by_sql(%Q{ 
    select yt.* 
    from your_table yt 
    join (select name, min(thedate) as thedate from your_table group by name) as dt 
     on yt.name = dt.name and yt.thedate = dt.thedate 
}); 

# Clear out the duplicates, sorting by id ensures consistent results 
unique_matches = with_dups.sort_by(&:id).group_by(&:name).map { |x| x.last.first } 

Nếu bạn khá chắc chắn rằng sẽ không có trùng lặp name/min(thedate) cặp sau đó là giải pháp 8.3 tương thích với sức mạnh là đặt cược tốt nhất của bạn; nhưng, nếu có rất nhiều bản sao, thì bạn muốn cơ sở dữ liệu thực hiện càng nhiều công việc càng tốt để tránh tạo hàng nghìn đối tượng AR mà bạn sắp bỏ đi.

Có thể một người nào đó có mạnh mẽ hơn PostgreSQL-Fu hơn tôi sẽ đến và cung cấp một cái gì đó đẹp hơn.

+0

+1 để có câu trả lời tuyệt vời! – apneadiving

+0

@apneadiving: Tôi kinda 'đã phải sau khi "thách thức" của bạn :) –

+0

Câu trả lời này cuối cùng đã giúp tôi hiểu những gì đang xảy ra bên trong PostgreSQL cho loại truy vấn này. Cảm ơn các câu trả lời chi tiết. –

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