2012-04-05 36 views
6

Đây là SQL của tôi:Làm cách nào để tạo dữ liệu trong MySQL?

SELECT 
    COUNT(id), 
    CONCAT(YEAR(created_at), '-', MONTH(created_at), '-', DAY(created_at)) 
FROM my_table 
GROUP BY YEAR(created_at), MONTH(created_at), DAY(created_at) 

Tôi muốn có một hàng để hiển thị ngay cả trong nhiều ngày mà không có ID được tạo. Ngay bây giờ tôi đang thiếu một tấn ngày cho những ngày không có hoạt động.

Bất kỳ suy nghĩ nào về cách thay đổi truy vấn này để thực hiện điều đó?

Trả lời

1

Cách để làm điều đó trong một truy vấn:

SELECT COUNT(my_table.id) AS total, 
CONCAT(YEAR(dates.ddate), '-', MONTH(dates.ddate), '-', DAY(dates.ddate)) 
FROM (
    -- Creates "on the fly" 65536 days beginning from 2000-01-01 (179 years) 
    SELECT DATE_ADD("2000-01-01", INTERVAL (b1.b + b2.b + b3.b + b4.b + b5.b + b6.b + b7.b + b8.b + b9.b + b10.b + b11.b + b12.b + b13.b + b14.b + b15.b + b16.b) DAY) AS ddate FROM 
    (SELECT 0 AS b UNION SELECT 1) b1, 
    (SELECT 0 AS b UNION SELECT 2) b2, 
    (SELECT 0 AS b UNION SELECT 4) b3, 
    (SELECT 0 AS b UNION SELECT 8) b4, 
    (SELECT 0 AS b UNION SELECT 16) b5, 
    (SELECT 0 AS b UNION SELECT 32) b6, 
    (SELECT 0 AS b UNION SELECT 64) b7, 
    (SELECT 0 AS b UNION SELECT 128) b8, 
    (SELECT 0 AS b UNION SELECT 256) b9, 
    (SELECT 0 AS b UNION SELECT 512) b10, 
    (SELECT 0 AS b UNION SELECT 1024) b11, 
    (SELECT 0 AS b UNION SELECT 2048) b12, 
    (SELECT 0 AS b UNION SELECT 4096) b13, 
    (SELECT 0 AS b UNION SELECT 8192) b14, 
    (SELECT 0 AS b UNION SELECT 16384) b15, 
    (SELECT 0 AS b UNION SELECT 32768) b16 
) dates 
LEFT JOIN my_table ON dates.ddate = my_table.created_at 
GROUP BY dates.ddate 
ORDER BY dates.ddate 

Mã tiếp theo là chỉ cần thiết nếu bạn muốn thử nghiệm và không có "my_table" chỉ về câu hỏi:

create table `my_table` (
    `id` int (11), 
    `created_at` date 
); 
insert into `my_table` (`id`, `created_at`) values('1','2000-01-01'); 
insert into `my_table` (`id`, `created_at`) values('2','2000-01-01'); 
insert into `my_table` (`id`, `created_at`) values('3','2000-01-01'); 
insert into `my_table` (`id`, `created_at`) values('4','2001-01-01'); 
insert into `my_table` (`id`, `created_at`) values('5','2100-06-06'); 
9

SQL nổi tiếng là xấu khi trả lại dữ liệu không có trong cơ sở dữ liệu. Bạn có thể tìm thấy giá trị bắt đầu và kết thúc cho khoảng trống ngày, nhưng nhận được tất cả các ngày là khó khăn.

Giải pháp là tạo bảng lịch với một bản ghi cho mỗi ngày và OUTER JOIN nó vào truy vấn của bạn.

Dưới đây là một ví dụ giả định created_at đó là loại NGÀY:

SELECT calendar_date, COUNT(`id`) 
FROM calendar LEFT OUTER JOIN my_table ON calendar.calendar_date = my_table.created_at 
GROUP BY calendar_date 

(tôi đoán rằng created_at thực sự là DATETIME, vì vậy bạn sẽ phải làm một chút thể dục để JOIN bảng).

+1

Đây thực sự là lựa chọn duy nhất của bạn trừ khi bạn có thể tạo những mục bị thiếu trong mã của bạn sau khi sele cting hồ sơ bạn có. Lưu ý rằng mặc dù bạn sẽ phải giữ bảng calendar_date này chứa đầy các ngày và hy vọng rằng bạn không quên thêm nhiều hơn bạn hiện đang cần. (Bạn sẽ đi bao nhiêu năm trong tương lai?) Cá nhân tôi không thích ý tưởng này vì nó cũng hạn chế bạn nhóm lại theo khoảng thời gian bạn đã chọn. Điều gì xảy ra nếu ngày mai bạn muốn hiển thị những thứ được nhóm theo Giờ? – Vyrotek

+1

Để rõ ràng, trên thực tế, không có giải pháp tốt cho vấn đề này khi sử dụng SQL. –

+0

Các tệp lịch rất hữu ích cho một loạt các sự kiện (đặc biệt là trong các tình huống bán lẻ, nơi mà lịch tài chính không phải lúc nào cũng lập bản đồ cho một bộ tài chính), bao gồm vấn đề cụ thể này. Bạn có thể tạo các câu lệnh ảo trong câu lệnh ... với CTE đệ quy (không có trong mySQL). –

7

ý tưởng chung

Có hai phương pháp chính để tạo ra dữ liệu trong MySQL. Một là tạo ra dữ liệu khi đang chạy truy vấn và một cái khác là để có nó trong cơ sở dữ liệu và sử dụng nó khi cần thiết. Tất nhiên, thứ hai sẽ nhanh hơn lần đầu tiên nếu bạn định chạy truy vấn của bạn thường xuyên. Tuy nhiên, thứ hai sẽ yêu cầu một bảng trong cơ sở dữ liệu mà chỉ có mục đích sẽ là để tạo ra các dữ liệu bị thiếu. Nó cũng sẽ yêu cầu bạn có đủ đặc quyền để tạo bảng đó.

động sinh dữ liệu

Cách tiếp cận này liên quan đến việc làm UNION s để tạo ra một bảng giả có thể được sử dụng để tham gia bảng thực tế với. Truy vấn khủng khiếp và lặp đi lặp lại là:

select aDate from (
    select @maxDate - interval (a.a+(10*b.a)+(100*c.a)+(1000*d.a)) day aDate from 
    (select 0 as a union all select 1 union all select 2 union all select 3 
    union all select 4 union all select 5 union all select 6 union all 
    select 7 union all select 8 union all select 9) a, /*10 day range*/ 
    (select 0 as a union all select 1 union all select 2 union all select 3 
    union all select 4 union all select 5 union all select 6 union all 
    select 7 union all select 8 union all select 9) b, /*100 day range*/ 
    (select 0 as a union all select 1 union all select 2 union all select 3 
    union all select 4 union all select 5 union all select 6 union all 
    select 7 union all select 8 union all select 9) c, /*1000 day range*/ 
    (select 0 as a union all select 1 union all select 2 union all select 3 
    union all select 4 union all select 5 union all select 6 union all 
    select 7 union all select 8 union all select 9) d, /*10000 day range*/ 
    (select @minDate := '2001-01-01', @maxDate := '2002-02-02') e 
) f 
where aDate between @minDate and @maxDate 

Dù sao, nó đơn giản hơn vẻ bề ngoài. Nó làm cho các sản phẩm Descartes của các bảng có nguồn gốc với 10 giá trị số do đó kết quả sẽ có 10^X hàng trong đó X là số lượng các bảng có nguồn gốc trong truy vấn. Trong ví dụ này, có 10000 phạm vi ngày để bạn có thể thể hiện thời gian trên 27 năm. Nếu bạn cần thêm, hãy thêm một số khác UNION vào truy vấn và cập nhật khoảng thời gian và nếu bạn không cần quá nhiều, bạn có thể xóa UNION s hoặc giá trị riêng lẻ khỏi các bảng có nguồn gốc. Chỉ cần làm rõ, bạn có thể tinh chỉnh khoảng thời gian bằng cách áp dụng bộ lọc với mệnh đề WHERE trên các biến số @minDate@maxDate (nhưng không sử dụng khoảng thời gian dài hơn biến mà bạn đã tạo bằng sản phẩm Descartes).

tĩnh thế hệ dữ liệu

Giải pháp này sẽ yêu cầu bạn tạo một bảng trong cơ sở dữ liệu của bạn. Cách tiếp cận này tương tự như cách tiếp cận trước. Trước tiên, bạn sẽ phải chèn dữ liệu vào bảng đó: một dải các số nguyên khác nhau từ 1 đến X trong đó X là phạm vi cần thiết tối đa.Một lần nữa, nếu bạn không chắc chắn chỉ cần chèn 100000 giá trị và bạn sẽ có thể tạo phạm vi ngày trong hơn 273 năm. Vì vậy, một khi bạn đã có những dãy số nguyên, bạn có thể biến nó thành một phạm vi ngày như thế này:

select '2012-01-01' + interval value - 1 day aDay from seq 
having aDay <= '2012-01-05' 

Giả sử một bảng tên seq với một cột tên là value. Ở trên cùng ngày từ ngày và ở dưới cùng ngày đến.

Biến này vào một cái gì đó hữu ích

Ok, bây giờ chúng tôi có thời gian cập nhật của chúng tôi được tạo ra nhưng chúng tôi vẫn mất tích một cách để truy vấn dữ liệu và hiển thị các giá trị bị mất như một thực tế 0. Đây là nơi mà left join đến để giải cứu. Để đảm bảo chúng ta đều trên cùng một trang, left join tương tự như một inner join nhưng chỉ có một khác biệt: nó sẽ lưu giữ tất cả các bản ghi từ bảng bên trái của kết nối, bất kể có bản ghi trùng khớp trên bảng bên phải. Nói cách khác, inner join sẽ xóa tất cả các hàng không phù hợp khi tham gia trong khi left join sẽ giữ các hàng trên bảng bên trái và, đối với các bản ghi bên trái không có bản ghi khớp trên bảng bên phải, left join sẽ điền vào "không gian" với giá trị null. Vì vậy, chúng ta nên tham gia bảng miền của chúng tôi (bảng có dữ liệu "bị thiếu") với bảng mới được tạo của chúng tôi đưa bảng thứ hai vào phần bên trái của liên kết và phần trước ở bên phải, để tất cả các yếu tố được xem xét, bất kể sự hiện diện của chúng trong bảng tên miền.

Ví dụ, nếu chúng ta có một bảng domainTable với các lĩnh vực ID, birthDate và chúng tôi muốn thấy một số lượng của tất cả các birthDate trong 5 ngày đầu tiên của 2012 mỗi ngày và nếu tính là 0 để hiển thị giá trị đó, thì đây truy vấn có thể được chạy:

select allDays.aDay, count(dt.id) from (
    select '2012-01-01' + interval value - 1 day aDay from seq 
    having aDay <= '2012-01-05' 
) allDays 
left join domainTable dt on allDays.aDay = dt.birthDate 
group by allDays.aDay 

này tạo ra một bảng có nguồn gốc với tất cả các ngày requried (chú ý tôi đang sử dụng thế hệ dữ liệu tĩnh) và thực hiện một left join chống lại bảng miền của chúng tôi, vì vậy tất cả các ngày sẽ được hiển thị, không phân biệt về việc liệu họ có một giá trị phù hợp trong bảng miền của chúng tôi hay không. Cũng lưu ý rằng count nên được thực hiện trên trường có giá trị null vì những giá trị này không được tính.

Thuyết minh được coi

1) Các truy vấn có thể được sử dụng để truy vấn các khoảng khác (tháng, năm) thực hiện những thay đổi nhỏ đối với mã

2) Thay vì thể xác định rõ số ngày bạn có thể truy vấn cho minmax giá trị từ các bảng miền như thế này:

select (select min(aDate) from domainTable) + interval value - 1 day aDay 
from seq 
having aDay <= (select max(aDate) from domainTable) 

Điều này sẽ tránh tạo nhiều hồ sơ hơn mức cần thiết.

Trên thực tế trả lời câu hỏi của bạn

Tôi nghĩ bạn nên đã tìm ra cách để làm những gì bạn muốn. Dù sao, đây là các bước để những người khác có thể hưởng lợi từ họ quá.Đầu tiên, tạo bảng số nguyên . Thứ hai, hãy chạy truy vấn này:

select allDays.aDay, count(mt.id) aCount from (
    select (select date(min(created_at)) from my_table) + interval value - 1 day aDay 
    from seq s 
    having aDay <= (select date(max(created_at)) from my_table) 
) allDays 
left join my_table mt on allDays.aDay = date(mt.created_at) 
group by allDays.aDay 

Tôi đoán created_at là ngày giờ và đó là lý do bạn kết nối theo cách đó. Tuy nhiên, điều đó xảy ra là cách MySQL tự nhiên lưu trữ ngày, vì vậy tôi chỉ nhóm theo trường ngày nhưng hãy đúc created_at thành kiểu dữ liệu thực tế date. Bạn có thể chơi với nó bằng cách sử dụng này fiddle.

Và đây là các dữ liệu giải pháp tạo động:

select allDays.aDay, count(mt.id) aCount from (
    select @maxDate - interval a.a day aDay from 
    (select 0 as a union all select 1 union all select 2 union all select 3 
    union all select 4 union all select 5 union all select 6 union all 
    select 7 union all select 8 union all select 9) a, /*10 day range*/ 
    (select @minDate := (select date(min(created_at)) from my_table), 
      @maxDate := (select date(max(created_at)) from my_table)) e 
    where @maxDate - interval a.a day between @minDate and @maxDate 
) allDays 
left join my_table mt on allDays.aDay = date(mt.created_at) 
group by allDays.aDay 

Như bạn có thể nhìn thấy bộ xương của truy vấn là giống như trước đó. Điều duy nhất thay đổi là cách bảng bắt nguồn allDays được tạo ra. Bây giờ, cách mà bảng dẫn xuất được tạo ra cũng hơi khác so với cái mà tôi đã thêm trước đây. Điều này là do trong danh sách ví dụ, tôi chỉ cần một phạm vi ngày 10. Như bạn có thể thấy, nó dễ đọc hơn là thêm một phạm vi ngày 1000. Đây là fiddle cho giải pháp động để bạn có thể chơi với nó.

Hy vọng điều này sẽ hữu ích!

+1

Woah - siêu toàn diện. Có thể tạo một bảng tạm thời để truy vấn trong một câu lệnh không? –

+0

Bạn có thể [tạo các bảng tạm thời] (http://dev.mysql.com/doc/refman/5.6/en/create-table.html) trong một câu lệnh. Tuy nhiên, tôi không nghĩ rằng điều này sẽ hữu ích bởi vì (những gì tôi nghĩ rằng bạn đang lập kế hoạch để làm), bạn sẽ tạo ra bảng, cư nó, truy vấn nó và sau đó loại bỏ nó.Sẽ tốt hơn nếu đã có bảng được điền hoặc sử dụng một bảng dẫn xuất (như trong phương thức động: 'select * from (derived_table) dt left join ...') bởi vì phần đắt tiền của thủ tục là dân số của bàn. –

0

Testbed:

create table testbed (id integer, created_at date); 
insert into testbed values 
     (1, '2012-04-01'), 
     (1, '2012-04-30'), 
     (2, '2012-04-02'), 
     (3, '2012-04-03'), 
     (3, '2012-04-04'), 
     (4, '2012-04-04'); 

tôi cũng sử dụng any_table, mà tôi tạo ra nhân tạo như thế này:

create table any_table (id integer); 
insert into any_table values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10); 
insert into any_table select * from any_table; -- repeat this insert 7-8 times 

Bạn có thể sử dụng bất kỳ bảng trong cơ sở dữ liệu của bạn cũng được dự kiến ​​sẽ có nhiều hàng thì max(created_dt) - min(created_dt) phạm vi , ít nhất 365 để trang trải một năm.

Query:

SELECT concat(year(dr._date),'-',month(dr._date),'-',day(dr._date)), 
     -- or, instead of concat(), simply: dr._date 
     count(id) 
    FROM (
     SELECT date_add(r.mindt, INTERVAL @dist day) _date, 
       @dist := @dist + 1 AS days_away 
      FROM any_table t 
      JOIN (SELECT min(created_at) mindt, 
         max(created_at) maxdt, 
         @dist := 0 
        FROM testbed) r 
     WHERE date_add(r.mindt, INTERVAL @dist day) <= r.maxdt) dr 
    LEFT JOIN testbed tb ON dr._date = tb.created_at 
GROUP BY dr._date; 
Các vấn đề liên quan