Php 付费会员服务
我有两个表,一个是事务表,一个是成员表 一个用户可以一次激活所有可用的成员资格,每个成员资格提供不同的好处 我正在尝试处理具有相同用户id和相同成员身份的多个事务记录 这是我的桌子Php 付费会员服务,php,mysql,Php,Mysql,我有两个表,一个是事务表,一个是成员表 一个用户可以一次激活所有可用的成员资格,每个成员资格提供不同的好处 我正在尝试处理具有相同用户id和相同成员身份的多个事务记录 这是我的桌子 Transactions table ID | USERID | MID | CREATED | AMOUNT ------------------------------------------------- 1 | 1 | 2 | 2014-10-01 00:00:00 |
Transactions table
ID | USERID | MID | CREATED | AMOUNT
-------------------------------------------------
1 | 1 | 2 | 2014-10-01 00:00:00 | 1
2 | 1 | 2 | 2014-10-16 00:00:00 | 1
3 | 2 | 1 | 2014-10-30 00:00:00 | 1
Membership tables
ID | TITLE | DURATION
-------------------------
1 | Premium | 365
2 | Supporter | 30
3 | Beta Access | 30
在transactions表中,我有两条userid 1的记录,一条始于2014-10-01,另一条始于2014-10-01
2014年10月16日
以下脚本可以很好地选择各个活动成员身份日志
SELECT t.USERID AS UID, t.CREATED AS CREATED, FROM_UNIXTIME(UNIX_TIMESTAMP(t.CREATED) + t.AMOUNT * m.DURATION) AS ENDS
FROM Transactions AS t
LEFT JOIN Memberships AS m on m.ID = t.MID
LIMIT 5
输出是这样的
UID | MID | CREATED | ENDS
-----------------------------------------------------
1 | 2 | 2014-10-01 00:00:00 | 2014-10-31 00:00:00
1 | 2 | 2014-10-16 00:00:00 | 2014-11-15 00:00:00
2 | 1 | 2014-10-30 00:00:00 | 2015-10-30 00:00:00
现在有两条记录具有相同的成员ID MID和用户ID UID,第一条记录在第二条记录之前过期
基本上,我所要做的就是“合并”或“合并”一个memberishp的总“未使用”天数,只要在当前用户id和membership id过期之前添加了另一个成员id
以下是显示给定数据和所需输出的示例:
ID | USERID | MID | CREATED | Amount
-------------------------------------------------
1 | 1 | 2 | 2014-10-01 00:00:00 | 1 #30 days days remains
2 | 1 | 2 | 2014-10-17 00:00:00 | 1 #14 days of the previous transaction is not fully consumed,43 days remains - (days amount + previous unused days)
3 | 1 | 2 | 2014-11-01 00:00:00 | 1 #28 days of the previous transaction (44 days ones) is not fully consumed,59 days remains - (days amount + previous unused days)
4 | 2 | 3 | 2014-10-01 00:00:00 | 1 #30 days days remains
5 | 2 | 3 | 2014-11-08 00:00:00 | 1 #30 days days remains
输出应该是这样的
UID | MID | CREATED | ENDS
-----------------------------------------------------
1 | 2 | 2015-10-01 00:00:00 | 2014-12-29 00:00:00
2 | 1 | 2014-10-01 00:00:00 | 2014-10-30 00:00:00
2 | 1 | 2014-11-08 00:00:00 | 2014-12-08 00:00:00
如果不清楚的话,我很抱歉,因为英语不是我的母语,也没有语言来解释我要完成的任务
编辑:
如果不可能通过mysql寻找php解决方案。我想说,主要有三种不同的方法,我尝试给出这些方法的示例。然而,所有方法都有优点/缺点 MySQL查询 这样的查询似乎需要某种递归。。。因此,一种可能的方法可以与之类似,通过在select语句中使用来利用MySQL的表达式求值 返回每个事务记录的持续时间和扩展结束时间的查询:
SELECT id, userid, mid, created,
-- initialize if new userid or membership id
IF (@lastuser!=userid or @lastmb!=mid, (@prevend:=created)+(@lastuser:=userid)+(@lastmb:=mid), 0) AS tmp,
-- calculate unused days
@unused:=IF(@prevend>created, datediff(@prevend, created), 0) AS tmp2,
-- calculate the end of current membership (will be used for next record)
@prevend:=DATE_ADD(created, INTERVAL (amount * duration)+@unused DAY) AS ends,
-- calculate the days remaining
@unused+duration AS 'days remain'
FROM (
SELECT tt.id, tt.userid, tt.mid, tt.created, tt.amount, duration
FROM transactions tt
LEFT JOIN memberships as m on m.ID = tt.MID
ORDER BY tt.userid, tt.created) t
JOIN (SELECT @lastuser:=0)tmp;
此查询的输出为:
id userid mid created tmp tmp2 ends days remain
1 1 2 2014-10-01 00:00:00 2017 0 2014-10-31 00:00:00 30
2 1 2 2014-10-17 00:00:00 0 14 2014-11-30 00:00:00 44
3 1 2 2014-11-01 00:00:00 0 29 2014-12-30 00:00:00 59
4 2 3 2014-10-01 00:00:00 2019 0 2014-10-31 00:00:00 30
5 2 3 2014-11-08 00:00:00 0 0 2014-12-08 00:00:00 30
另一项任务是仅输出合并的间隔:
SELECT userid, mid, begins, max(ends) as ends FROM (
SELECT id, userid, mid, created,
-- initialize if new userid or membership id
IF (@lastuser!=userid or @lastmb!=mid, (@prevend:=created)+(@lastuser:=userid)+(@lastmb:=mid), 0) AS tmp,
-- firstcreated stores the very first creation time of overlapping memberships
if (@prevend>created, @firstcreated, (@firstcreated:=created)) as begins,
-- calculate unused days
@unused:=IF(@prevend>created, datediff(@prevend, created), 0) AS tmp2,
-- calculate the end of current membership (will be used for next record)
@prevend:=DATE_ADD(created, INTERVAL (amount * duration)+@unused DAY) AS ends,
-- calculate the days remaining
@unused+duration AS 'days remain'
FROM (
SELECT tt.id, tt.userid, tt.mid, tt.created, tt.amount, duration
FROM transactions tt
LEFT JOIN memberships as m on m.ID = tt.MID
ORDER BY tt.userid, tt.created) t
JOIN (SELECT @lastuser:=0)tmp
) mship
GROUP BY userid, mid, begins;
但是,请注意,这确实不是一个可取的解决方案,因为表达式的求值顺序无法保证。因此,查询可能会产生一个好结果,但是对于不同的数据集,或者对于不同的MySQL版本,它很容易产生一个坏结果。在建议的查询中,有一个子查询带有ORDERBY子句,因此记录顺序在这里不应该成为问题,但是如果您想将此查询放入希望维护更长时间的代码中,例如,当您迁移到新版本的MySQL时,您可能会感到惊讶
至少,它似乎也在MySQL 5.5和MySQL 5.6上工作
所以再次提醒大家,因为正如MySQL文档所说:
一般来说,除了SET语句之外,您不应该
为用户变量赋值并读取同一变量中的值
陈述例如,要增加变量,可以这样做:
设置@a=@a+1;对于其他语句,例如SELECT,您可能会得到
你期望的结果,但这不是保证。在下面
语句,您可能会认为MySQL将首先计算@a,然后再计算@a
第二次做作业:
选择@a,@a:=@a+1
但是,涉及用户变量的表达式的求值顺序
没有定义
计算客户端应用程序端的所有内容,例如PHP
想法是一样的。获取按用户ID、mid和创建日期排序的事务。对记录进行迭代,对于每个新事务,如果有“未使用”的日期,则使用“未使用”的日期延长成员资格的持续时间,该日期可以从以前的事务中计算出来。当我们看到会员资格出现中断时,我们会保存实际期限
执行此操作的示例PHP代码:
<?php
$conn = mysqli_connect("localhost", "user", "password", "db");
if (mysqli_connect_errno($conn)) {
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
// Query to select membership information
$res = mysqli_query($conn, "select t.id, userid, mid, created, (m.duration * t.amount) as duration
from transactions t
left join memberships m
on t.mid=m.id
order by userid, mid, created");
echo "Temporary calculation:<br/>";
echo "<table border=\"1\">";
echo "<th>Id</th><th>UserId</th><th>MID</th><th>Created</th><th>Unused</th><th>End</th><th>Duration</th>";
$last_userid=0;
while ($row = $res->fetch_assoc()) {
// Beginning of a new userid or membership id
if ($row['userid']!=$last_userid or $row['mid']!=$last_mid) {
// If we are not at the first record, we save the current period
if ($last_userid!=0) {
$mships[$last_userid][$last_mid][$first_created->format('Y-m-d H:i:s')]=$last_end->format('Y-m-d H:i:s');
}
// Initialize temporaries
$last_userid=$row['userid'];
$last_mid=$row['mid'];
$first_created=new DateTime($row['created']);
$last_end=clone $first_created;
}
// Calculate duration
$created=new DateTime($row['created']);
$unused=date_diff($created, $last_end);
$ends=clone $created;
$ends->add(new DateInterval("P".$row['duration']."D"));
// $unused->invert is 1 if diff is negative
if ($unused->invert==0 && $unused->days>=0) {
// This transaction extends/immediately follows the previous period
$ends->add(new DateInterval('P'.$unused->days.'D'));
} else {
// We split the period -> save it!
$mships[$row['userid']][$row['mid']][$first_created->format('Y-m-d H:i:s')]=$last_end->format('Y-m-d H:i:s');
$first_created=new DateTime($row['created']);
}
$duration=date_diff($ends, $created);
echo "<tr>";
echo "<td>",$row['id'],"</td>";
echo "<td>",$row['userid'],"</td>";
echo "<td>",$row['mid'],"</td>";
echo "<td>",$row['created'],"</td>";
echo "<td>",($unused->invert==0 ? $unused->format('%a') : 0),"</td>";
echo "<td>",$ends->format('Y-m-d H:i:s'),"</td>";
echo "<td>",$duration->format('%a'),"</td>";
echo "</tr>";
$last_end=$ends;
}
// Last period should be saved
if ($last_userid!=0) {
$mships[$last_userid][$last_mid][$first_created->format('Y-m-d H:i:s')]=$last_end->format('Y-m-d H:i:s');
}
echo "</table><br/>";
echo "Final array:<br/>";
echo "<table border=\"1\">";
echo "<th>UserId</th><th>MID</th><th>Created</th><th>End</th>";
foreach ($mships as $uid => &$mids) {
foreach ($mids as $mid => &$periods) {
foreach ($periods as $begin => $end) {
echo "<tr>";
echo "<td>",$uid,"</td>";
echo "<td>",$mid,"</td>";
echo "<td>",$begin,"</td>";
echo "<td>",$end,"</td>";
echo "</tr>";
}
}
}
$conn->close();
?>
MySQL存储过程
还可以使用存储过程计算结果集。例如,使用与前面PHP代码相同的算法的示例过程
DROP PROCEDURE IF EXISTS get_memberships;
delimiter //
CREATE PROCEDURE get_memberships()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE uid, mid, duration INT;
DECLARE created, unused, first_created, ends TIMESTAMP;
-- make sure that there is no user with 0 id
DECLARE last_uid, last_mid INT DEFAULT 0;
DECLARE last_end TIMESTAMP;
DECLARE cur CURSOR FOR SELECT t.userid, t.mid, t.created, (m.duration * t.amount) as duration
FROM transactions t
LEFT JOIN memberships m
ON t.mid=m.id
ORDER BY userid, mid, created;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
REPEAT
FETCH cur INTO uid, mid, created, duration;
IF (!done) THEN
IF (uid!=last_uid OR last_mid!=mid) THEN
IF (last_uid!=0) THEN
INSERT INTO results (userid, mid, created, ends) VALUES (last_uid, last_mid, first_created, last_end);
END IF;
SET last_uid = uid;
SET last_mid = mid;
SET last_end = created;
SET first_created = created;
END IF;
SET ends = DATE_ADD(created, INTERVAL duration DAY);
IF (last_end>=created) THEN
SET ends = DATE_ADD(ends, INTERVAL datediff(last_end, created) DAY);
ELSE
INSERT INTO results (userid, mid, created, ends) VALUES (uid, mid, first_created, last_end);
SET first_created = created;
END IF;
SET last_end = ends;
END IF;
UNTIL done
END REPEAT;
IF (last_uid!=0) THEN
INSERT INTO results (userid, mid, created, ends) VALUES (uid, last_mid, first_created, last_end);
END IF;
CLOSE cur;
END
//
DROP TABLE IF EXISTS results //
CREATE TEMPORARY TABLE results AS SELECT userid, mid, created, created as ends FROM transactions WHERE 0 //
call get_memberships //
SELECT * FROM results //
DROP TABLE results;
但是,这种技术的一个缺点是使用临时表。我想说,主要有三种不同的方法,我尝试给出这些方法的示例。然而,所有方法都有优点/缺点 MySQL查询 这样的查询似乎需要某种递归。。。因此,一种可能的方法可以与之类似,通过在select语句中使用来利用MySQL的表达式求值 返回每个事务记录的持续时间和扩展结束时间的查询:
SELECT id, userid, mid, created,
-- initialize if new userid or membership id
IF (@lastuser!=userid or @lastmb!=mid, (@prevend:=created)+(@lastuser:=userid)+(@lastmb:=mid), 0) AS tmp,
-- calculate unused days
@unused:=IF(@prevend>created, datediff(@prevend, created), 0) AS tmp2,
-- calculate the end of current membership (will be used for next record)
@prevend:=DATE_ADD(created, INTERVAL (amount * duration)+@unused DAY) AS ends,
-- calculate the days remaining
@unused+duration AS 'days remain'
FROM (
SELECT tt.id, tt.userid, tt.mid, tt.created, tt.amount, duration
FROM transactions tt
LEFT JOIN memberships as m on m.ID = tt.MID
ORDER BY tt.userid, tt.created) t
JOIN (SELECT @lastuser:=0)tmp;
此查询的输出为:
id userid mid created tmp tmp2 ends days remain
1 1 2 2014-10-01 00:00:00 2017 0 2014-10-31 00:00:00 30
2 1 2 2014-10-17 00:00:00 0 14 2014-11-30 00:00:00 44
3 1 2 2014-11-01 00:00:00 0 29 2014-12-30 00:00:00 59
4 2 3 2014-10-01 00:00:00 2019 0 2014-10-31 00:00:00 30
5 2 3 2014-11-08 00:00:00 0 0 2014-12-08 00:00:00 30
另一项任务是仅输出合并的间隔:
SELECT userid, mid, begins, max(ends) as ends FROM (
SELECT id, userid, mid, created,
-- initialize if new userid or membership id
IF (@lastuser!=userid or @lastmb!=mid, (@prevend:=created)+(@lastuser:=userid)+(@lastmb:=mid), 0) AS tmp,
-- firstcreated stores the very first creation time of overlapping memberships
if (@prevend>created, @firstcreated, (@firstcreated:=created)) as begins,
-- calculate unused days
@unused:=IF(@prevend>created, datediff(@prevend, created), 0) AS tmp2,
-- calculate the end of current membership (will be used for next record)
@prevend:=DATE_ADD(created, INTERVAL (amount * duration)+@unused DAY) AS ends,
-- calculate the days remaining
@unused+duration AS 'days remain'
FROM (
SELECT tt.id, tt.userid, tt.mid, tt.created, tt.amount, duration
FROM transactions tt
LEFT JOIN memberships as m on m.ID = tt.MID
ORDER BY tt.userid, tt.created) t
JOIN (SELECT @lastuser:=0)tmp
) mship
GROUP BY userid, mid, begins;
但是,请注意,这确实不是一个可取的解决方案,因为表达式的求值顺序无法保证。因此,查询可能会产生一个好结果,但是对于不同的数据集,或者对于不同的MySQL版本,它很容易产生一个坏结果。在建议的查询中,有一个子查询带有ORDERBY子句,因此记录顺序在这里不应该成为问题,但是如果您想将此查询放入希望维护更长时间的代码中,例如,当您迁移到新版本的MySQL时,您可能会感到惊讶
在乐
ast,它似乎也在MySQL 5.5和MySQL 5.6上工作
所以再次提醒大家,因为正如MySQL文档所说:
一般来说,除了SET语句之外,您不应该
为用户变量赋值并读取同一变量中的值
陈述例如,要增加变量,可以这样做:
设置@a=@a+1;对于其他语句,例如SELECT,您可能会得到
你期望的结果,但这不是保证。在下面
语句,您可能会认为MySQL将首先计算@a,然后再计算@a
第二次做作业:
选择@a,@a:=@a+1
但是,涉及用户变量的表达式的求值顺序
没有定义
计算客户端应用程序端的所有内容,例如PHP
想法是一样的。获取按用户ID、mid和创建日期排序的事务。对记录进行迭代,对于每个新事务,如果有“未使用”的日期,则使用“未使用”的日期延长成员资格的持续时间,该日期可以从以前的事务中计算出来。当我们看到会员资格出现中断时,我们会保存实际期限
执行此操作的示例PHP代码:
<?php
$conn = mysqli_connect("localhost", "user", "password", "db");
if (mysqli_connect_errno($conn)) {
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
// Query to select membership information
$res = mysqli_query($conn, "select t.id, userid, mid, created, (m.duration * t.amount) as duration
from transactions t
left join memberships m
on t.mid=m.id
order by userid, mid, created");
echo "Temporary calculation:<br/>";
echo "<table border=\"1\">";
echo "<th>Id</th><th>UserId</th><th>MID</th><th>Created</th><th>Unused</th><th>End</th><th>Duration</th>";
$last_userid=0;
while ($row = $res->fetch_assoc()) {
// Beginning of a new userid or membership id
if ($row['userid']!=$last_userid or $row['mid']!=$last_mid) {
// If we are not at the first record, we save the current period
if ($last_userid!=0) {
$mships[$last_userid][$last_mid][$first_created->format('Y-m-d H:i:s')]=$last_end->format('Y-m-d H:i:s');
}
// Initialize temporaries
$last_userid=$row['userid'];
$last_mid=$row['mid'];
$first_created=new DateTime($row['created']);
$last_end=clone $first_created;
}
// Calculate duration
$created=new DateTime($row['created']);
$unused=date_diff($created, $last_end);
$ends=clone $created;
$ends->add(new DateInterval("P".$row['duration']."D"));
// $unused->invert is 1 if diff is negative
if ($unused->invert==0 && $unused->days>=0) {
// This transaction extends/immediately follows the previous period
$ends->add(new DateInterval('P'.$unused->days.'D'));
} else {
// We split the period -> save it!
$mships[$row['userid']][$row['mid']][$first_created->format('Y-m-d H:i:s')]=$last_end->format('Y-m-d H:i:s');
$first_created=new DateTime($row['created']);
}
$duration=date_diff($ends, $created);
echo "<tr>";
echo "<td>",$row['id'],"</td>";
echo "<td>",$row['userid'],"</td>";
echo "<td>",$row['mid'],"</td>";
echo "<td>",$row['created'],"</td>";
echo "<td>",($unused->invert==0 ? $unused->format('%a') : 0),"</td>";
echo "<td>",$ends->format('Y-m-d H:i:s'),"</td>";
echo "<td>",$duration->format('%a'),"</td>";
echo "</tr>";
$last_end=$ends;
}
// Last period should be saved
if ($last_userid!=0) {
$mships[$last_userid][$last_mid][$first_created->format('Y-m-d H:i:s')]=$last_end->format('Y-m-d H:i:s');
}
echo "</table><br/>";
echo "Final array:<br/>";
echo "<table border=\"1\">";
echo "<th>UserId</th><th>MID</th><th>Created</th><th>End</th>";
foreach ($mships as $uid => &$mids) {
foreach ($mids as $mid => &$periods) {
foreach ($periods as $begin => $end) {
echo "<tr>";
echo "<td>",$uid,"</td>";
echo "<td>",$mid,"</td>";
echo "<td>",$begin,"</td>";
echo "<td>",$end,"</td>";
echo "</tr>";
}
}
}
$conn->close();
?>
MySQL存储过程
还可以使用存储过程计算结果集。例如,使用与前面PHP代码相同的算法的示例过程
DROP PROCEDURE IF EXISTS get_memberships;
delimiter //
CREATE PROCEDURE get_memberships()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE uid, mid, duration INT;
DECLARE created, unused, first_created, ends TIMESTAMP;
-- make sure that there is no user with 0 id
DECLARE last_uid, last_mid INT DEFAULT 0;
DECLARE last_end TIMESTAMP;
DECLARE cur CURSOR FOR SELECT t.userid, t.mid, t.created, (m.duration * t.amount) as duration
FROM transactions t
LEFT JOIN memberships m
ON t.mid=m.id
ORDER BY userid, mid, created;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
REPEAT
FETCH cur INTO uid, mid, created, duration;
IF (!done) THEN
IF (uid!=last_uid OR last_mid!=mid) THEN
IF (last_uid!=0) THEN
INSERT INTO results (userid, mid, created, ends) VALUES (last_uid, last_mid, first_created, last_end);
END IF;
SET last_uid = uid;
SET last_mid = mid;
SET last_end = created;
SET first_created = created;
END IF;
SET ends = DATE_ADD(created, INTERVAL duration DAY);
IF (last_end>=created) THEN
SET ends = DATE_ADD(ends, INTERVAL datediff(last_end, created) DAY);
ELSE
INSERT INTO results (userid, mid, created, ends) VALUES (uid, mid, first_created, last_end);
SET first_created = created;
END IF;
SET last_end = ends;
END IF;
UNTIL done
END REPEAT;
IF (last_uid!=0) THEN
INSERT INTO results (userid, mid, created, ends) VALUES (uid, last_mid, first_created, last_end);
END IF;
CLOSE cur;
END
//
DROP TABLE IF EXISTS results //
CREATE TEMPORARY TABLE results AS SELECT userid, mid, created, created as ends FROM transactions WHERE 0 //
call get_memberships //
SELECT * FROM results //
DROP TABLE results;
但是,这种技术的一个缺点是使用临时表。在具有“想要的输出”的表中,是否有意对最后4条记录使用id 2?这不应该是独一无二的吗?2014-10-17=>2014-10-31为14天,因此我计算2014-10-17交易为44天,因此下一笔交易为59天。我做错什么了吗?@lp_uuuMyBad它应该是唯一的id,是的,它应该是44天,由30天计算,而不是31天。谢谢你指出,修好了。也将尝试您的解决方案。在具有“想要的输出”的表中,您是否有意在最后4条记录中使用id 2?这不应该是独一无二的吗?2014-10-17=>2014-10-31为14天,因此我计算2014-10-17交易为44天,因此下一笔交易为59天。我做错什么了吗?@lp_uuuMyBad它应该是唯一的id,是的,它应该是44天,由30天计算,而不是31天。谢谢你指出,修好了。我也会尝试你的解决方案。这看起来很有希望,不知道用户定义的变量,或者虽然mysql有,但我不是经常使用mysql,只是为了控制用户和内存。脚本几乎达到了我想要的效果,但计算是不正确的,无法理解哪里出了问题。只有一件事需要澄清。一个新成员可以扩展相同类型的成员资格,或者这并不重要?e、 g.如果我的支持者会员资格将于12:31到期。我今天买了试用版,我的会员资格会持续到1月31日吗?或者这是两件不同的事情,我仍然有我的支持者ms直到12.31和测试访问直到01.18?因为上面的查询将把这两个不同的东西合并在一起。这可能是查询失败的原因。我更新了处理不同成员身份的答案,并尝试在php中实现相同的想法是一样的。。。。即使它不是完美的,我也希望它能帮助你找到一个可行的解决方案。这看起来很有希望,不知道用户定义的变量,或者虽然mysql有,但我不是经常使用mysql,只是为了控制用户和内存。脚本几乎达到了我想要的效果,但计算是不正确的,我无法理解哪里出了问题,只有一件事需要澄清。一个新成员可以扩展相同类型的成员资格,或者这并不重要?e、 g.如果我的支持者会员资格将于12:31到期。我今天买了试用版,我的会员资格会持续到1月31日吗?或者这是两件不同的事情,我仍然有我的支持者ms直到12.31和测试访问直到01.18?因为上面的查询将把这两个不同的东西合并在一起。这可能是查询失败的原因。我更新了处理不同成员身份的答案,并尝试在php中实现相同的想法是一样的。。。。即使它并不完美,我希望它能帮助你找到一个可行的解决方案。