5

我有一个 asp.net-mvc 网站,我有一个 html 数据表,其中一列是排名,它代表该行的优先级(每行代表一个请求)。我允许人们使用新数字编辑一行中的数据,但它实际上不会影响任何其他行(例如,没有什么能阻止人们输入一个已经存在于另一行中的值)

我正在寻找一种解决方案来拥有更有效的前端和快速的后端。我将逐一分解:

  1. 前端:

我想要一些功能,我可以上下拖放行或单击向上/向下箭头,或者当我在一行中输入排名值时,它会更新所有其他行的排名。如果有排名第一的新条目,我基本上不希望用户必须更新每一行(基本上必须更新之前的 1 => 2、之前的 2=>3 和之前的 3=>4 等...

是否有任何 jquery 库或有用的模式可以帮助支持此功能(以避免必须从头开始连接它),因为这似乎是我可以想象许多其他人使用的通用功能。

作为参考,如果有人熟悉JIRA Greenhopper 规划板,这将是一个理想的模仿示例。

  1. 后端

我的相关问题是,当我去更新后端时,我试图弄清楚如何避免这种操作非常缓慢。如果我的数据库表中有一个排名字段并且有人将一个项目更新为第一,我基本上必须在每一行上运行更新查询以将值更改为现有值 + 1。是否有一些模式可以做到这一点更有效?

4

8 回答 8

5

对于前端,请查看jQueryUI 的 Sortable功能。它通常应用于<ul/>or<ol/>元素,但如果需要,快速的 Google 搜索也会显示调整它以适应元素的指南<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

添加索引和可能的分区(取决于表的大小)将提供很大的性能改进 - 唯一的权衡是更偶尔的数据库管理。

这里的主要问题是您需要哪个方面:快速读取、快速写入或简单设计?您可以拥有三个中的两个:)

于 2013-08-21T01:57:49.283 回答
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 小部件的文档

于 2013-08-23T21:20:09.423 回答
1

对于向上和向下移动项目,您可以使用jQuery UI sortable。最简单的解决方案是向所有项目添加一个称为分数的浮点数,可能是双精度数。众所周知,这是一种在排序可能发生变化时保持一组元素有序的更有效的技术。这个分数必须保存在从数据库到表示层的所有层中。当您需要将一个元素放在其他两个元素之间时,您只需将其浮动设置为两个元素的平均值,这样您就可以确定它位于它们之间。

您从数据库中检索按分数排序的所有项目。

您从两个分数为 0 和 1 的假项目(未渲染,也未插入数据库,而是纯虚拟)开始算法。所有“真实”项目将插入它们之间。因此,您的第一个元素将获得 0.5 分,依此类推。

您必须在数据库中存储您达到的两个分数之间的最小距离。当这个数字变得太小并且接近你可能得到的最小精度时,你必须通过分配分数来重新组织你的表格,这样两个连续元素之间的所有分数距离都相同并且等于 1/(项目数)。

如果您需要向用户显示“经典排名”1、2、3....,您可以在渲染页面之前在网页中创建它,并且在每次用户修改之后立即创建它。更新所有等级的成本等于屏幕中显示的项目。这不是很大 这不会导致性能下降,因为屏幕上的所有元素都必须由浏览器重新渲染,其成本高于在屏幕上重写所有等级的成本。...重要...如果您有虚拟滚动或分页...排名重写仅涉及屏幕上的活动元素,因为排名的唯一目的是向用户显示,因为所有计算都使用分数。

于 2013-08-23T19:59:03.457 回答
0

扩展@Dwoolk 的答案,您可以使用浮点数进行排序。那么你应该总是(至少在很长一段时间内)能够在两个现有值之间找到新值。

于 2013-08-21T16:47:36.960 回答
0

这是后端的几个想法。

使后端中的值类似于:1 – rank #1 100 – rank #2 200 – rank #3 等等……

当您在其他两个之间添加一些值时,这将允许您更新最少的行数。

例如,如果您想在排名 #3 处插入一个值,您只需输入值 150。

向用户显示数据时,您不会显示数据库中的实际值,而是使用ROW_NUMBER函数以用户友好的方式显示它。

当您想在第 1 位添加某些内容时,您只需输入一个比现有第一个位置小一些的值。只要顺序正确,实际值是否最终看起来像 -250 都没关系,ROW_NUMBER 函数将负责演示。

您偶尔要做的就是对值重新排序,以便两者之间有足够的空间。

于 2013-07-19T10:17:35.680 回答
0

This is a great example with full implementation code.

于 2013-08-27T13:34:54.650 回答
0

我在我正在从事的项目中完成了类似的功能。

  • 对于前端:我使用jQuery before()after()API 来移动行,例如:

    $(prevRow).before(currentRow)

  • 对于后端:我在数据库中有一个 int 列,用于存储每条记录的顺序。

现在,一旦在前端排列了行,所有行的新序列通过 Ajax 发送到数据库并更新所有记录(在我的情况下,只需要更新表上过滤的记录)。

这可能不是最好的解决方案,但我找不到任何其他方法来完成这项工作。

于 2013-08-27T10:29:06.437 回答
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>

这是一个Plunker。

另外,请注意,在每次重新排序之后,可以进行 AJAX 调用来更新数据库中的单个分数记录。

于 2013-08-27T03:12:49.727 回答