关于mysql:在数据库中将扩展前的文件名增加1

Increment File Name Before Extension By 1 in the Database

我有一个脚本,它上传文件并将文件名的详细信息存储在数据库中。当文档上传时,如果DOCUMENT_ID已经存在,我希望能够更新数据库中文件的名称,以增加数字,例如_1,_2,_3(在文件扩展名之前)。表结构如下所示:

1
2
3
4
5
ID   |  DOCUMENT_ID  |  NAME            |  MODIFIED     |   USER_ID
33   |  81           |  document.docx   |  2014-03-21   |   1
34   |  82           |  doc.docx        |  2014-03-21   |   1
35   |  82           |  doc.docx        |  2014-03-21   |   1
36   |  82           |  doc.docx        |  2014-03-21   |   1

所以在上面的例子中,我希望ID 35 NAME为doc_1.docx,ID 36 NAME为doc_2.docx。

这是我到目前为止所处的地方。我检索了上传的最后一个文件详细信息:

1
2
3
4
5
6
7
8
9
10
$result1 = mysqli_query($con,"SELECT ID, DOCUMENT_ID, NAME, MODIFIED
FROM b_bp_history ORDER BY ID DESC LIMIT 1"
);

while($row = mysqli_fetch_array($result1))
{
$ID = $row['ID'];
$documentID = $row['DOCUMENT_ID'];
$documentName = $row['NAME'];
$documentModified = $row['MODIFIED'];
}

因此,这将为我提供我需要查看DOCUMENT_ID是否已存在的详细信息。现在我认为最好通过执行以下操作来查看它是否存在:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$sql ="SELECT ID, DOCUMENT_ID
FROM b_bp_history WHERE DOCUMENT_ID = $documentID"
;
$result2 = mysqli_query($sql);

if(mysqli_num_rows($result2) >0){

/* This is where I need my update */

} else {

/* I don't need an update in here as it will automatically add to the database
table with no number after it.  Not sure if I should always add the first one
with a _1 after it so the increment is easy? */


}

从上面我可以看到,我需要在那里进行更新,它基本上会检查名称后面是否存在数字,如果确实存在,则将其递增1。在else语句中,即如果DOCUMENT_ID尚不存在,我可以添加带有_1.docx的第一个,这样增量会更容易吗?

如果DOCUMENT_ID已经存在,则前半部分的更新需要检查扩展前的最后一个数字并增加+1,所以如果它是_1则接下来将是_2。不知道如何做到这一点。我想要的最终结果是:

1
2
3
4
5
ID   |  DOCUMENT_ID  |  NAME              |  MODIFIED     |   USER_ID
33   |  81           |  document.docx     |  2014-03-21   |   1
34   |  82           |  doc.docx          |  2014-03-21   |   1
35   |  82           |  doc_1.docx        |  2014-03-21   |   1
36   |  82           |  doc_2.docx        |  2014-03-21   |   1

我希望能解释一下,谢谢你的帮助。

干杯,
安迪


在MySQL中生成序列ID值以表示基于修订ID的命名约定

I used MySQL 5.5.32 to develop and test this solution. Be sure to review the bottom section of my solution for a few homework assignments for future consideration in your overall design approach.

Ok.

要求和初步评论摘要

外部脚本写入文档历史记录表。有关用户提交的文件的元信息保存在此表中,包括其用户指定的名称。 OP请求SQL更新语句或DML操作的过程块,它将原始文档名称重新分配给表示离散REVISION ID概念的名称。

  • 原始表设计包含一个独立的主键:ID
  • 隐含的业务密钥也存在于DOCUMENT_ID(可能由脚本本身在外部分配的数字id)和MODIFIED(表示提交/记录文档的最新版本的时间的DATE类型值)之间的关系中。
  • Although other RDBMS systems have useful objects and built-in features such as Oracle's SEQUENCE object and ANALYTICAL FUNCTIONS, There are options available with MySQL's SQL based capabilities.

    Ok.

    设置工作架构

    下面是用于构建此解决方案中讨论的环境的DDL脚本。它应该与OP描述匹配一个例外(下面讨论):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     CREATE TABLE document_history
    (
     id int auto_increment primary key,
     document_id int,
     name varchar(100),
     modified datetime,
     user_id int
     );

     INSERT INTO document_history (document_id, name, modified,
       user_id)
     VALUES
       (81, 'document.docx', convert('2014-03-21 05:00:00',datetime),1),
       (82, 'doc.docx', convert('2014-03-21 05:30:00',datetime),1),
       (82, 'doc.docx', convert('2014-03-21 05:35:00',datetime),1),
       (82, 'doc.docx', convert('2014-03-21 05:50:00',datetime),1);

     COMMIT;

    DOCUMENT_HISTORY的设计带有DATETIME类型的列,用于名为MODIFIED的列。否则,document_history表中的条目很可能会为围绕以下复合业务键组合组织的查询返回多个记录:DOCUMENT_IDMODIFIED

    如何提供顺序修订ID分配

    基于SQL的分区行计数的创造性解决方案位于较早的帖子中:@bobince的MySQL中的ROW_NUMBER()。

    适用于此任务的SQL查询:

    1
    2
    3
    4
    5
    6
    7
     select t0.document_id, t0.modified, count(*) as revision_id
       from document_history as t0
       join document_history as t1
         on t0.document_id = t1.document_id
        and t0.modified >= t1.modified
      group by t0.document_id, t0.modified
      order by t0.document_id asc, t0.modified asc;

    使用提供的测试数据生成此查询的结果:

    1
    2
    3
    4
    5
    6
     | DOCUMENT_ID |                     MODIFIED | REVISION_ID |
     |-------------|------------------------------|-------------|
     |          81 | March, 21 2014 05:00:00+0000 |           1 |
     |          82 | March, 21 2014 05:30:00+0000 |           1 |
     |          82 | March, 21 2014 05:35:00+0000 |           2 |
     |          82 | March, 21 2014 05:50:00+0000 |           3 |

    请注意,修订标识序列遵循检入每个版本的正确顺序,并且在计算与不同文档标识相关的新系列修订时,修订顺序会正确重置。

    编辑:@ThomasK的一个很好的评论?hne是考虑将此REVISION_ID保持为版本跟踪表的持久属性。这可以从指定的文件名派生,但可能是首选,因为对单值列的索引优化更有可能起作用。仅修订版ID可用于其他目的,例如创建准确的SORT列以查询文档的历史记录。

    使用MySQL字符串操作函数

    修订标识还可以受益于其他约定:列名称宽度的大小应该适应附加的修订版ID后缀。一些MySQL字符串操作将有助于:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
     -- Resizing String Values:

     SELECT SUBSTR('EXTRALONGFILENAMEXXX',1,17) FROM DUAL

     | SUBSTR('EXTRALONGFILENAMEXXX',1,17) |
     |-------------------------------------|
     |                   EXTRALONGFILENAME |    


     -- Substituting and Inserting Text Within Existing String Values:

     SELECT REPLACE('THE QUICK <LEAN> FOX','<LEAN>','BROWN') FROM DUAL

     | REPLACE('THE QUICK <LEAN> FOX','<LEAN>','BROWN') |
     |--------------------------------------------------|
     |                              THE QUICK BROWN FOX |


     -- Combining Strings Using Concatenation

     SELECT CONCAT(id, '-', document_id, '-', name)
       FROM document_history

     | CONCAT(ID, '-', DOCUMENT_ID, '-', NAME) |
     |-----------------------------------------|
     |                      1-81-document.docx |
     |                           2-82-doc.docx |
     |                           3-82-doc.docx |
     |                           4-82-doc.docx |

    将它们全部拉到一起:使用修订符号构造新文件名

    使用上面的上一个查询作为基础内联视图(或子查询),这是为给定修订日志记录生成新文件名的下一步:

    带有修订文件名的SQL查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     select replace(docrec.name, '.', CONCAT('_', rev.revision_id, '.')) as new_name,
        rev.document_id, rev.modified
       from (
              select t0.document_id, t0.modified, count(*) as revision_id
                from document_history as t0
                join document_history as t1
                  on t0.document_id = t1.document_id
                 and t0.modified >= t1.modified
               group by t0.document_id, t0.modified
               order by t0.document_id asc, t0.modified asc
           ) as rev

      join document_history as docrec
        on docrec.document_id = rev.document_id
       and docrec.modified = rev.modified;

    输出修改后的文件名

    1
    2
    3
    4
    5
    6
     |        NEW_NAME | DOCUMENT_ID |                     MODIFIED |
     |-----------------|-------------|------------------------------|
     | document_1.docx |          81 | March, 21 2014 05:00:00+0000 |
     |      doc_1.docx |          82 | March, 21 2014 05:30:00+0000 |
     |      doc_2.docx |          82 | March, 21 2014 05:35:00+0000 |
     |      doc_3.docx |          82 | March, 21 2014 05:50:00+0000 |

    这些(NEW_NAME)值是更新DOCUMENT_HISTORY表所需的值。对DOCUMENT_IDDOCUMENT_ID = 82的检查表明,对于复合业务密钥的这一部分,签入修订版的编号顺序正确。

    查找未处理的文档记录

    如果文件名格式相当一致,则SQL LIKE运算符可能足以识别已经更改的记录名称。 MySQL还通过REGULAR EXPRESSIONS提供过滤功能,通过文档名称值解析提供了更大的灵活性。

    剩下的就是弄清楚如何仅更新单个记录或一组记录。在别名表之间的连接之后,放置过滤条件的适当位置将位于查询的最外部:

    1
    2
    3
     ...
       and docrec.modified = rev.modified
     WHERE docrec.id = ??? ;

    还有其他地方可以优化更快的响应时间,例如在内部子查询中导出修订版ID值...您对您感兴趣的特定记录集的了解越多,就可以对开头进行细分SQL语句只关注感兴趣的内容。

    家庭作业:对解决方案的一些结束评论

    这些东西纯粹是可选的,它们代表了在设计和可用性方面想到的一些侧面想法。

    两步或一步?

    使用当前设计,每个记录有两个离散操作:脚本INSERT,然后通过SQL DML调用获取值的UPDATE。必须记住两个SQL命令可能很烦人。考虑构建为仅插入操作构建的第二个表。

  • 使用第二个表(DOCUMENT_LIST)来保存几乎相同的信息,除了可能的两列:

  • BASE_FILE_NAME(即doc.docx或document.docx),可以申请多个HISTORY_ID值。
  • FILE_NAME(即doc_1.docx,doc_2.docx等),每个记录都是唯一的。
  • 在源表上设置数据库TRIGGERDOCUMENT_HISTORY并将我们在其中开发的SQL查询放入其中。这将在脚本填充历史记录表后的大致相同时刻自动填充正确的修订文件名。

  • WHY BOTHER? This suggestion mainly fits under the category of SCALABILITY of your database design. The assignment of a revision name is still a two step process, but the second step is now handled automatically within the database, whereas you'd have to remember to include it everywhere you invoked a DML operation on top of the history table.

    Ok.

    管理别名

    我没有在任何地方看到它,但我认为USER最初为被跟踪的文件指定了一些名称。最后,它似乎无关紧要,因为它是系统的最终用户永远不会看到的内部跟踪的东西。

    For your information, this information isn't portrayed to the customer, it is saved in a table in the database as a version history...

    Ok.

    如果"基础"名称在给定后保持不变,则阅读给定文档的历史将更容易:

    Varying File Names

    在上面的数据示例中,除非DOCUMENT_ID已知,否则可能不清楚列出的所有文件名是否相关。这可能不一定是个问题,但从语义的角度来看,将用户指定的文件名分隔为ALIASES是一种很好的做法,可以随时随意更改和分配。

    考虑设置一个单独的表来跟踪最终用户给出的"用户友好"名称,并将其与应该表示的文档ID相关联。用户可以进行数百或数千次重命名请求...而后端文件系统使用更简单,更一致的命名方法。

    好。


    这是工作更新查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    UPDATE document_history
    INNER JOIN (SELECT dh.id, IF(rev.revision_id = 0, dh.name,REPLACE(dh.name, '.', CONCAT('_', rev.revision_id, '.'))) AS new_name,
    rev.document_id, rev.modified
    FROM (
    SELECT t0.document_id, t0.modified, count(*) - 1 AS revision_id
    FROM document_history as t0
    JOIN document_history as t1
    ON t0.document_id = t1.document_id
    AND t0.modified >= t1.modified
    GROUP BY t0.document_id, t0.modified
    ORDER BY t0.document_id ASC, t0.modified ASC) AS rev
    JOIN document_history dh
    ON dh.document_id = rev.document_id
    AND dh.modified = rev.modified) update_record
    ON document_history.id = update_record.id
    SET document_history.name = update_record.new_name;

    您可以在http://www.sqlfiddle.com/#!2/9b3cda/1上看到SQL Fiddle

    我使用UPDATE上此页面上提供的信息来汇编我的查询:

    MySQL - 基于SELECT Query的UPDATE查询

    使用下面的页面生成Revision ID

    MySQL中的ROW_NUMBER()

    还使用了Richard Pascual在其精心解答的答案中提供的架构。

    希望此查询可以帮助您根据需要为文档命名。


    我最近遇到了类似的麻烦,但我使用的是MSSQL而且我没有MySQL语法,所以这里有一个T-SQL代码。 希望,它会帮助你!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    declare
        @id int,
        @document_id int,
        @document_name varchar(255),
        @append_name int,
        @name varchar(255),
        @extension varchar(10)

    set @append_name = 1

    select top 1
        @id = ID,
        @document_id = DOCUMENT_ID,
        @document_name = NAME
    from
        b_bp_history

    while exists (
        select *
        from b_bp_history
        where
            NAME = @document_name and
            DOCUMENT_ID = @document_id and
            ID <> @id)
    begin
        set @name = ''
        set @extension = ''

        declare @dot_index int -- index of dot-symbol in document name
        set @dot_index = charindex('.', reverse(@document_name))

        if (@dot_index > 0)
        begin      
            set @name = substring(@document_name, 0, len(@document_name) - @dot_index + 1)
            set @extension = substring(@document_name, len(@document_name) - @dot_index + 2, len(@document_name) - len(@name))
        end
        else
            set @name = @document_name

        if (@append_name > 1) -- if not first try to rename file
        begin
            if (right(@name, len(cast(@append_name - 1 as varchar)) + 1)) = '_' + cast(@append_name - 1 as varchar)
            begin
                set @name = substring(@name, 0, len(@name) - (len(cast(@append_name - 1 as varchar))))
            end
        end

        set @name = @name + '_' + cast(@append_name as varchar)

        if (len(@extension) > 0)
            set @document_name = @name + '.' + @extension
        else
            set @document_name = @name


        set @append_name = @append_name + 1
    end

    update b_bp_history
    set NAME = @document_name
    where ID = @id