2

我正在尝试提出一个网络应用程序,有点像分步程序应用程序。我无法真正描述它,也不知道如何称呼它,但它是一个应用程序,它提供了有关如何实现某些东西的分步说明,例如工作申请。

“帮助用户做事的应用程序。”

所以我设置了一些 GUI,现在我面临问题的核心:数据库设置。

在我设计的 GUI 中,我决定是这样的:

图 1.0

传奇:

  • 带支票的绿色- 已完成
  • 橙色- 用户当前处于该步骤
  • 灰色- 他还没有开始那里的任何子步骤。

如图所示,我有 3 个顶级程序,以及每个程序下 其他一些子程序。该图片中缺少的一件事是顶级程序也属于某个类别

而我想要实现的是跟踪用户的活动。这是交易:

  1. 没有必要必须完成第一个顶层程序才能进入下一个程序,您可以跳过并返回 -> 这没有问题。
  2. 完成所有子过程后,顶级过程为该特定用户标记 1,表示它已完成。
  3. 类别也是如此,当该类别下的所有顶级程序都完成时,该类别为该特定用户标记 1,表示他/她完成了该类别。

我打算像这样设置我的数据库:

-tbl_users-

 id    |    username    |    password    |
 -----------------------------------------
   1   |    some_user   |  adf8jkdfndsa  |
...

tbl_step_cat

 id    |      cat_name      |
 ---------------------------
   1   |    some_category   |
...

tbl_steps

 id    |      step_shortdesc     |    step_longdesc      |  cat_id 
 -------------------------------------------------------------------
   1   |      some step one      |  do the following...  |     1
 -------------------------------------------------------------------
   2   |      some step two      |  do the following...  |     1
 -------------------------------------------------------------------
   3   |      some step three    |  do the following...  |     2
...

tbl_substeps

 id    |     substep_shortdesc   |    substep_longdesc   |  step_id 
 -------------------------------------------------------------------
   1   |    some substep one     |  do the following...  |     1
 -------------------------------------------------------------------
   2   |    some substep two     |  do the following...  |     1
 -------------------------------------------------------------------
   3   |    some substep three   |  do the following...  |     1
 -------------------------------------------------------------------
   4   |    some substep a       |  do the following...  |     2
 -------------------------------------------------------------------
   5   |    some substep b       |  do the following...  |     2
 -------------------------------------------------------------------
   6   |    some substep 1       |  do the following...  |     3
...

然后是用户和步骤之间的关系表

tbl_user_stepcat

 id    |     user_id   |    stepcat_id   |  datetime 
 -------------------------------------------------------------------
   1   |      1        |       1         |  sometime
 -------------------------------------------------------------------
   2   |      1        |       2         |  sometime
 -------------------------------------------------------------------

tbl_user_step

 id    |     user_id   |     step_id     |  datetime 
 -------------------------------------------------------------------
   1   |      1        |       1         |  sometime
 -------------------------------------------------------------------
   2   |      1        |       2         |  sometime
 -------------------------------------------------------------------

tbl_user_substep

 id    |     user_id   |    substep_id   |  datetime 
 -------------------------------------------------------------------
   1   |      1        |       1         |  sometime
 -------------------------------------------------------------------
   2   |      1        |       2         |  sometime
 -------------------------------------------------------------------

对不起,如果这有点长,那只是因为代码。

现在我的问题是如何返回我想要的结果。如您所见,当用户登录应用程序时,我希望他/她立即看到这些信息。

到目前为止我还没有尝试过,因为我的大脑刚刚关闭并且注意力不集中,这是我迄今为止最好的时间。

如果我要编写这个应用程序的 SQL,我当然会做多个连接。

我想首先选择所有类别并将其输出给用户。

SELECT * FROM tbl_step_cat

这会给我所有的类别,我接下来要做的是找出哪个步骤完成与否,这样我就可以做“造型”

我可能会

SELECT cat_name FROM tbl_step_cat JOIN
tbl_user_stepcat ON tbl_user_stepcat.stepcat_id = tbl_step_cat.id
...

我现在注意力不集中,无法思考。我该怎么做:

  1. 输出所有猫/步骤/子步骤
  2. 在关系表中获取那些有条目的人,这意味着它已经完成
  3. 在哪里user = session['user']

非常感谢,我只是需要指导。

4

5 回答 5

5

好的,如果您只是指定 JOIN,那将是一个 INNER JOIN,因此将排除用户尚未完成每​​个步骤和子步骤的结果。因此,为了确保您可以获得每个联接的结果,我将其设为 LEFT JOIN。这可能会将其与空结果连接起来,这很好,因为您可以测试子类别字段是否为空,以便填充表单上的刻度部分。

我一直认为将数据库逻辑拆分为单独的查询没有什么害处,而不是尝试编写一个将所有内容连接到所有内容的巨型查询,这可能是多余的。

我想我会为每个类别做一个单独的查询,即:

(伪)

SELECT * FROM CATEGORIES 
foreach (category) {
    SELECT * FROM SUBCATEGORIES WHERE CATEGORY = CATEGORYID
    foreach (SUBCATEGORY) {
        SELECT * FROM SUBSUBCATEGORIES WHERE SUBCATEGORY = SUBCATEGORYID
    }
}

并且在每个点查询用户表以查看用户是否在您遍历列表时完成了每个步骤,并使用该循环来填充您的刻度。

它可能不像单个巨型连接那样优雅,但如果这不是你的事,我认为简化查询逻辑以帮助你理解没有任何害处。

于 2013-01-09T14:47:54.437 回答
3

您不需要这些表user_stepcatuser_step因为它们只会存储冗余信息。

每个completed状态都是由子步骤的完成触发的,并且可以通过这个查询来检索

-- cat completed or not
SELECT
  sc.*, 
  MIN( IF( uss.substep_id IS NULL, 0, 1 ) ) completed,
  IF( MIN( IF( uss.substep_id IS NULL, 0, 1 ) ), 
    MAX( uss.datetime ), 
    NULL ) completed_at
FROM
  step_cat sc
LEFT JOIN steps s ON s.cat_id = sc.id
LEFT JOIN substeps ss ON ss.step_id = s.id
LEFT JOIN user_substep uss ON uss.user_id = 1 AND uss.substep_id = ss.id
GROUP BY sc.id;

-- steps completed or not
SELECT
  s.*, 
  MIN( IF( uss.substep_id IS NULL, 0, 1 ) ) completed,
  IF( MIN( IF( uss.substep_id IS NULL, 0, 1 ) ), 
    MAX( uss.datetime ), 
    NULL ) completed_at
FROM
  steps s
LEFT JOIN substeps ss ON ss.step_id = s.id
LEFT JOIN user_substep uss ON uss.user_id = 1 AND uss.substep_id = ss.id
GROUP BY s.id;

-- substeps completed or not
SELECT
  ss.*, 
  IF( uss.substep_id IS NULL, 0, 1 ) completed,
  uss.datetime completed_at
FROM
  substeps ss
LEFT JOIN user_substep uss ON uss.user_id = 1 AND uss.substep_id = ss.id;

SQL 小提琴演示

更新

为了给您更相关的 GUI 答案,我更新了示例数据以匹配您的屏幕截图。

对于演示,您需要知道当前用户 ID、类别 ID 和步骤 ID

SET @user_id = 1; -- actual user
SET @cat_id = 1; -- actual category
SET @step_id = 2; -- actual step

你可以得到当前的类别和完整的状态

SELECT
  sc.*, 
  MIN( IF( uss.substep_id IS NULL, 0, 1 ) ) completed,
  IF( MIN( IF( uss.substep_id IS NULL, 0, 1 ) ), MAX( uss.datetime ), NULL ) completed_at
FROM
  step_cat sc
LEFT JOIN steps s ON s.cat_id = sc.id
LEFT JOIN substeps ss ON ss.step_id = s.id
LEFT JOIN user_substep uss ON 
-- limit to user
uss.user_id = @user_id 
AND uss.substep_id = ss.id
-- limit to cat
WHERE sc.id = @cat_id
GROUP BY sc.id;

类别步骤和完成状态与此

SELECT
  s.*, 
  MIN( IF( uss.substep_id IS NULL, 0, 1 ) ) completed,
  IF( MIN( IF( uss.substep_id IS NULL, 0, 1 ) ), MAX( uss.datetime ), NULL ) completed_at
FROM
  steps s
LEFT JOIN substeps ss ON ss.step_id = s.id
LEFT JOIN user_substep uss
-- limit to user_id
ON uss.user_id = @user_id 
AND uss.substep_id = ss.id
-- limit to cat_id
WHERE s.cat_id = @cat_id
GROUP BY s.id;

以及至少子步骤及其完整状态

SELECT
  ss.*, 
  IF( uss.substep_id IS NULL, 0, 1 ) completed,
  uss.datetime completed_at
FROM
  substeps ss
LEFT JOIN user_substep uss 
-- limit to user_id
ON uss.user_id = @user_id 
AND uss.substep_id = ss.id
-- limit to step_id
WHERE ss.step_id = @step_id;

SQL 小提琴演示

通过所有这 3 个查询,您可以获得 GUI 演示所需的所有信息。

于 2013-01-10T16:13:03.927 回答
1

您现在可能过于关注数据库结构:

  • 如果您在分步过程的分步页面上,您可以查看该步骤是否已完成。更新步骤的状态。
  • 如果您在一个步骤页面上,您还可以查询该步骤所属的所有步骤,并且当您在访问单个步骤页面时保持数据最新时,您可以只显示该步骤的状态。
  • 如果没有分步程序,则需要一次更新所有步骤,但仅在您第一次进入分步程序时更新。
  • 如果一个步骤和其他成就一样,那么它就像其他成就一样。将步骤的配置数据保留在成就(单步骤)之外。

然后你可以减少你的数据库结构,同时保持它更灵活,也更容易重用你的成就系统的程序。

例如,如果你决定有一天你想创造一项成就,如果有人完成了你所谓的继猫的整个类别,如果我没看错你的问题。

你需要做的就是检查一个n个成就是否已经完成。因此,无论是否包含其他成就,都将所有成就保存在一张表中)。这属于其他地方。与类别相同。

一种常见的方法是对数据进行部分去规范化,例如,您可以在每个成就中保留一些自由文本,以在其中存储一些数据,以便分解处理可以使用。

运行管理员作业以保持数据井然有序,还可以检查您是否错过了检查更高级别的一些成就。无论如何,您都需要这样做,因为当您在处理过程中引入错误的第一刻,即使在完全标准化的形式中您不会违反任何约束,您的数据也会被破坏。因此,无论如何您都需要进行“手动”检查。

因为这一切,就去做吧。不要把头包得太紧,保持简单,完成工作,然后你可以考虑改进它。我可以给出的第一个建议是保持简单,这样你就不会犯那么多错误。您的系统将具有此处的答案无论如何都无法涵盖的专业。

所以你只需要:

  1. 有一个定义成就的表
  2. 有一个表来存储用户是否完成了一项成就。
  3. 具有可以判断成就是否已完成的处理。
  4. 有处理完成一个成就。

尽管:

  • 多步形式的每一步也是一项成就。

是否是多步骤只是不是很有趣。这只是一个标准的成就。那将是最大的好处。将差异封装到处理中,而不是数据结构中。这将允许您现在和将来采用。

于 2013-01-13T13:40:50.443 回答
1

您的意思是您的 SQL 架构看起来像 step_cat <- steps <- substeps 吗?

如果是这样,表 tbl_user_stepcat 和 tbl_user_step 是无用的,因为您可以使用 JOIN 查询从给定的子步骤/步骤/猫获取所有父信息。

如果你想关注用户的成就,你应该有一个“achievement_history”表,关注用户、子步骤和定义的状态,它可能会根据其成就取一个特定的值。这样每行都引用给定的用户子步骤成就,然后每次给定子步骤将具有正确的成就状态,用户将能够处理另一个子步骤并插入新行。

请记住,使用嵌套查询是一种丑陋的方式,因为 DBMS 将永远无法计划查询,并且可能存储预定义的方式来获得应有的快速结果,如果正确设置,则使用索引和外键.

于 2013-01-13T20:57:40.967 回答
1

你必须在后端编写 3 个存储过程,并使用前端的输入 Session[Username] 调用它们。

storedprocedure1 .. listofcategories.. 输入 - 无,输出 - 类别属性

storedprocedure2.. listofcategories_filtered_by_user.. input- Session[UserName] output- 类别属性

在这个存储过程中写入你写的 Join SELECT cat_name FROM tbl_step_cat JOIN tbl_user_stepcat ON tbl_user_stepcat.stepcat_id = tbl_step_cat.id

storedprocedure3.. listofsubcategories_filtered_by_user.. input- Session[UserName] output-(子类别属性)

在这个存储过程中,为子类别编写 Join

> 现在使用输入 Session[UserName] 参数从前端调用每个存储过程

于 2013-01-14T10:39:49.437 回答