帮助编写一个 mysql 查询 - 这之前一定已经完成了 1000 次,但我很挣扎..请帮忙?
更新: 我正在编辑我的问题,希望得到更好的答案。我发现这并不那么简单,但我不敢相信没有比迄今为止提到的更简单的解决方案。 我现在正在寻找是否有某种 php、mysql 解决方案以最有效的方式处理这个问题。我修改了下面的问题,试图让事情变得更清楚
我有一个包含以下字段的表:
- UserID
- GroupID
- Action
- ActionDate
该表仅存储系统上的用户添加到组(操作 = 1)或从组中删除(操作 = -1)。每当发生上述操作之一时,都会记录日期时间,如 ActionDate
只要用户在该帐单月份中至少 15 天属于该组(帐单月份是指不一定是月初,可能是从 1 月 15 日到 2 月 15 日)
我每个月都会在计费月开始时为当时属于其组的所有用户向我的组计费。现在,在一个月内,他们可能会向组中添加新用户或从组中删除现有用户。 如果他们删除了某个用户,我需要知道该用户在该帐单月份的至少 15 天内是否属于该组。如果他什么都不做,如果不是,那么该用户的团体需要退款(因为他们在月初为该用户付款,但他加入该团体的时间不到 15 天) 如果他们添加了用户并且该用户在该组中至少存在 15 天(即在帐单月份的 15 天内添加并且在 15 天结束之前未删除),则必须为该用户向该组收费。如果用户最终没有成为该组的一员 15 天,那么我们什么也不做(免费)。
一些额外的复杂性包括:
- 在该计费月份内,用户可能会被多次添加或删除,我们需要跟踪他属于该组的总天数
- 我们需要能够区分为了正确对组进行计费而(最终)删除或添加(最终)的用户。 (例如,作为群组成员有 10 天的用户 - 如果他最终被从群组中删除,那么我们会退款。如果他被添加到群组中,那么我们不会收费 - 因为少于 10 天)
- 在任何情况下在给定的计费月份中,用户可能不会出现在该表中,因为他们的状态未更改 - 即他们仍然是组的一部分或从未属于组。事实是,不需要对这些用户做任何事情,因为如果有必要,他们将被包含在“今天组中有多少用户”的基本每月计算中,
我开始意识到没有简单的 mysql 解决方案,我需要一个 php, mysql 组合。请帮忙!!!
这是我最近的 sql 尝试,但它没有包含我在下面讨论的所有问题:
SELECT *
FROM groupuserlog
where action = 1
and actiondate >= '2010-02-01'
and actiondate < date_add('2010-02-01',INTERVAL 15 DAY)
and userid not in (select userid
from groupuserlog
where action = -1
and actiondate < '2010-03-01'
and actiondate > date_add('2010-02-01', INTERVAL 15 DAY))
Update:
I am editing my question in the hope of getting a better answer. I see this is not so simple but I cant believe there is not a simpler solution than what has been mentioned so far.
I am now looking to see if there is some kind of php, mysql solution to deal with this in the most efficent way. I have modified my question below to try and make things clearer
I have a table with the following fields:
- UserID
- GroupID
- Action
- ActionDate
This table simply stores whenever a user on my system is added to a group (action = 1) or removed from a group (action = -1). The datetime is recorded whenever one of the above actions take place, as ActionDate
A group is charged for every user they have each month as long as the user was part of the group for at least 15 days of that billing month (a billing month means not the beginning of a month necessarily, could be from the 15th of Jan to 15th of Feb)
I bill my groups every month at the begining of a billing month for all users who are part of their group at that time. Now over the course of the month they might add new users to their group or remove existing users from their group.
If they removed a user I need to know if the user was part of the group for at least 15 days of that billing month. If he was then do nothing, if not then the group needs to be refunded for that user (as they paid for the user at the beginning of the month but he was part of the group for less than 15 days)
If they added a user and the user was in the group for at least 15 days (ie added within 15 days of billing month AND was not removed before 15 days were up) then the group must be charged for this user. If the user did not end up with 15 days as part of the group then we do nothing (no charge).
Some of the additional complexities are:
- A user might be added or removed multiple times over the course of that billing month and we would need to keep track of total number of days that he was part of the group
- We need to be able to differentiate between users who are being removed (ultimately) or added (ultimately) in order to correctly bill the group. (for example a user who has 10 days as part of the group - if he was ultimately removed from the group then we issue a refund. If he was being added to the group then we dont charge - because less than 10 days)
- In any given billing month the user might not appear in this table since their status was not changed - ie they remained a part of the group or were never part of the group. The truth is that nothing needs to be done with these users as if necessary they will be included in the base monthly calculation of "how many users in group today"
I am starting to realize there is no simple mysql solution and i need a php, mysql combo. Please help!!!
Here is my most recent sql attempt but it does not incorporate all the issues i have discussed below:
SELECT *
FROM groupuserlog
where action = 1
and actiondate >= '2010-02-01'
and actiondate < date_add('2010-02-01',INTERVAL 15 DAY)
and userid not in (select userid
from groupuserlog
where action = -1
and actiondate < '2010-03-01'
and actiondate > date_add('2010-02-01', INTERVAL 15 DAY))
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我假设用户可能在计费周期之前很久就加入了群组,并且在计费周期内可能不会更改状态。这需要扫描整个表以构建一个如下所示的成员资格表:
一旦正确填充,您想要的答案就很容易获得:
扫描需要由游标执行(这不会很快)。我们需要按 ActionDate 和 Action 对输入表进行排序,以便“加入”事件出现在“离开”事件之前。计数字段
是否可以帮助应对病态情况 - 会员资格在某一天结束,然后在同一天重新开始,在同一天再次结束,并在同一天再次开始,等等。在这些情况下,我们递增每个开始事件的计数,以及每个结束事件的递减。仅当结束事件导致倒计时为零时,我们才会关闭会员资格。在填充成员资格表结束时,您可以查询 count 的值:封闭的成员资格应具有 count = 0,开放的成员资格(尚未关闭)应具有 count = 1。应仔细检查 count 超出 0 和 1 的任何条目- 这表明某处存在错误。
光标查询是:
“Action desc”应该打破联系,以便如果有人在同一日期加入和离开组,则开始事件出现在结束事件之前。 ActionDate 需要从日期时间转换为日期,因为我们对天单位感兴趣。
游标内的操作如下:
我没有给您所需的游标定义的确切语法(您可以在 MySQL 手册中找到),因为完整的代码会模糊这个想法。事实上,在应用程序中执行游标逻辑可能会更快 - 甚至可能在应用程序中构建成员资格详细信息。
编辑:这是实际的代码:
这是一些测试数据:
这是正在运行的代码和结果:
然后,检查 2 月 09 日出现了多少个计费天:
这可以只扫描表以查找自上次运行以来的更改:
如果你这样做,传递一个允许你从头开始重建的标志也是一个好主意 - 即。将控制表重置为开始时间,并在运行常规过程之前截断成员资格表。
I am assuming that a User might have joined a group long before the billing period, and might not change status during the billing period. This requires your entire table to be scanned to construct a membership table which looks like this:
Once this is correctly populated, the answer you want is easily obtained:
The scan needs to be performed by a cursor (which will not be fast). We need to sort your input table by ActionDate and Action so that "join" events appear before "leave" events. The count field
is there to help cope with pathological cases - where a membership is ended one date, then re-started on the same date, and ended again on the same date, and started again on the same date, etc. In these cases, we increment the count for each start event, and decrement for each end event. We will only close a membership when an end event takes the count down to zero. At the end of populating the membership table, you can query the value of count: closed memberships should have count = 0, open memberships (not yet closed) should have count = 1. Any entries with count outside 0 and 1 should be examined closely - this would indicate a bug somewhere.
The cursor query is:
"Action desc" should break ties so that start events appear before end events should someone join and leave a group on the same date. ActionDate needs to be converted from a datetime to a date because we're interested in units of days.
The actions within the cursor would be the following:
I have not given you the exact syntax of the cursor definition required (you can find that in the MySQL manual) because the full code will obscure the idea. In fact, it might be faster to perform the cursor logic within your application - perhaps even building the membership details within the application.
EDIT: Here is the actual code:
Here's some test data:
Here's the code being run, and the results:
Then, check how many billing days appear in feb 09:
This can be made to just scan table for changes since the last run:
If you do that, it is also a good idea to pass in a flag that allows you to rebuild from scratch - ie. reset the control table to the start of time, and truncate the membership table before running the usual procedure.
不确定你的桌子,但也许类似?
Not sure about your table but perhaps something like?
我认为所有的复杂性都在于如何找出给定添加操作的相邻删除操作。那么,添加一个指向后续操作的主键的列怎么样?
假设该列名为 NextID,
有多少用户在给定月份加入某个群组并在该群组中保留了至少 15 天:
在给定月份有多少人从群组中删除且未在群组中保留 至少 15 天至少 15 天:
I think all the complexity lies in how to figure out the adjacent remove action for a given add action. So, how about adding a column pointing at the primary key of the subsequent action?
Supposing that column is called NextID,
How many users joined a group in a given month and remained part of that group for at least 15 days:
How many people were removed from a group in a given month that did not remain in a group for at least 15 days:
我开始研究 Martin 提出的解决方案,并意识到虽然这可能是正确的选择,但我决定使用我最了解的 php,而不是复杂的 sql。虽然效率肯定较低,但由于我的桌子尺寸永远不会太大,这对我来说最有意义。
最后,我编写了一个简单的查询,它按时间顺序为给定月份组中的所有用户活动创建用户历史记录。
然后,我循环遍历结果集并收集每个用户的所有数据。该月的第一个操作(添加或删除)指示该用户是否是该组中以前存在的用户。然后,我查看历史记录并简单计算活跃天数 - 最后,我只是查看是否应该退款或收费,具体取决于用户之前是否存在于该组中。
它不是那么漂亮,但它干净地完成了工作,并允许我进行一些我需要做的额外处理。
感谢大家的帮助。
我的 php 代码,如果有人感兴趣的话,如下所示:
I started working through Martin's proposed solution and realised that although it is probably the right path to take i decided that I would go with what I know best which is php as opposed to complex sql. Although for sure less efficient, since my table sizes will never be too big it makes the most sense for me.
In the end I wrote a simple query which creates a user history in chronological order for all user activity in the group for a given month.
I then loop through the result set and collect all data for each user. The first action (either add or remove) of the month indicates whether or not this is a user was someone who previously existed in the group or not. I then go through the history and simply calculate the number of active days - at the end of it I just see if a refund or charge shoudl be issued, depending on whether the user previously existed in the group or not.
Its not so pretty but it does the job cleanly and allows me for some additional processing which I need to do.
Thanks to everyone fo the help.
My php code, if anyone is interested looks as follows: