简体   繁体   English

获取范围从天

[英]Fetch range from days

I have this table structure: 我有这个表结构:

EDIT more complex example: add hidden range 编辑更复杂的例子:添加隐藏范围

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   |
...

with 'category-day' as unique keys. 'category-day'作为唯一键。 I would extract a range of dates, for each category, according with column "a" and given limit range, like so: 我会根据列“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

or similar. 或类似的。

I search the best way for do it. 我搜索最好的方法来做到这一点。 Using only mysql preferably but also with a little bit of php. 最好只用mysql而且还带一点点php。

NOTE1: not all day are inserted: between two days non-contiguos could not be other days. 注1:不是全天都插入:两天之内非连续性不可能是其他日子。 In this case I would in output the missed range with column "a" = 0. 在这种情况下,我将输出错过的范围,列“a”= 0。

NOTE2: I did it with a simple query and some rows of php but I don't like it because my simple algorithm need a cycle for each day in range multiplied for each category found. 注意2:我用一个简单的查询和一些PHP行,但我不喜欢它,因为我的简单算法需要一个周期,每个范围内的每个类别都会找到。 If range is too big and there are too much categories, that's not so good. 如果范围太大而且类别太多,那就不太好了。

FINAL EDIT: OK! 最终编辑:好的! After reading all comments and answers, I think not exists a valid, efficient and, at same time, readable solution. 阅读完所有评论和答案后,我认为不存在有效,高效且同时可读的解决方案。 So Mosty Mostacho answer is a no 100% valid solution, but it has 100% valid suggestions. 所以Mosty Mostacho的答案是100%有效的解决方案,但它有100%有效的建议。 Thank you all. 谢谢你们。

New edit: 新编辑:

As I told you in a comment, I strongly recommend you to use the quick query and then process the missing dates in PHP as that would be faster and more readable: 正如我在评论中告诉你的那样,我强烈建议你使用快速查询,然后在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

However, if you still want to use the query version, then try this: 但是,如果您仍想使用查询版本,请尝试以下操作:

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)

Note that MySQL won't allow you to create data on the fly, unless you hardcode UNIONS , for example . 需要注意的是MySQL不会允许你动态创建的数据,除非你硬编码UNIONS ,为例子 This is an expensive process that's why I strongly suggest you to create a table with only an integer field with values from 1 to X , where X is, at least the maximum amount of dates that separate the min(day) and max(day) from your table. 这是一个昂贵的过程,这就是为什么我强烈建议你创建一个只有integer字段的表,其中值为1X ,其中X是, 至少是分隔min(day)max(day)的最大日期数量从你的桌子。 If you're not sure about that date, just add 100,000 numbers and you'll be able to generate range periods for over 200 years. 如果您不确定该日期,只需添加100,000数字,您就可以生成超过200年的测距期。 In the previous query, this table is seq and the column it has is val . 在上一个查询中,此表是seq ,它具有的列是val

This results in: 这导致:

+--------------+--------------+
|     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 |
+--------------+--------------+

Ok, I'm lying. 好吧,我在说谎。 The result is actually returning a counter column. 结果实际上是返回一个counter列。 Just disregard it, as removing it (using a derived table) would be even less performant! 只是忽略它,因为删除它(使用派生表)会更低性能!

and here's a one liner brutality for you :) (Note: Change the "datt" table name.) 这里有一个单行的暴行:)(注意:更改“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

and it's output is : 它的输出是:

|| 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 ||

Note: Thats the result for non-existing 2012-01-02 in a 01-12 day table. 注意:这是01-12天表中不存在的2012-01-02的结果。

No need for PHP or temporary tables or anything. 不需要PHP或临时表或任何东西。

DISCLAIMER: I did this just for fun . 免责声明:我这样做只是为了好玩 This stunt may be too crazy to be used in a production environment. 这种特技可能太疯狂,无法在生产环境中使用。 Therefore I'm not posting this as a "real" solution. 因此,我不会将此作为“真正的”解决方案发布。 Also I'm not willing to explain how it works :) And I didn't rethink / refactor it. 此外,我不愿意解释它是如何工作的:)我并没有重新思考/重构它。 There might be more elegant ways and names / aliases could be more informative. 可能有更优雅的方式,名称/别名可以提供更多信息。 So please no flame or anything. 所以请不要火焰或任何东西。

Here's my solution. 这是我的解决方案。 Looks more complicated than it is. 看起来比它复杂。 I think it may be easier to understand than other answers, no offense :) 我认为比其他答案更容易理解,没有冒犯:)

Setting up test data: 设置测试数据:

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   );

And here it comes: 它来了:

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

Result looks like this: 结果如下所示:

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

Firstly, this is an extension of @Mosty's solution. 首先,这是@Mosty解决方案的扩展。

To enable Mosty's solution to include category/date combinations than do not exist in the table I took the following approach - 为了使Mosty的解决方案能够包括类别/日期组合而不是表中没有,我采取了以下方法 -

Start by getting a distinct list of categories and then join this to the entire date range - 首先获取一个不同的类别列表,然后将其加入整个日期范围 -

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`

The above query builds the full date range using the table dummy with a single field id . 上面的查询使用带有单个字段id的表dummy来构建完整的日期范围。 The id field contains 0,1,2,3,.... - it needs to have enough values to cover every day in the required date range. id字段包含0,1,2,3,.... - 它需要有足够的值来覆盖所需日期范围内的每一天。 This can then be joined back to the original table to create a complete list of all categories for all dates and the appropriate value for a - 然后可以将其连接回原始表,以创建所有日期的所有类别的完整列表以及 - 的适当值 -

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`

This can then be applied to Mosty's query in place of table t - 然后可以将其应用于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

Completely on mysql side will have performance adv: Once the procedure has been created, it runs within 0.35 - 0.37 sec 完全在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: 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

Here is mysql solution which will give the desired result excluding the missed range only. 这是mysql解决方案,它将提供所需的结果,仅排除错过的范围。

PHP: The missing range can be added through php. 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++;
}

To make this work as you want it to, you should have two tables: 要使其按照您的意愿工作,您应该有两个表:

  1. for periods 期间
  2. for days 好几天

Where each period can have many days related to it through FOREIGN KEY . 通过FOREIGN KEY每个时期可以有多天与之相关。 With current table structure, the best you can do is to detect the continuous periods on PHP side. 使用当前的表结构,您可以做的最好的事情是检测PHP端的连续周期。

Is this what you mean? 你是这个意思吗?

SELECT
    category,
    MIN(t1.day),
    MAX(t2.day),
    a
FROM
    `table` AS t1
INNER JOIN `table` AS t2 USING (category, a)

If I understand your question correctly, I would use something to the effect of: 如果我理解你的问题,我会使用一些东西:

SELECT MAX(day), MIN(day) FROM `YourTable` WHERE `category`= $cat AND `A`= $increment;

... and ... ......而且......

$dateRange = $cat.","."$min"."|"."$max".",".$increment;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM