Sey*_*avi 11 mysql sql-server oracle asp.net-mvc database-design
我已经分配了一个任务来创建(相对)简单的报告系统.在这些系统中,将向用户显示报告的表格结果.表有一些字段,每个字段为每个记录中的用户提供一些信息.但问题是开发人员不会声明每个报告字段.它必须由系统用户声明.所以我的报告表是动态的.
我在" ASP.NET MVC中的数据驱动自定义视图引擎"中看到了使用Asp.net MVC Framework创建动态表单的示例,但我不知道这对我的系统是否合适.
UPDATE1:
目前我以下面的实体关系图结束:
在上图中,我将每个记录存储在Report
表中.我也存储报告类型ReportType
.对于将在报告记录中使用的每个字段,我将使用a ReportFieldValue
.字段类型将存储在ReportField
.
所以,如果我想首先向我的数据库添加一条记录,我会向Report
Table 添加一行.然后,对于每个添加的记录字段,我将向ReportFieldValue
表中添加一行.
但是您可能会注意到,在这些方法中,我必须将每个字段值存储在char(255)中.问题是类似的字段类型datetime
不应该存储为字符串.这类系统有任何设计模式或架构吗?
Jon*_*ler 13
通过更换避免stringly类型的数据VALUE
有NUMBER_VALUE
,DATE_VALUE
,STRING_VALUE
.这三种类型大多数时候都足够好.如果需要,您可以稍后添加XMLTYPE和其他精美列.对于Oracle,使用VARCHAR2而不是CHAR来节省空间.
始终尝试将值存储为正确的类型.原生数据类型更快,更小,更易于使用,更安全.
Oracle有一个通用数据类型系统(ANYTYPE,ANYDATA和ANYDATASET),但这些类型很难使用,在大多数情况下应该避免使用.
建筑师通常认为对所有数据使用单个字段会使事情变得更容易.它可以更容易地生成数据模型的漂亮图片,但它使其他一切更加困难.考虑以下问题:
针对字符串类型的数据开发类型安全的查询是很痛苦的.例如,假设你想为这个千禧年出生的人找到"出生日期":
select *
from ReportFieldValue
join ReportField
on ReportFieldValue.ReportFieldid = ReportField.id
where ReportField.name = 'Date of Birth'
and to_date(value, 'YYYY-MM-DD') > date '2000-01-01'
Run Code Online (Sandbox Code Playgroud)
你能发现这个bug吗?即使您以正确的格式存储日期,上述查询也很危险,很少有开发人员知道如何正确修复它.Oracle进行了优化,难以强制执行特定的操作顺序.您需要这样的查询才能安全:
select *
from
(
select ReportFieldValue.*, ReportField.*
--ROWNUM ensures type safe by preventing view merging and predicate pushing.
,rownum
from ReportFieldValue
join ReportField
on ReportFieldValue.ReportFieldid = ReportField.id
where ReportField.name = 'Date of Birth'
)
where to_date(value, 'YYYY-MM-DD') > date '2000-01-01';
Run Code Online (Sandbox Code Playgroud)
您不希望告诉每个开发人员以这种方式编写查询.
您的设计是实体属性值(EAV)数据模型的变体,它通常被视为数据库设计中的反模式.
也许更好的方法是创建一个报告值表,例如300列(NUMBER_VALUE_1到NUMBER_VALUE_100,VARCHAR2_VALUE_1..100和DATE_VALUE_1..100).
然后,设计其余的数据模型,围绕哪些报告使用哪些列以及每列使用哪些列.
这有两个好处:首先,您不是在字符串中存储日期和数字(已经指出了其好处),其次,您避免了与EAV模型相关的许多性能和数据完整性问题.
使用Oracle 11g2数据库,我将一个表中的30,000条记录移动到EAV数据模型中.然后我查询了模型以获得那些30,000条记录.
SELECT SUM (header_id * LENGTH (ordered_item) * (SYSDATE - schedule_ship_date))
FROM (SELECT rf.report_type_id,
rv.report_header_id,
rv.report_record_id,
MAX (DECODE (rf.report_field_name, 'HEADER_ID', rv.number_value, NULL)) header_id,
MAX (DECODE (rf.report_field_name, 'LINE_ID', rv.number_value, NULL)) line_id,
MAX (DECODE (rf.report_field_name, 'ORDERED_ITEM', rv.char_value, NULL)) ordered_item,
MAX (DECODE (rf.report_field_name, 'SCHEDULE_SHIP_DATE', rv.date_value, NULL)) schedule_ship_date
FROM eav_report_record_values rv INNER JOIN eav_report_fields rf ON rf.report_field_id = rv.report_field_id
WHERE rv.report_header_id = 20
GROUP BY rf.report_type_id, rv.report_header_id, rv.report_record_id)
Run Code Online (Sandbox Code Playgroud)
结果是:
1 row selected.
Elapsed: 00:00:22.62
Execution Plan
----------------------------------------------------------
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2026 | 53 (67)|
| 1 | SORT AGGREGATE | | 1 | 2026 | |
| 2 | VIEW | | 130K| 251M| 53 (67)|
| 3 | HASH GROUP BY | | 130K| 261M| 53 (67)|
| 4 | NESTED LOOPS | | | | |
| 5 | NESTED LOOPS | | 130K| 261M| 36 (50)|
| 6 | TABLE ACCESS FULL | EAV_REPORT_FIELDS | 350 | 15050 | 18 (0)|
|* 7 | INDEX RANGE SCAN | EAV_REPORT_RECORD_VALUES_N1 | 130K| | 0 (0)|
|* 8 | TABLE ACCESS BY INDEX ROWID| EAV_REPORT_RECORD_VALUES | 372 | 749K| 0 (0)|
----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
7 - access("RV"."REPORT_HEADER_ID"=20)
8 - filter("RF"."REPORT_FIELD_ID"="RV"."REPORT_FIELD_ID")
Note
-----
- 'PLAN_TABLE' is old version
Statistics
----------------------------------------------------------
4 recursive calls
0 db block gets
275480 consistent gets
465 physical reads
0 redo size
307 bytes sent via SQL*Net to client
252 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
Run Code Online (Sandbox Code Playgroud)
这是22秒,每个3列获得30,000行.也就是说方式太长.从一张平台上我们可以看到2秒以内,很容易.
使用 MariaDB 及其动态列。实际上,这可以让您将所有杂项列放入一个列中,但仍然可以有效地访问它们。
我会将一些公共字段保留在自己的列中。
更多关于 EAV 的讨论和建议(以及如何在没有动态列的情况下做到这一点)。
归档时间: |
|
查看次数: |
2920 次 |
最近记录: |