【问题标题】:How to get interval of X number of consecutive days and values from tables records and scores如何从表记录和分数中获取 X 连续天数的间隔和值
【发布时间】:2018-02-03 19:45:50
【问题描述】:

我正在尝试从两个表中获取 X 个连续日期(间隔)的平均分数。有了这个,我的意思是日期必须根据列 records.status 的值是连续的(仅当状态为 T 或 P 时,才会选择行值,尤其是 score.score)。

例如,如果我为 personid = 133* 选择 4 个连续日期的间隔,我希望返回以下内容(预平均计算,我认为我应该使用 SQL 查询获得?)

2015-07-11  5
2015-10-17  2
2015-11-06  5
2016-01-20  5

2016-01-30  4
2016-05-19  4
2016–09-07  1   
2016-09-28  3

2016-12-29  2
2017-01-17  1
2017-01-22  3
2017-04-02  2

并绘制图表(经过平均计算,我认为我需要用 PHP 来做)

group 1 (2015-07-11 / 2016-01-20) 4.25
group 2 (2016-01-30 / 2016-09-28) 3.00
group 3 (2016-12-29 / 2017-04-02) 2.00

*这是我随机生成的一些样本数据,我正在测试,但我的实际数据更大,结构更好,包含更多列和真正连续的日期(周一至周五,隔天)。

http://sqlfiddle.com/#!9/4b7a62/1

非常欢迎任何提示和建议。

MySQL 版本:5.6.26 [edit1] 不知何故,我的 sqlfiddle sn-p 离线,但这应该是我的示例设置

————2 DB tables schema’s
CREATE TABLE IF NOT EXISTS `records` (
  `person` varchar(32) NOT NULL,
  `status` varchar(32) NOT NULL,
  `purdate` date NOT NULL,
  `personid` int(11) DEFAULT NULL,
  `id` int(11) NOT NULL
)

CREATE TABLE IF NOT EXISTS `scores` (
  `personid` int(11) DEFAULT NULL,
  `score` int(11) DEFAULT NULL,
  `date` date DEFAULT NULL,
  `id` int(11) NOT NULL
) 

—-php for sample data—
function getRandomDateTime($startDate, $endDate, $num) {

    for ($i = 0; $i < $num; $i++) {
        $dateArr[] = date('Y-m-d', mt_rand(strtotime($startDate), strtotime($endDate)));       
    }
    sort($dateArr, SORTDATE);// SORT_REGULAR);SORTDATE);//
    return $dateArr;

}
$test = getRandomDateTime('2015-06-03', '2017-05-12', 100);

echo "insert into records (person, status, purdate, personID) values\r\n";
foreach($test as $value) {
    $arrCode = ['P','L','T'];
    $arrId = [133, 145,156];
   $rand = $arrCode[array_rand($arrCode, 1)];
   $randID = $arrId[array_rand($arrId, 1)];
    echo "('person_name', '".$rand."', '".$value."', '".$randID."'),\r\n";
}

echo "insert into scores (personID, score) values\r\n";
for ($i=0;$i < 100;$i++) {
    $arrId = [133, 145,156];
    $randID = $arrId[array_rand($arrId, 1)];
    echo "('".$randID."','".rand(1,5)."'),\r\n";

}

——— SQL Query To Update The Date Column—
UPDATE scores  
SET scores.date = (  
SELECT records.purdate  
    FROM records  
    WHERE records.id = scores.id  
);

[edit2] 这个简单的 php 函数,我称之为:。 getConsecutiveInterval(4)。

  function getConsecutiveInterval($interval) {
    global $conn;

//    $interval = 4;
    $offset = '';
// For loop will control the results sets divided by 4 
    for ($i = 1; $i <= $interval; $i++) {
        // To add the offset after the first set
        if ($offset > 0) {
            $limitValues = $interval . ", " . $offset . " ";
        } else {
            $limitValues = $interval;
        }
// Query is the same and at the end of it you include LIMIT to be controlled by the loop.
        $q = "SELECT a.purdate, b.score, a.status "
                . "FROM records a "
                . "INNER JOIN scores2 b "
                . "ON a.purdate = b.date AND a.personid = b.personid "
                . "WHERE a.personid = 133 AND a.status IN('P','T') "
                . "ORDER BY purdate ASC, score DESC ";
        $sqlquery = $q . "  LIMIT " . $limitValues;
        $avg = 0;
        $total = 0;
    //Total Found Use To Divide by ... For Max Loop
    $result = mysqli_query($conn, $q);
    $num_rows = mysqli_num_rows($result);
    //end
        foreach (mysqli_query($conn, $sqlquery) as $results) {
            // Do Something
            $total += $results['score'];
            $avg = $total / $interval;
        }
        echo $avg . '<br/>';
        $offset += $interval;
    } echo '<hr/>';
}

我知道随机数据输出不同的平均值,但基于我的以下随机数据和硬编码的 personid = 133

我使用 PHP 函数得到的平均输出

我预计平均值为2.75, 3.5 and 3.5(based on the rest 2 dates not 4)

当我使用 getConsecutiveInterval(3);我希望平均值为3.33, 3.33, 2.66 and 4 (based on 1 date)

【问题讨论】:

  • 您需要将样本数据放入问题中,最好是表格。您在问题中的示例有一个日期,但您的数据有两个。
  • 什么版本的 MySQL 或 MariaDB?
  • @MattW。 SQL Fiddle 有问题?
  • 啊我没看到链接
  • @GordonLinoff 我已经制作了一些示例数据,但是两个表都有一个相等的列日期和一个相等的人 ID 是正确的 GerardH.Pille 我正在使用 mysql 5.6.26(我编辑了我的问题)

标签: php mysql sql intervals


【解决方案1】:

更新: 我之前给你的例子帮助我理解了你的需求和你的背景知识(你更喜欢开发什么)。

我知道 PHP 解决方案最适合您,但您知道并非所有 MySQL 解决方案都应该依赖 PHP。所以,我决定用我能想到的最好的方法。

我有您通过 PHP 提供的示例,它们足以让您更好地了解您正在使用的数据类型。

从这些样本中我看到records.purdate 和scores.date 是相同的,你基本上将purdate 列复制到scores.date 列中。这可能是一种冗余,但它会帮助我们获取每个连续日期的开始日期和结束日期。

首先我需要提一下,我正在开发 MySQL v5.7,我使用MySQL Workbench 6.3 作为 IDE(我已经很长时间没有使用 phpMyAdmin,但它也应该可以使用它)。

您需要创建一个存储过程,如果您不知道如何在 phpMyAdmin 中管理它,只需 google 即可。

我会给你一个工作的(测试):

CREATE PROCEDURE `getConsecutiveInterval`(IN `selectRows` INT, IN `skippedRows` INT)
BEGIN
SET @selectRows = selectRows; 
SET @skippedRows = skippedRows; 

IF skippedRows = 0 THEN
SET @skippedRows = "";
ELSE 
SET @skippedRows = CONCAT(" , " , skippedRows);
END IF;

SET @q = CONCAT("SELECT concat(date_format(MIN(StartDate), '%Y-%m-%d'), '  /  ', date_format(MAX(EndDate), '%Y-%m-%d')) AS Dates, AVG(Score)
FROM (
SELECT 
    a.purdate AS StartDate, 
    b.date AS EndDate, 
    b.score  AS Score
FROM records a 
LEFT JOIN scores b 
ON a.purdate = b.date AND a.personid = b.personid 
WHERE 
    a.personid = 133
AND a.status IN('P','T') 
AND b.score IS NOT NULL
ORDER BY purdate ASC, score DESC 
LIMIT ", @selectRows, @skippedRows, " ", ") D;");

PREPARE ConsecutiveInterval FROM @q;
EXECUTE ConsecutiveInterval;
DEALLOCATE PREPARE ConsecutiveInterval;
END

这个存储过程类似于您的 getConsecutiveInterval() 函数,除了它在 MySQL 中。

它是如何工作的: 您可以通过

调用存储过程
CALL getConsecutiveInterval(selectRows,skippedRows)

我在存储过程中做了一个条件,如果skippedRows为0,那么它将是一个空字符串。否则将始终返回skippedRows。

例如,使用您提供的示例:

CALL getConsecutiveInterval(4,0)

将返回:

'2015-07-11  /  2016-01-20', '4.25'

CALL getConsecutiveInterval(4,1)

会回来

2016-01-30  /  2016-01-30   4.00

等等。

selectRows var 是 PHP 中的 $interval,skippedRows 是 $offset。

然后,从您的 PHP 端,您可以通过以下方式调用它:

$query = "CALL getConsecutiveInterval( " . $interval . " , "  . $offset .")";

这样,您将只控制 PHP 输出的 $interval 和 $offset 整数,其余的将由 MySQL 自己维护。

$offset 的计算将和以前一样:

$offset += $interval;

您还可以更改存储过程以使用更多参数进行扩展,例如 personid、status ..等。无论您需要什么参数,您都可以随时扩展它。

例如,我将使用 personid 扩展它:

CREATE PROCEDURE `getConsecutiveInterval`(IN `selectRows` INT, IN `skippedRows` INT, IN personID INT)
BEGIN
SET @selectRows = selectRows; 
SET @skippedRows = skippedRows; 
SET @personid = personID;

IF skippedRows = 0 THEN
SET @skippedRows = "";
ELSE 
SET @skippedRows = CONCAT(" , " , skippedRows);
END IF;

IF personID > 0 THEN 
SET @personid = CONCAT(" AND a.personid = ",  personID); 
ELSE 
SET @personid = ""; 
END IF;

SET @q = CONCAT("SELECT concat(date_format(MIN(StartDate), '%Y-%m-%d'), '  /  ', date_format(MAX(EndDate), '%Y-%m-%d')) AS Dates, AVG(Score)
FROM (
SELECT 
    a.purdate AS StartDate, 
    b.date AS EndDate, 
    b.score  AS Score
FROM records a 
LEFT JOIN scores b 
ON a.purdate = b.date AND a.personid = b.personid 
WHERE 
    a.status IN('P','T') 
AND b.score IS NOT NULL ", @personid, " ORDER BY purdate ASC, score DESC LIMIT ", @selectRows, @skippedRows, " ", ") D;");

PREPARE ConsecutiveInterval FROM @q;
EXECUTE ConsecutiveInterval;
DEALLOCATE PREPARE ConsecutiveInterval;
END

这将添加另一个要像这样调用的参数:

CALL getConsecutiveInterval(4,0, 133);

133是personid,如果我把它改成0,那么条件a.personid = 133

将从查询中删除,我将根据表格排序获得随机数据。

我希望此更新对您的旅程有所帮助。

【讨论】:

  • 感谢您的两个建议。我不得不承认您的查询不是我习惯的(主要是 CRUD 查询)。我直接在 PHPmyadmin 中尝试了您的 sql 循环查询,但出现错误(仅测试 SELECT 查询部分输出预期值)。 LOOP部分可以用mysql 5.6完成吗??为什么 sql 区间变量设置为 3 而不是 4?关于 LIMITING 查询,最后一个函数我会添加一个日期范围(即选中的 startDay - 选中的 endDay)
  • ad 2/ 您的 PHP 建议查询部分是否仅指 SELECT a.purdate, .......IN('P','T') ORDER BY purdate ASC, score DESC 并且因为我错误地使用了间隔一词。
  • 哦,ad2/ $offset 第一次是怎么设置的?我已经根据您的 php 建议使用一个简单的函数编辑了我的问题
  • 代码未经测试,只是为了展示我在回答中所说的场景,如果您提供示例数据,我将对其进行审查,sqlfiddle 已损坏,只需将架构和一些插入值代码放入发布,以确保它们正常工作。至于sql区间,你可以任意设置,我随机选择了3。 LIMIT 也适用于日期范围,没有问题。
  • @user9191816 在 PHP 部分,是的,您将查询作为字符串注入(在查询部分)。 $offset 第一次将为空,然后在第一个循环之后将具有 $interval 值,并且它将在每次运行时添加它。因此,如果您的 $interval 为 4。您的 $offset 在第一次运行时将为空,在第二次运行时为 4,在第三次运行时为 8 ...等等。
【解决方案2】:

如果你的 mysql 版本提供了窗口函数,那么解决方案似乎相当简单。

select g.personid, min(g.date) dfrom, max(g.date) dto, avg(g.score) avgscore
  from (
    select s.*, floor((s.rn - 1) / 4) gn
      from (
        select scores.personid, scores.date, scores.score ,
           row_number() over (
             partition by scores.personid
             order by scores.date) as rn
         from scores
         join records
           on  scores.personid = records.personid
           and scores.date = records.purdate
         where records.status in ('T','P')
         order by personid, date
      ) s
  ) g
  group by g.personid, g.gn
  order by g.personid, g.gn;

使用来自 sql fiddle 的数据,这给出:

+----------+------------+------------+----------+
| personid | dfrom      | dto        | avgscore |
+----------+------------+------------+----------+
|      133 | 2015-07-11 | 2016-01-20 |   4.2500 |
|      133 | 2016-01-30 | 2016-09-28 |   3.0000 |
|      133 | 2016-10-02 | 2017-04-02 |   2.0000 |
|      145 | 2015-06-29 | 2016-06-30 |   3.0000 |
|      145 | 2016-10-24 | 2017-01-16 |   3.3333 |
|      156 | 2015-10-20 | 2015-12-17 |   2.0000 |
|      156 | 2015-12-19 | 2016-05-21 |   3.0000 |
|      156 | 2016-05-25 | 2016-10-16 |   4.7500 |
|      156 | 2017-01-30 | 2017-01-30 |   4.0000 |
+----------+------------+------------+----------+

【讨论】:

    【解决方案3】:

    做了一个测试例子:

    declare @selectedIntervalCount int=3, @selectedID int=1
    declare @startDate date='2018-01-01',@endDate date='2018-01-31'
    declare @data table(pID int,pDate date, statsValue int)
    insert into @data(pID,pDate, statsValue)
    values(1,'2018-01-01',1)
    ,(1,'2018-01-02',2),(1,'2018-01-03',3),(1,'2018-01-04',4)
    ,(1,'2018-01-05',5),(1,'2018-01-06',1),(1,'2018-01-07',2)
    ,(1,'2018-01-08',7),(1,'2018-01-09',4),(1,'2018-01-10',3)
    ,(1,'2018-01-11',8),(1,'2018-01-12',5),(1,'2018-01-13',3)
    
    
    select tt1.tempIX/@selectedIntervalCount 'intervalIX', cast(min(tt1.pDate) as varchar)+' - '+cast(max(tt1.pDate) as varchar) 'interval', sum(tt1.statsValue)/cast(count(tt1.statsValue) as float) 'avgStatsValue' from( 
        select (row_number() over (order by pDate) -1) 'tempIX', t1.pDate, t1.statsValue 
        from @data t1
        where t1.pDate between @startDate and @endDate 
        and t1.pID=@selectedID
    ) tt1
    group by tt1.tempIX/@selectedIntervalCount
    order by tt1.tempIX/@selectedIntervalCount
    

    输出是:

    intervalIX  interval    avgStatsValue
    0   2018-01-01 - 2018-01-03 2
    1   2018-01-04 - 2018-01-06 3,33333333333333
    2   2018-01-07 - 2018-01-09 4,33333333333333
    3   2018-01-10 - 2018-01-12 5,33333333333333
    4   2018-01-13 - 2018-01-13 3
    

    【讨论】:

    • 好吧,我在您进行大规模更新之前写了这个,但这应该将每个 X 日期分组在一个间隔中,然后得到间隔的平均值
    • tx,是的,我已经更新了我的问题(sqlfiddle 似乎处于离线状态),如果我能理解查询并将其转换为我的变量,我会尝试。顺便说一句,您如何处理剩余值(即连续 4 天,但我找到了 15 行,如何处理 3 天?)
    • 过去 3 天将被分组并取平均值。但如果间隔为 4,您可以计算并强制间隔包含例如 4 个日期
    • @user9191816 提供了一个我实际测试过的示例:) 更改了 tempIX 生成
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-11
    • 1970-01-01
    • 2010-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-01
    相关资源
    最近更新 更多