在数据库中将扩展名前的文件名增加 1
Posted
技术标签:
【中文标题】在数据库中将扩展名前的文件名增加 1【英文标题】:Increment File Name Before Extension By 1 in the Database 【发布时间】:2014-04-28 16:59:33 【问题描述】:我有一个脚本,它上传文件并将文件名的详细信息存储在数据库中。当文档被上传时,如果 DOCUMENT_ID 已经存在,我希望能够更新数据库中文件的名称,以通过增量编号进行处理,例如 _1、_2、_3(在文件扩展名之前)。表结构如下所示:
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。
这是我到目前为止所达到的。我已检索到最后上传的文件详细信息:
$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 是否已存在所需的详细信息。现在我认为最好通过执行以下操作来查看它是否存在:
$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? */
正如您从上面看到的,我需要在那里进行更新,基本上检查名称后是否存在数字,如果存在则将其加一。在 else 语句中,即如果 DOCUMENT_ID 不存在,我可以使用 _1.docx 添加第一个,这样增量会更容易?
如果 DOCUMENT_ID 已存在,则前半部分的更新将需要检查扩展前的最后一个数字并增加 +1,因此如果它是 _1,那么下一个将是 _2。不知道如何做到这一点。我想要的最终结果是:
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
我希望能解释一下,谢谢你的帮助。
干杯, 安迪
【问题讨论】:
我会使用 ON UPDATE 触发器。监听更改名称并从触发器更新 NAME。 好的,你有这方面的例子吗?一直在为如何执行此更新而摸不着头脑,目前没有任何进展。谢谢 有没有人知道我怎样才能让它工作? ...为什么这很重要?您对需要更改文件名的文档做了什么?如果我稍后去寻找我的文件,如果 1) 名称已更改或 2) 我得到一个不同的文件,我会感到非常惊讶。如果这是为了响应操作系统的重命名,那你就完蛋了——你不能保证两个交错的线程不会交换值(或更糟)。DOCUMENT_ID
怎么了?你怎么知道文件是相关的?如果没有文件扩展名会怎样?如果有多个(或多部分)扩展会怎样?
很抱歉,这与问题有什么关系?要么回答,要么不回答。我们对文件的处理是我们的业务。供您参考,此信息不会向客户展示,而是作为版本历史记录保存在数据库的表中。
【参考方案1】:
在 MySQL 中生成序列 ID 值以表示基于修订 ID 的命名约定
我使用
MySQL 5.5.32
来开发和测试这个解决方案。请务必查看我的解决方案的底部部分,了解一些家庭作业,以供您将来在整体设计方法中考虑。
要求和初步意见总结
外部脚本写入文档历史记录表。有关用户提交文件的元信息保存在此表中,包括其用户指定的名称。 OP 请求 SQL 更新语句或 DML 操作的程序块,将原始文档名称重新分配给代表离散 REVISION ID
概念的名称。
ID
DOCUMENT_ID
(可能由脚本本身在外部分配的数字 id)和MODIFIED
(表示提交/记录文档的最新版本的日期类型值)之间的关系中也存在隐含的业务密钥.
尽管其他 RDBMS 系统具有有用的对象和内置功能,例如 Oracle 的 SEQUENCE 对象和分析函数,但 MySQL 的基于 SQL 的功能提供了一些选项。
设置工作模式
以下是用于构建此解决方案中讨论的环境的 DDL 脚本。它应该与 OP 描述相匹配,但有一个例外(下面讨论):
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_ID
和 MODIFIED
。
如何提供排序的修订 ID 分配
基于 SQL 的分区行计数的创造性解决方案在较早的帖子中:@bobince 的ROW_NUMBER() in MySQL。
适用于此任务的 SQL 查询:
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;
使用提供的测试数据得出的查询结果:
| 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 |
请注意,修订 ID 序列遵循每个版本签入的正确顺序,并且在计算与不同文档 ID 相关的新修订系列时,修订序列会正确重置。
编辑:@ThomasKöhne 的一个很好的评论是考虑将此
REVISION_ID
保留为版本跟踪表的持久属性。这可以从分配的文件名派生,但它可能是首选,因为对单值列的索引优化更有可能起作用。单独的修订 ID 可能对其他用途很有用,例如创建准确的SORT
列以查询文档的历史记录。
使用 MySQL 字符串操作函数
修订标识还可以从附加约定中受益:列名宽度的大小应调整为也适应附加的修订 ID 后缀。一些有用的 MySQL 字符串操作:
-- 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 查询
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;
使用修改后的文件名输出
| 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
表所需的值。检查MODIFIED
列中的DOCUMENT_ID
= 82 表明,对于复合业务键的这一部分,签入修订按正确的顺序编号。
查找未处理的文档记录
如果文件名格式相当一致,SQL LIKE
运算符可能足以识别已更改的记录名称。 MySQL 还通过REGULAR EXPRESSIONS
提供过滤功能,这为解析文档名称值提供了更大的灵活性。
剩下的就是弄清楚如何只更新一条记录或一组记录。放置过滤条件的适当位置将位于别名表之间连接之后的查询的最外层:
...
and docrec.modified = rev.modified
WHERE docrec.id = ??? ;
还有其他地方可以优化以获得更快的响应时间,例如在派生修订 ID 值的内部子查询中...您对感兴趣的特定记录集了解得越多,您就可以分割开头的 SQL 语句以仅查看感兴趣的内容。
家庭作业:对解决方案的一些结束评论
这些东西完全是可选的,它们代表了在撰写本文时想到的关于设计和可用性方面的一些想法。
两步法还是一步法?
在当前设计中,每条记录有两个离散操作:INSERT
通过脚本,然后UPDATE
通过 SQL DML 调用对值进行操作。必须记住两个 SQL 命令可能很烦人。考虑为仅插入操作构建第二个表。
使用第二个表 (DOCUMENT_LIST
) 保存几乎相同的信息,除了可能有两列:
BASE_FILE_NAME
(即 doc.docx 或 document.docx)可能适用于多个 HISTORY_ID 值。
FILE_NAME
(即 doc_1.docx、doc_2.docx 等)对于每条记录都是唯一的。
在源表DOCUMENT_HISTORY
上设置一个数据库TRIGGER
并将我们开发的SQL 查询放入其中。这将在脚本填充历史记录表后的大致同一时刻自动填充正确的修订文件名。
何必呢? 这个建议主要适合您的数据库设计的
SCALABILITY
类别。修订名称的分配仍然是一个两步过程,但第二步现在在数据库中自动处理,而您必须记住在历史表顶部调用 DML 操作的任何地方都包含它。
管理别名
我没有在任何地方看到它,但我认为USER
最初为被跟踪的文件分配了一些名称。最后,这似乎无关紧要,因为它是系统的最终用户永远不会看到的内部跟踪的东西。
为了您的信息,此信息不会向客户描述,它作为版本历史保存在数据库中的表中...
如果“基本”名称在给出后保持不变,则读取给定文档的历史记录会更容易:
在上面的数据示例中,除非知道DOCUMENT_ID
,否则可能不清楚列出的所有文件名都是相关的。这可能不一定是问题,但从语义的角度来看,将用户分配的文件名分隔为 ALIASES
是一种很好的做法,可以随时更改和分配。
考虑设置一个单独的表来跟踪最终用户给出的“用户友好”名称,并将其与它应该代表的文档 ID 相关联。一个用户可能会提出成百上千个重命名请求……而后端文件系统使用更简单、更一致的命名方法。
【讨论】:
【参考方案2】:这是工作更新查询
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 - UPDATE query based on SELECT Query
使用下面的页面生成Revision ID
:
ROW_NUMBER() in MySQL
还使用了 Richard Pascual 在他详尽的回答中提供的架构。
希望此查询可以帮助您根据需要命名文档。
【讨论】:
【参考方案3】:我最近也遇到了类似的问题,但是我使用的是 MSSQL 并且我没有 MySQL 语法,所以这里是一个 T-SQL 代码。希望对你有帮助!
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
【讨论】:
以上是关于在数据库中将扩展名前的文件名增加 1的主要内容,如果未能解决你的问题,请参考以下文章
SQL SERVER 2008怎么设置自动删除三天前的数据?