我有一个asp.net-mvc网站,我有一个html数据表,其中一列是排名,表示该行的优先级(每行代表一个请求)。我允许人们使用新数字连续编辑数据,但它实际上并不会影响任何其他行(例如,没有任何东西阻止人们输入另一行中存在的值)
我正在寻找一种更有效的前端和快速后端的解决方案。我将分解每一个:
我想要一些功能,我可以上下拖动行或点击向上/向下箭头,或者当我连续输入排名值时,它会更新所有其他行的排名。我基本上不希望用户必须更新每一行,如果有一个排名为#1的新条目(基本上必须更新前一个1 => 2,前一个2 => 3,前一个3 => ; 4等。
是否有任何jquery库或有用的模式有助于支持此功能(以避免必须从头开始连接),因为这看起来像我可以想象许多其他人使用的通用功能。
作为参考,如果有人熟悉JIRA Greenhopper Planning board,那么这将是理想的模仿示例。
我的相关问题是,当我更新后端时,我试图找出如何避免这种操作真的很慢。如果我的数据库表中有一个排名字段,有人将项目更新为第一,我基本上必须在每一行上运行更新查询,将值更改为现有值+ 1.是否有一些模式可以执行此操作更有效?
答案 0 :(得分:4)
前端查看jQueryUI's Sortable功能。它通常应用于<ul/>
或<ol/>
元素,但如果需要,也可以快速搜索turns up guides以适应<table/>
元素。
将一个插入级联到许多更新中的后端问题可以通过将其视为链接列表来解决。
我假设您将数据存储为:
Rank | Key
1 | Zebra
2 | Pig
3 | Pony
因此,插入新行可能需要更新可能的每个现有行:
Rank | Key
1 | Penguin * Inserted
2 | Zebra * Updated
3 | Pig * Updated
4 | Pony * Updated
相反,通过将它们与表格中的另一行相关联来存储它们的订单。
Key | ComesAfter
Zebra | null
Pig | Zebra
Pony | Pig
因此,插入新行最多只需要一次更新。
Key | ComesAfter
Penguin | null * Inserted
Zebra | Penguin * Updated
Pig | Zebra * Unchanged
Pony | Pig * Unchanged
当然,你可以使用这个
的ID然而
您可能会过早优化;即使在发布许多更新时,一些简单的SQL工作也可能证明是非常有效的。考虑一下:
Key | Rank
Zebra | 3
Pig | 1
Pony | 2
请注意,Value在此处充当自然键,它也可能具有ID(与Rank
不同)。
要将新行添加为Rank 1,您需要执行以下操作:
begin tran
insert values ('Penguin', 1)
update table set Rank = Rank + 1 where Rank >= 1
commit
添加索引和可能的分区(取决于表的大小)将提供很好的性能改进 - 唯一的权衡是更偶尔的数据库管理。
这里的主要问题是你需要哪个方面:快速读取,快速写入或简单设计?你可以有三个中的两个:)
答案 1 :(得分:2)
对于你的后端:是你纯粹猜测的问题?或者你真的试过了吗?
如果您的表在其“排名”列上有索引,则运行以下查询应该没有问题:
UDPATE table SET rank=rank+1 WHERE rank >= {begin value} AND rank <= {end value};
即使您的表达到1M行范围,您的查询也会影响数千行。
您需要根据自己的需要调整指数 - 例如如果您的查询实际上是:
UDPATE table SET rank=rank+1 WHERE listID = {given list ID} AND rank >= {begin value} AND rank <= {end value};
你应该索引listID, rank
(按此顺序)。
对于前端:,如其他答案中所述,请阅读jqueryUI Sortable widget的文档
答案 2 :(得分:1)
对于向上和向下移动项目,您可以使用jQuery UI sortable。最简单的解决方案是在所有项目中添加一个名为score的浮点数,可能是double。众所周知,这是一种更有效的技术,可以在订购时保持一组有序元素的变化。必须在从DB到表示层的所有层中保留分数。当你需要在另外两个元素之间放置一个元素时,你只需将它的浮点数设置为两个元素的平均值,这样你就可以确定它位于它们之间。
您可以从按其分数排序的数据库中检索所有项目。
您使用两个假项目(未呈现,未插入数据库,但纯虚拟)启动算法,其分数为0和1.所有“真实”项目都将插入它们之间。因此,你的第一个元素将得到0.5分,依此类推。
您必须在DB中存储您达到的两个分数之间的最小距离。当这个数字变得太小并且接近最小精度你可能得到一个双倍时,你必须通过分配分数来重组你的表,这样两个连续元素之间的所有分数距离是相同的并且等于1 /(项目数量。)
如果您需要向用户显示“经典排名”1,2,3 ......您可以在呈现页面之前在网页中创建它,并在每次修改用户之后立即创建。 更新所有排名的成本等于屏幕中显示的项目。这不是一个很大的原因因为浏览器必须重新渲染屏幕上的所有元素,其成本高于重写屏幕上所有排名的成本,因此不会降低性能。 ...重要...... 如果你有虚拟滚动或分页...等级重写仅涉及屏幕上的活动元素,因为等级的唯一目的是向用户显示因为所有的计算都是用分数完成的。
答案 3 :(得分:0)
以下是后端的几个想法。
在后端创建值如下: 1 - 排名#1 100 - 排名#2 200 - 排名#3 等等......
这将允许您在其他两个之间添加一些值时更新最小行数。
例如,如果要在排名#3处插入值,则只需输入值150即可。
向用户显示数据时,您不会在数据库中显示实际值,而是使用ROW_NUMBER函数以用户友好的方式显示它。
如果您想在地点#1添加某些内容,您只需输入一个小于现有首位的值即可。只要订单正确,实际值最终看起来像-250并不重要,ROW_NUMBER函数将负责演示。
偶尔你需要做的就是重新排序数值,这样两者之间就有足够的空间。
答案 4 :(得分:0)
扩展@ Dwoolk的答案,您可以使用浮点数进行排序。那么你应该总是(至少很长一段时间)能够在两个现有值之间找到新值。
答案 5 :(得分:0)
Angularjs在处理这类事情的“布线”方面非常棒。以下是Francesco建议的算法示例,该算法使用Angular控制器处理项目/分数与表格之间的所有绑定以及处理排序/重新评分的指令:
angular.module("listApp", []).
controller("listController", function($scope) {
var sortByKey = function(arr, key) {
arr.sort(function(a, b) {
if (a[key] > b[key]) {
return -1;
} else if (b[key] > a[key]) {
return 1;
} else {
return 0;
}
});
};
$scope.items = [{id: 1111, name: "Item 1", score:0.2},
{id: 3333, name: "Item 2", score:0.5},
{id: 5555, name: "Item 3", score:0.7}];
sortByKey($scope.items, "score");
}).
directive("sortable", function() {
return function(scope, element) {
var startIndex = null;
var stopIndex = null;
var averageOfNeighbors = function(items, index) {
var beforeScore = index === 0 ? 1 : items[index -1].score;
var afterScore = index === items.length - 1 ? 0 : items[index + 1].score;
return (beforeScore + afterScore) / 2;
}
element.sortable( {
start: function(e, ui) {
startIndex = ui.item[0].sectionRowIndex;
},
stop: function(e, ui) {
stopIndex = ui.item[0].sectionRowIndex;
if (stopIndex !== startIndex) {
var toMove = scope.items.splice(startIndex, 1)[0];
scope.items.splice(stopIndex, 0, toMove);
toMove.score = averageOfNeighbors(scope.items, stopIndex);
console.log("Set item(" + toMove.id + ").score to " + toMove.score);
scope.$apply();
}
}
});
}
})
然后,这可以简单地绑定到这样的表:
<tbody sortable>
<tr ng-repeat="item in items">
<td>{{item.name}}</td>
<td>{{$index + 1}}</td>
<td>{{item.score}}</td>
</tr>
</tbody>
另请注意,每次重新排序后,都可以进行AJAX调用以更新数据库中的单个得分记录。
答案 6 :(得分:0)
我在我正在进行的项目中完成了类似的功能。
对于前端:我使用了 jQuery before()
和after()
API来
移动行,例如:
$(prevRow)。之前(currentRow)
对于后端:我在数据库中有一个存储该列的int列 每条记录的订单。
现在,一旦行排列在前端,所有行的新序列将通过Ajax发送到数据库并更新所有记录(在我的情况下,只需要更新表上的过滤记录)。
这可能不是最好的解决方案,但我找不到任何其他方法来完成这项工作。
答案 7 :(得分:0)
This是完整实施代码的一个很好的例子。