mysql在where子句中拆分一个字符串

时间:2014-04-29 14:54:30

标签: mysql sql

我有一个事件系统,对于我的重复事件,我使用的是类似cron的系统。

重复事件:

+----+----------+--------------+
| id | event_id | repeat_value |
+----+----------+--------------+
|  1 |       11 | *_*_*        |
|  2 |       12 | *_*_2        |
|  3 |       13 | *_*_4/2      |
|  4 |       14 | 23_*_*       |
|  5 |       15 | 30_05_*      |
+----+----------+--------------+

注意:cron值为day_month_day of week

事件:

+----+------------------------+---------------------+---------------------+
| id | name                   | start_date_time     | end_date_time       |
+----+------------------------+---------------------+---------------------+
| 11 | Repeat daily           | 2014-04-30 12:00:00 | 2014-04-30 12:15:00 |
| 12 | Repeat weekly          | 2014-05-06 12:00:00 | 2014-05-06 13:00:00 |
| 13 | Repeat every two weeks | 2014-05-08 12:45:00 | 2014-05-08 13:45:00 |
| 14 | Repeat monthly         | 2014-05-23 15:15:00 | 2014-05-23 16:00:00 |
| 15 | Repeat yearly          | 2014-05-30 07:30:00 | 2014-05-30 10:15:00 |
+----+------------------------+---------------------+---------------------+

无论如何,我有一个查询来选择事件:

SELECT *
FROM RepeatEvent
JOIN `Event` 
ON `Event`.`id` = `RepeatEvent`.`event_id`

产生:

+----+----------+--------------+----+------------------------+---------------------+---------------------+
| id | event_id | repeat_value | id | name                   | start_date_time     | end_date_time       |
+----+----------+--------------+----+------------------------+---------------------+---------------------+
|  1 |       11 | *_*_*        | 11 | Repeat daily           | 2014-04-30 12:00:00 | 2014-04-30 12:15:00 |
|  2 |       12 | *_*_2        | 12 | Repeat weekly          | 2014-05-06 12:00:00 | 2014-05-06 13:00:00 |
|  3 |       13 | *_*_4/2      | 13 | Repeat every two weeks | 2014-05-08 12:45:00 | 2014-05-08 13:45:00 |
|  4 |       14 | 23_*_*       | 14 | Repeat monthly         | 2014-05-23 15:15:00 | 2014-05-23 16:00:00 |
|  5 |       15 | 30_05_*      | 15 | Repeat yearly          | 2014-05-30 07:30:00 | 2014-05-30 10:15:00 |
+----+----------+--------------+----+------------------------+---------------------+---------------------+

但是,我想在一个月内选择活动。我只会有一些条件:每天,每周,每两周,每月和每年。 我想在我的where子句中放入一个方法来划分重复值的字符串,如果它符合以下任何条件以显示它作为结果(repeatEvent是被查询的行,搜索是要查找的日期) :

array(3) = string_divide(repeat_value, '_')
daily = array(0)
monthy = array(1)
dayOfWeek = array(2)

if(daily == '*' && month == '*' && dayOfWeek == '*') //returns all the daily events as they will happen
    return repeatEvent

if(if(daily == '*' && month == '*' && dayOfWeek == search.dayOfWeek) //returns all the events on specific day
    return repeatEvent

if(daily == search.date && month == '*' && dayOfWeek == '*') //returns all the daily events as they will happen
    return repeatEvent

if (contains(dayOfWeek, '/'))
    array(2) = string_divide(dayOfWeek,'/')
    specificDayOfWeek = array(0);
    if(specificDayOfWeek == repeatEvent.start_date.dayNumber)
            if(timestampOf(search.timestamp)-timestampOf(repeatEvent.start_date)/604800 == (0 OR EVEN)
                return repeatEvent

if(daily == search.date && month == search.month && dayOfWeek == '*') //returns a single yearly event (shouldn't often crop up)
    return repeatEvent 

//everything else is either an unknown format of repeat_value or not an event on this day

总结一下,我想运行一个查询,其中在where子句中拆分重复值,我可以查询拆分项。我看过游标,但互联网似乎建议反对它们。

我可以处理在PHP中选择所有重复事件的结果,但是,我想这很慢。

如果看一下四月份,我希望看到以下内容:

+----------+--------------+----+------------------------+---------------------+---------------------+
| event_id | repeat_value | id | name                   | start_date_time     | end_date_time       |
+----------+--------------+----+------------------------+---------------------+---------------------+
|       11 | *_*_*        | 11 | Repeat daily           | 2014-04-30 12:00:00 | 2014-04-30 12:15:00 |
+----------+--------------+----+------------------------+---------------------+---------------------+

如果看一下五月,我希望看到的是

+----------+--------------+----+------------------------+---------------------+---------------------+
| event_id | repeat_value | id | name                   | start_date_time     | end_date_time       |
+----------+--------------+----+------------------------+---------------------+---------------------+
|       11 | *_*_*        | 11 | Repeat daily           | 2014-04-30 12:00:00 | 2014-04-30 12:15:00 |
|       12 | *_*_2        | 12 | Repeat weekly          | 2014-05-06 12:00:00 | 2014-05-06 13:00:00 |
|       13 | *_*_4/2      | 13 | Repeat every two weeks | 2014-05-08 12:45:00 | 2014-05-08 13:45:00 |
|       14 | 23_*_*       | 14 | Repeat monthly         | 2014-05-23 15:15:00 | 2014-05-23 16:00:00 |
|       15 | 30_05_*      | 15 | Repeat yearly          | 2014-05-30 07:30:00 | 2014-05-30 10:15:00 |
+----------+--------------+----+------------------------+---------------------+---------------------+

以下是我希望看到的六月份

+----------+--------------+----+------------------------+---------------------+---------------------+
| event_id | repeat_value | id | name                   | start_date_time     | end_date_time       |
+----------+--------------+----+------------------------+---------------------+---------------------+
|       11 | *_*_*        | 11 | Repeat daily           | 2014-04-30 12:00:00 | 2014-04-30 12:15:00 |
|       12 | *_*_2        | 12 | Repeat weekly          | 2014-05-06 12:00:00 | 2014-05-06 13:00:00 |
|       13 | *_*_4/2      | 13 | Repeat every two weeks | 2014-05-08 12:45:00 | 2014-05-08 13:45:00 |
|       14 | 23_*_*       | 14 | Repeat monthly         | 2014-05-23 15:15:00 | 2014-05-23 16:00:00 |
+----------+--------------+----+------------------------+---------------------+---------------------+

4 个答案:

答案 0 :(得分:4)

你可以在上面加上一个绑带,但没有人会帮你告诉你这就是答案。

如果您的MySQL数据库可以更改,我强烈建议您将包含下划线day_month_day of year的当前列拆分为三个单独的列daymonthday_of_year 。我还建议您将格式更改为INT而不是VARCHAR。这将使搜索和解析变得更快,更容易,因为它的设计方式不需要通过复杂的程序转换成计算机语言......它已经是大部分方式了。

原因如下:

原因1:您的表未优化

您的桌子未经过优化,无论您在此阶段选择做什么,都会放慢速度。 SQL不是为了在一列中包含多个值而构建的。 SQL数据库的整个要点是将值拆分为不同的列和行。

规范化此表的优点是搜索它会快得多,并且您将能够在MySQL中构建查询。看看Normalization。这是一个复杂的概念,但一旦你得到它,你将避免创建凌乱和复杂的程序。

原因2:您的表格可以稍微调整一下,以利用计算机日期/时间功能。

计算机根据Unix纪元时间跟踪时间。它计算秒数并始终在您的计算机上运行。实际上,计算机一直在计算它,因为顾名思义,第一台Unix计算机曾经开启过。此外,每个基于计算机和计算机的程序/系统具有内置的快速日期和时间功能。 MySQL也不例外。

我还建议将所有这些存储为整数。 repeat_doy(一年中的某一天)很容易成为smallint或至少是一个标准int,而不是一个月和一天,你可以把实际的1-365天年。您可以使用DAY_OF_YEAR(NOW())将其输入MySQL。要将其作为日期撤回,您可以使用MAKEDATE(YEAR(NOW),repeat_doy)。您可以使用0或NULL来代替星号来表示全部。

使用类似cron的系统,无论如何你可能不需要进行那种计算。 相反,在其他地方测量一年中的某一天可能更容易(每台计算机和语言都可以做到这一点。在Unix中它只是date "%j")。

解决方案

将一个repeat_value拆分为三个单独的值,并根据UNIX时间值将它们全部转换为整数。日是1-7(或周日到周六0-6),月是1-12,一年中的日是1-365(记住,我们不包括366因为我们正在以任意非飞跃为基础年)。

如果您想以原始格式提取SELECT查询中的信息,使用concat合并三列比尝试搜索和拆分一列更容易。您还可以轻松利用内置的MySQL功能,快速将您所提取的内容转换为真实的,最新的,几天,而无需您付出太多努力。

在SQL数据库中实现它:

+----+----------+--------------+--------------+------------+
| id | event_id | repeat_day   | repeat_month | repeat_doy |
+----+----------+--------------+--------------+------------+
|  1 |       11 |      *       |       *      |      *     | 
|  2 |       12 |      *       |       *      |      2     |
|  3 |       13 |      *       |       *      |      4/2   |
|  4 |       14 |     23       |       *      |      *     |
|  5 |       15 |     30       |       5      |      *     |
+----+----------+--------------+--------------+------------+

现在,无论查询的复杂程度如何,您都应该能够构建一个查询来将所有这些数据放在一起。通过规范化您的表格,您将能够充分利用关系数据库的强大功能,而无需头痛和黑客攻击。

修改 Hugo Delsing在下面的评论中提到了一个很好的观点。在我最初的例子中,我为day_of_year提供了一个闰年修复,其中我选择忽略2月29日。一个更好的解决方案消除了修复的需要。使用复合索引将day_of_year拆分为monthday。他还有几周和几周的建议,但我建议你阅读它以获取更多细节。

答案 1 :(得分:2)

尝试用以下方法写出条件:

substring_index(repeat_value,'_', 1)

而不是每天

substring_index(substring_index(repeat_value,'_', -2), '_', 1)

而不是每月 和

substring_index(substring_index(repeat_value,'_', -1), '_', 1)

而不是dayOfWeek

答案 2 :(得分:1)

如果您只想每月而不是每天发生这些事件,我认为您正在过度思考这个问题。假设您始终正确填充repeat_value,则查询非常基本。

基本上所有事件都发生在repeat_valueLIKE '%_*_%'LIKE '%_{month}_%'的每个月。

既然你提到PHP我假设你正在用PHP构建查询,因此我使用了相同的。

<?php
function buildQuery($searchDate) {
  //you could/should do some more checking if the date is valid if the user provides the string
  $searchDate = empty($searchDate) ? date("Y-m-d") : $searchDate;

  $splitDate = explode('-', $searchDate);
  $month = $splitDate[1];

  //Select everything that started after the searchdate
  //the \_ is because else the _ would match any char.
  $query = 'SELECT *
            FROM RepeatEvent
            JOIN `Event` 
            ON `Event`.`id` = `RepeatEvent`.`event_id`
            WHERE `Event`.`start_date_time` < \''.$searchDate.'\'
            AND
            (
              `RepeatEvent`.`repeat_value` LIKE \'%\_'.$month.'\_%\'
              OR `RepeatEvent`.`repeat_value` LIKE \'%\_*\_%\'
            )
            ';

  return $query;                
}

//show querys for all months on current day/year
for ($month = 1; $month<=12; $month++) {
  echo buildQuery(date('Y-'.$month.'-d')) . '<hr>';
}

?>

现在,如果repeat_value可能出错,您可以添加一个简单的正则表达式检查,以确保该值始终为*_*_**_*_*/*

答案 3 :(得分:0)

您可以在MySQL中使用基本正则表达式:

http://dev.mysql.com/doc/refman/5.0/en/pattern-matching.html

对于5月(第一天)的月度活动,您可以使用这样的模式(未经测试):

[0-9\*]+\_[5\*]\_1

您可以通过PHP生成此模式