[英]Fetch range from days
我有這個表結構:
編輯更復雜的例子:添加隱藏范圍
category| day | a |
--------|------------|-------|
1 | 2012-01-01 | 4 |
1 | 2012-01-02 | 4 |
1 | 2012-01-03 | 4 |
1 | 2012-01-04 | 4 |
1 | 2012-01-05 | 5 |
1 | 2012-01-06 | 5 |
1 | 2012-01-07 | 5 |
1 | 2012-01-08 | 4 |
1 | 2012-01-09 | 4 |
1 | 2012-01-10 | 4 |
1 | 2012-01-11 | 5 |
1 | 2012-01-12 | 5 |
1 | 2012-01-16 | 5 |
1 | 2012-01-17 | 5 |
1 | 2012-01-18 | 5 |
1 | 2012-01-19 | 5 |
...
'category-day'作為唯一鍵。 我會根據列“a”和給定的限制范圍為每個類別提取一系列日期,如下所示:
1,2012-01-01|2012-01-04,4
1,2012-01-05|2012-01-07,5
1,2012-01-08|2012-01-10,4
1,2012-01-11|2012-01-12,5
1,2012-01-13|2012-01-15,0
1,2012-01-16|2012-01-19,5
或類似的。
我搜索最好的方法來做到這一點。 最好只用mysql而且還帶一點點php。
注1:不是全天都插入:兩天之內非連續性不可能是其他日子。 在這種情況下,我將輸出錯過的范圍,列“a”= 0。
注意2:我用一個簡單的查詢和一些PHP行,但我不喜歡它,因為我的簡單算法需要一個周期,每個范圍內的每個類別都會找到。 如果范圍太大而且類別太多,那就不太好了。
最終編輯:好的! 閱讀完所有評論和答案后,我認為不存在有效,高效且同時可讀的解決方案。 所以Mosty Mostacho的答案是100%有效的解決方案,但它有100%有效的建議。 謝謝你們。
新編輯:
正如我在評論中告訴你的那樣,我強烈建議你使用快速查詢,然后在PHP中處理缺少的日期,因為它會更快,更易讀:
select
concat(@category := category, ',', min(day)) col1,
concat(max(day), ',', @a := a) col2
from t, (select @category := '', @a := '', @counter := 0) init
where @counter := @counter + (category != @category or a != @a)
group by @counter, category, a
但是,如果您仍想使用查詢版本,請嘗試以下操作:
select
@counter := @counter + (category != @category or a != @a) counter,
concat(@category := category, ',', min(day)) col1,
concat(max(day), ',', @a := a) col2
from (
select distinct s.day, s.category, coalesce(t1.a, 0) a
from (
select (select min(day) from t) + interval val - 1 day day, c.category
from seq s, (select distinct category from t) c
having day <= (select max(day) from t)
) s
left join t t1 on s.day = t1.day and s.category = t1.category
where s.day between (
select min(day) from t t2
where s.category = t2.category) and (
select max(day) from t t2
where s.category = t2.category)
order by s.category, s.day
) t, (select @category := '', @a := '', @counter := 0) init
group by counter, category, a
order by category, min(day)
需要注意的是MySQL不會允許你動態創建的數據,除非你硬編碼UNIONS
,為例子 。 這是一個昂貴的過程,這就是為什么我強烈建議你創建一個只有integer
字段的表,其中值為1
到X
,其中X
是, 至少是分隔min(day)
和max(day)
的最大日期數量從你的桌子。 如果您不確定該日期,只需添加100,000
數字,您就可以生成超過200年的測距期。 在上一個查詢中,此表是seq
,它具有的列是val
。
這導致:
+--------------+--------------+ | COL1 | COL2 | +--------------+--------------+ | 1,2012-01-01 | 2012-01-04,4 | | 1,2012-01-05 | 2012-01-07,5 | | 1,2012-01-08 | 2012-01-10,4 | | 1,2012-01-11 | 2012-01-12,5 | | 1,2012-01-13 | 2012-01-15,0 | | 1,2012-01-16 | 2012-01-19,5 | +--------------+--------------+
好吧,我在說謊。 結果實際上是返回一個counter
列。 只是忽略它,因為刪除它(使用派生表)會更低性能!
這里有一個單行的暴行:)(注意:更改“datt”表名。)
select dd.category,
dd.day as start_day,
(select dp.day from
(
select 1 as n,d1.category,d1.day,d1.a from datt d1 where not exists (
select * from datt where day = d1.day - INTERVAL 1 DAY and a=d1.a
)
union
select 2 as n,d1.category,d1.day,d1.a from datt d1 where not exists (
select * from datt where day = d1.day + INTERVAL 1 DAY and a=d1.a
)
) dp where dp.day >= dd.day - INTERVAL (n-2) DAY order by day asc limit 0,1)
as end_day,
dd.a from (
select 1 as n,d1.category,d1.day,d1.a from datt d1 where not exists (
select * from datt where day = d1.day - INTERVAL 1 DAY and a=d1.a
)
union
select 2 as n,d1.category,d1.day,d1.a from datt d1 where not exists (
select * from datt where day = d1.day + INTERVAL 1 DAY and a=d1.a
)
) dd
where n=1
它的輸出是:
|| 1 || 2012-01-01 || 2012-01-01 || 4 ||
|| 1 || 2012-01-03 || 2012-01-04 || 4 ||
|| 1 || 2012-01-05 || 2012-01-07 || 5 ||
|| 1 || 2012-01-08 || 2012-01-10 || 4 ||
|| 1 || 2012-01-11 || 2012-01-12 || 5 ||
注意:這是01-12天表中不存在的2012-01-02的結果。
不需要PHP或臨時表或任何東西。
免責聲明:我這樣做只是為了好玩 。 這種特技可能太瘋狂,無法在生產環境中使用。 因此,我不會將此作為“真正的”解決方案發布。 此外,我不願意解釋它是如何工作的:)我並沒有重新思考/重構它。 可能有更優雅的方式,名稱/別名可以提供更多信息。 所以請不要火焰或任何東西。
這是我的解決方案。 看起來比它復雜。 我認為比其他答案更容易理解,沒有冒犯:)
設置測試數據:
drop table if exists test;
create table test(category int, day date, a int);
insert into test values
(1 , '2012-01-01' , 4 ),
(1 , '2012-01-02' , 4 ),
(1 , '2012-01-03' , 4 ),
(1 , '2012-01-04' , 4 ),
(1 , '2012-01-05' , 5 ),
(1 , '2012-01-06' , 5 ),
(1 , '2012-01-07' , 5 ),
(1 , '2012-01-08' , 4 ),
(1 , '2012-01-09' , 4 ),
(1 , '2012-01-10' , 4 ),
(1 , '2012-01-11' , 5 ),
(1 , '2012-01-12' , 5 ),
(1 , '2012-01-16' , 5 ),
(1 , '2012-01-17' , 5 ),
(1 , '2012-01-18' , 5 ),
(1 , '2012-01-19' , 5 );
它來了:
SELECT category, MIN(`day`) AS firstDayInRange, max(`day`) AS lastDayInRange, a
, COUNT(*) as howMuchDaysInThisRange /*<-- as a little extra*/
FROM
(
SELECT
IF(@prev != qr.a, @is_a_changing:=@is_a_changing+1, @is_a_changing) AS is_a_changing, @prev:=qr.a, qr.* /*See if column a has changed. If yes, increment, so we can GROUP BY it later*/
FROM
(
SELECT
test.category, q.`day`, COALESCE(test.a, 0) AS a /*When there is no a, replace NULL with 0*/
FROM
test
RIGHT JOIN
(
SELECT
DATE_SUB(CURDATE(), INTERVAL number_days DAY) AS `day` /*<-- Create dates from now back 999 days. This query is surprisingly fast. And adding more numbers to create more dates, i.e. 10000 dates is also no problem. Therefor a temporary dates table might not be necessary?*/
FROM
(
SELECT (a + 10*b + 100*c) AS number_days 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) aa
, (SELECT 0 AS b 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) bb
, (SELECT 0 AS c 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) cc
)sq /*<-- This generates numbers 0 to 999*/
)q USING(`day`)
, (SELECT @is_a_changing:=0, @prev:=0) r
/*This WHERE clause is just to beautify. It may not be necessary*/
WHERE q.`day` >= (SELECT MIN(test.`day`) FROM test) AND q.`day` <= (SELECT MAX(test.`day`) FROM test)
)qr
)asdf
GROUP BY is_a_changing
ORDER BY 2
結果如下所示:
category firstDayInRange lastDayInRange a howMuchDaysInThisRange
--------------------------------------------------------------------------
1 2012-01-01 2012-01-04 4 4
1 2012-01-05 2012-01-07 5 3
1 2012-01-08 2012-01-10 4 3
1 2012-01-11 2012-01-12 5 2
2012-01-13 2012-01-15 0 3
1 2012-01-16 2012-01-19 5 4
首先,這是@Mosty解決方案的擴展。
為了使Mosty的解決方案能夠包括類別/日期組合而不是表中沒有,我采取了以下方法 -
首先獲取一個不同的類別列表,然后將其加入整個日期范圍 -
SELECT category, `start` + INTERVAL id DAY AS `day`
FROM dummy,(SELECT DISTINCT category FROM t) cats, (SELECT MIN(day) `start`, MAX(day) `end` FROM t) tmp
WHERE id <= DATEDIFF(`end`, `start`)
ORDER BY category, `day`
上面的查詢使用帶有單個字段id
的表dummy
來構建完整的日期范圍。 id
字段包含0,1,2,3,.... - 它需要有足夠的值來覆蓋所需日期范圍內的每一天。 然后可以將其連接回原始表,以創建所有日期的所有類別的完整列表以及 - 的適當值 -
SELECT cj.category, cj.`day`, IFNULL(t.a, 0) AS a
FROM (
SELECT category, `start` + INTERVAL id DAY AS `day`
FROM dummy,(SELECT DISTINCT category FROM t) cats, (SELECT MIN(day) `start`, MAX(day) `end` FROM t) tmp
WHERE id <= DATEDIFF(`end`, `start`)
ORDER BY category, `day`
) AS cj
LEFT JOIN t
ON cj.category = t.category
AND cj.`day` = t.`day`
然后可以將其應用於Mosty的查詢來代替表格t
-
SELECT
CONCAT(@category := category, ',', MIN(`day`)) col1,
CONCAT(MAX(`day`), ',', @a := a) col2
FROM (
SELECT cj.category, cj.day, IFNULL(t.a, 0) AS a
FROM (
SELECT category, `start` + INTERVAL id DAY AS `day`
FROM dummy,(SELECT DISTINCT category FROM t) cats, (SELECT MIN(day) `start`, MAX(day) `end` FROM t) tmp
WHERE id <= DATEDIFF(`end`, `start`)
ORDER BY category, `day`
) AS cj
LEFT JOIN t
ON cj.category = t.category
AND cj.`day` = t.day) AS t, (select @category := '', @a := '', @counter := 0) init
WHERE @counter := @counter + (category != @category OR a != @a)
GROUP BY @counter, category, a
完全在mysql方面將有性能adv:一旦創建了程序,它將在0.35 - 0.37秒內運行
create procedure fetch_range()
begin
declare min date;
declare max date;
create table testdate(
date1 date
);
select min(day) into min
from category;
select max(day) into max
from category;
while min <= max do
insert into testdate values(min);
set min = adddate(min,1);
end while;
select concat(category,',',min(day)),concat(max(day),',',a)
from(
SELECT if(isNull(category),@category,category) category,if(isNull(day),date1,day) day,@a,if(isNull(a) || isNull(@a),if(isNull(a) && isNull(@a),@grp,@grp:=@grp+1),if(@a!=a,@grp:=@grp+1,@grp)) as sor_col,if(isNull(a),0,a) as a,@a:=a,@category:= category
FROM `category`
RIGHT JOIN testdate ON date1 = category.day) as table1
group by sor_col;
drop table testdate;
end
O / P:
1,2012-01-01|2012-01-04,4
1,2012-01-05|2012-01-07,5
1,2012-01-08|2012-01-10,4
1,2012-01-11|2012-01-12,5
1,2012-01-13|2012-01-15,0
1,2012-01-16|2012-01-19,5
這是mysql解決方案,它將提供所需的結果,僅排除錯過的范圍。
PHP:可以通過php添加缺少的范圍。
$sql = "set @a=0,@grp=0,@datediff=0,@category=0,@day='';";
mysql_query($sql);
$sql= "select category,min(day)min,max(day) max,a
from(
select category,day,a,concat(if(@a!=a,@grp:=@grp+1,@grp),if(datediff(@day,day) < -1,@datediff:=@datediff+1,@datediff)) as grp_datediff,datediff(@day,day)diff, @day:= day,@a:=a
FROM category
order by day)as t
group by grp_datediff";
$result = mysql_query($sql);
$diff = 0;
$indx =0;
while($row = mysql_fetch_object($result)){
if(isset($data[$indx - 1]['max'])){
$date1 = new DateTime($data[$indx - 1]['max']);
$date2 = new DateTime($row->min);
$diff = $date1->diff($date2);
}
if ($diff->days > 1) {
$date = new DateTime($data[$indx-1]['max']);
$interval = new DateInterval("P1D");
$min = $date->add($interval);
$date = new DateTime($data[$indx-1]['max']);
$interval = new DateInterval("P".$diff->days."D");
$max = $date->add($interval);
$data[$indx]['category'] = $data[$indx-1]['category'];
$data[$indx]['min'] = $min->format('Y-m-d');
$data[$indx]['max'] = $max->format('Y-m-d');
$data[$indx++]['a'] = 0;
$data[$indx]['category'] = $row->category;
$data[$indx]['min'] = $row->min;
$data[$indx]['max'] = $row->max;
$data[$indx]['a'] = $row->a;
}else{
$data[$indx]['category'] = $row->category;
$data[$indx]['min'] = $row->min;
$data[$indx]['max'] = $row->max;
$data[$indx]['a'] = $row->a;
}
$indx++;
}
要使其按照您的意願工作,您應該有兩個表:
通過FOREIGN KEY
每個時期可以有多天與之相關。 使用當前的表結構,您可以做的最好的事情是檢測PHP端的連續周期。
你是這個意思嗎?
SELECT
category,
MIN(t1.day),
MAX(t2.day),
a
FROM
`table` AS t1
INNER JOIN `table` AS t2 USING (category, a)
如果我理解你的問題,我會使用一些東西:
SELECT MAX(day), MIN(day) FROM `YourTable` WHERE `category`= $cat AND `A`= $increment;
......而且......
$dateRange = $cat.","."$min"."|"."$max".",".$increment;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.