回答

收藏

将多维数组存储在数据库中:关系还是多维?

技术问答 技术问答 223 人阅读 | 0 人回复 | 2023-09-14

我已经阅读了许多有关 多维到单维 , 多维数据库
  D  x) Y$ |& G. b4 a等的文章,但是所有答案都无济于事。我确实在Google上找到了很多文档,但是这些文档仅提供背景信息,而没有回答眼前的问题。
7 P- K& F, O6 q0 `9 O我有很多彼此相关的字符串。PHP脚本中需要它们。该结构是分层的。这是一个例子。: u1 F& f1 `3 `/ }& R5 V  J
A:7 f5 \, y+ [/ U% Y, A8 ?% e
  AA:1 S& x$ K$ T/ }" `; V1 q
    AAA! ~8 M8 y$ Z, B+ E; K1 Y+ j
    AAC
$ W$ N% K! R3 W9 q+ {; C; J, l  AB4 Q; e1 V, |! r/ S$ q+ m2 u
  AE:# Y9 V% L2 Y+ k- L; |0 P
    AEA
8 z. c; U7 J% H2 u    AEE:) C% l  k3 c6 o3 a2 U: _
      AEEB. z" a) q  @: s- L" ~7 m+ t
B:& z- b! v; j2 h1 a$ }
  BA:5 G9 J5 w. u* @: C% X
    BAA4 E  u* i2 x2 m; b! L& t6 a- P
  BD:+ v* W) F3 v& Z
    BDC:
: M% U) w" r$ `7 H% v7 H6 f! w      BDCB
! U/ Y* }" U2 a3 p1 @! G      BDCE# a6 D4 |) l" D& P# x
    BDD:& b, g) I  _- C7 |  `
      BDDA
% `5 J* J! }5 o8 x  BE:
% o# q$ ^, ^8 u, c* F8 V    BED:
5 v" q4 P9 e4 G$ E( `) w) |( y      BEDA
1 {) v/ [* U9 S& G9 H* y9 \" I2 HC:+ D2 D, x) j) x7 Q5 X
  CC:4 u+ `! ]. F6 b
    CCB:
! k2 S' l; I+ n. E# _, ~      CCBC
3 A( K0 k' ]+ F: U      CCBE  b; j2 b* V1 a. h
    CCC:
) J/ T/ T) Z5 j) A) `# v1 `      CCCA
# M! M; s- X2 i2 [* m% u- s( o      CCCE
, p* g) E$ ]- ^: e, e  CE
7 F0 ?. C: t# b5 i! H* r7 e每个缩进在多维数组中都假定一个新级别。% g  M& T8 K  Y/ a1 N+ a( i+ t8 r
目的是通过名称及其所有后代检索具有PHP的元素。例如,如果我查询A,我想接收一个包含的字符串数组array('A', 'AA', 'AAA',
% \' a+ `; H  B$ g0 z) K1 r'AAC', 'AB', 'AE', 'AEA', 'AEE',: J% T0 w  O1 E
'AEEB')。“问题”是还可以对较低级别的元素进行查询。如果我查询AEE,我想得到array('AEE', 'AEEB')。/ q' z& w- x- ]
当我理解关系数据库的概念时,这意味着我不能使用关系数据库,因为元素之间没有通用的“键”。我认为可能的解决方案是为每个单元分配PARENT元素。因此,在一个表中:
  {! [2 B& G! UCELL | PARENT! s% e4 C9 \& n* c! F* |7 y+ q
A      NULL
7 a1 |# a- K) N! m, p( M$ l3 ^+ b7 I) W0 JAA     A
0 ]5 y8 F0 f( y3 q3 bAAA    AA
( G  g( T" k* X! y. ?+ {3 X- S6 t2 CAAC    AA, |6 U1 @& }, @- N' P! }
AB     A0 I' ]8 d9 e. w  S, M) d
AE     A
6 H1 {8 Z8 b6 J+ w) CAEA    AE
' e( X6 w) Y9 K, B/ RAEE    AE
* u: U4 ~. N0 a4 V0 L& }& hAEEB   AEE  ]  a% _' m7 j; i* _! {8 e  d
这样,我认为您应该能够查询给定的字符串以及共享该父项的所有项目,然后递归地沿着该路径进行操作,直到找不到更多项目为止。 但是4 ~- Q, {+ Q3 n2 ?& H
,这对我来说似乎很慢,因为整个搜索空间都需要在每个级别上进行浏览-这正是您在多维数组中所不想要的。% C1 k, E1 R( w/ t1 y
所以我有点茫然。请注意,实际上大约有100,000个以这种方式构造的字符串,因此速度很重要。幸运的是,数据库是静态的,不会更改。如何在无需处理冗长的循环和搜索时间的情况下将这样的数据结构存储在数据库中?6 _4 w, [# p4 w% Y
哪种数据库软件和数据类型最适合呢? 我已经注意到,我们的服务器上已经存在PostgreSQL,所以我宁愿坚持这一点。
/ p1 x9 u' d# F正如我所说,我是数据库新手,但我非常渴望学习。因此,我正在寻找一个详尽的答案,该答案将详细介绍并提供某种方法的优缺点。性能是关键。预期的答案将包含针对此用例的最佳数据库类型和语言,并以该语言编写脚本以构建这种结构。
/ p0 x- h- Z: f( g                9 [, J; ?5 |7 @5 z4 Q$ U7 s
解决方案:7 I4 o' t1 {( \" [( ~; }
               
7 v4 @  H" Z  b0 Y( Y  c' S
( p4 Z* f* W/ ~$ Z$ q& ]% r8 ^1 b
( U2 d3 B" v2 [2 P  e' `                6 M; s. d6 Z9 S, S7 ?, ^5 n
目的是通过名称及其所有后代检索具有PHP的元素。6 o! p7 t( y! F: t9 d5 l2 E
, C) G+ _) U, N& U& C
如果这是您所需要的,可以使用LIKE搜索
2 p* z4 C+ R8 C1 j3 O& BSELECT ** A2 k6 ^3 d9 g
FROM Table14 {& G) S1 I  c) L* D' L
WHERE CELL LIKE 'AEE%';0 Y& `  M5 q4 ]9 [' O
以索引开头的CELL是范围检查,这很快。
4 L) ]; n/ u' ~8 A% l. r如果您的数据看起来不是这样,则可以创建一个path看起来像目录路径的列,其中包含从根到元素“在路径/路径上”的所有节点。& s! U' W( [- T6 H/ q. F* P
| id | CELL | parent_id | path     |
- Z. G/ h' @3 ~, A% ^|====|======|===========|==========|
1 L% \* m5 M! ~# R, d( n|  1 | A    |      NULL | 1/       |
( x  y. k* M- \5 o|  2 | AA   |         1 | 1/2/     |$ `7 k2 l/ U; ~1 |# v
|  3 | AAA  |         2 | 1/2/3/   |
* N' k( x0 d/ D5 `% ~( J7 T|  4 | AAC  |         2 | 1/2/4/   |
! q# |" C. J3 U|  5 | AB   |         1 | 1/5/     |
! C: D4 g  B- H5 ?|  6 | AE   |         1 | 1/6/     |
4 m8 U& F7 }8 y- w+ L+ j8 c4 @|  7 | AEA  |         6 | 1/6/7/   |& b/ D( r. ~: q( U- v0 x
|  8 | AEE  |         6 | 1/6/8/   |4 @4 U! ]; Q/ @2 |8 d5 m
|  9 | AEEB |         8 | 1/6/8/9/ |
9 c6 W6 q4 C: }  [" T" Z要检索“ AE”的所有后代(包括自身),您的查询将是
; i* u' I. k6 O) b1 W8 OSELECT *
5 R, S4 P5 H1 i9 B1 z+ R$ sFROM tree t
: Z# Z4 _! d  z, ?8 D8 RWHERE path LIKE '1/6/%';) k# D6 M, q" X/ f0 }- S6 C
或(特定于MySQL的串联)
, c) U3 N, e, N( ?8 XSELECT t.*
0 Y: [8 L4 H1 V  qFROM tree t
6 s( C6 J" N% q( f8 B6 pCROSS JOIN tree r -- root( k- k9 Z6 ^7 {6 I9 t6 y( _% ?8 |: F
WHERE r.CELL = 'AE'
5 i; q0 L* A% \, Q  AND t.path LIKE CONCAT(r.path, '%');
# c- I. D$ Z& q1 i, w4 q& i结果:
" I" p$ s( G9 @& @- d0 X| id | CELL | parent_id |     path |1 M9 a4 a* W% w+ t. H3 F
|====|======|===========|==========|
" N3 ]/ d! a& ^* T2 v8 k  J|  6 | AE   |         1 | 1/6/     |% a- f, H* P* x. q1 q) u
|  7 | AEA  |         6 | 1/6/7/   |* k, \* `+ Q7 X# E: j- p: q
|  8 | AEE  |         6 | 1/6/8/   |
5 L0 ]9 @, @% u6 U) L; m! }|  9 | AEEB |         8 | 1/6/8/9/ |
4 o! [; E$ a$ `/ W9 }" ]! ^! G演示版1 b, v! S9 j5 j+ u" D' V, ?; }
表现6 c" _, z/ F$ B3 a, C& z5 m- e
我创建了100K行假数据上MariaDB的与插件顺序使用以下脚本:
/ r5 d2 N9 V$ Adrop table if exists tree;
( @$ c- T# W7 y4 ~( o& g4 h/ R6 [- iCREATE TABLE tree (/ N# c8 ~* h3 J$ E6 v: R# h
  `id` int primary key,) k" T; S) L2 B/ |" s% X
  `CELL` varchar(50),
! |  }' \$ ?- e- c2 x  T  `parent_id` int,
# v/ h! A; h* {+ A7 Z- x  `path` varchar(255),8 F6 m- J' j- O# ?  o. Z% V/ `( X
  unique index (`CELL`)," [3 S+ g6 L+ Z& b/ M% Y
  unique index (`path`)
9 p3 C+ \9 N9 d0 i# E);
7 |  Q1 q- l+ [+ kDROP TRIGGER IF EXISTS `tree_after_insert`;1 n$ i9 H- `# `8 a3 P) z( Y; ?
DELIMITER //* W: I- v, d7 D! k& o
CREATE TRIGGER `tree_after_insert` BEFORE INSERT ON `tree` FOR EACH ROW BEGIN
' P  Y4 P' U+ P( g- F8 O0 u* d    if new.id = 1 then
8 M- Q4 {: L9 G4 d        set new.path := '1/';
" n7 h) y) C" t  C    else    6 I( k7 q7 k$ ]
        set new.path := concat((
' C4 c/ k# M, X$ g. W            select path from tree where id = new.parent_id" c- B! u- c3 v) \; L7 T. Y
        ), new.id, '/');. J1 [) D8 D1 U) v8 e5 F; r- S* g0 h
    end if;; _1 N4 p+ K" U- a
END//, C" Z$ K: H4 @2 U( a3 _
DELIMITER ;6 T& Z+ l! s# ~3 J3 P
insert into tree0 E0 s% b3 i3 t  n& l% u: f
    select seq as id
6 c2 M) C# ]8 \  }" y( i- |        , conv(seq, 10, 36) as CELL" `  |* p) B! I/ g7 U5 f/ ]% x. |- i. _
        , case
0 ^% O8 f) d/ J+ E! H0 b3 C; L            when seq = 1 then null
8 e2 N% x; O  b2 U3 f% a7 c; s            else floor(rand(1) * (seq-1)) + 1
1 l7 x0 }$ }' G8 y/ t. n        end as parent_id( _' L9 t- y+ k1 L+ _7 t* r# @
        , null as path; D9 n+ |( F2 [6 b
    from seq_1_to_100000
2 \0 _+ D8 G* U& X* v;
$ c& G# h- {0 v8 F4 uDROP TRIGGER IF EXISTS `tree_after_insert`;
3 m% x! W3 C% Q( t0 b-- runtime ~ 4 sec.* E+ I# H+ U9 w  b2 s3 _- Z0 x$ w, Z3 T
测验+ K, T) C0 B" A0 F
计算根目录下的所有元素:8 d0 `5 u/ h6 q5 v+ f
SELECT count(*)
* o: R$ c8 H$ J# Q, O9 QFROM tree t# R- H; }1 O* q1 Y4 U
CROSS JOIN tree r -- root
; i( Q4 W+ ^$ b7 m) V6 ?3 kWHERE r.CELL = '1'
; {/ F( V$ e& o* f* e: k& ]  AND t.path LIKE CONCAT(r.path, '%');
# F, d: A4 Y3 z) r$ V! {( s1 u2 q-- result: 100000
) k3 i6 z/ \1 W/ H( f) Z-- runtime: ~ 30 ms
8 k# e+ O  A! ?7 B* L获取特定节点下的子树元素:
% d7 {6 q4 B$ ~* MSELECT t.*3 ]+ C- ~# N/ ^+ C
FROM tree t! M4 k6 p# b% |% _# t( v
CROSS JOIN tree r -- root. [: E' p0 [# r" f+ d/ b9 K# [8 J( _
WHERE r.CELL = '3B0'* Z+ \" q" F+ I
  AND t.path LIKE CONCAT(r.path, '%');% H1 A& m( ^; p2 C5 }
-- runtime: ~ 30 ms# c2 n+ B* f- h7 J* z  p* v) R6 z
结果:
' x; V6 \$ W2 S  L1 m+ r| id    | CELL | parent_id | path                                |/ B7 u( a1 K. S5 p- c% I$ K
|=======|======|===========|=====================================|
1 c6 K" q. e7 p8 y8 u- p7 t|  4284 | 3B0  |       614 | 1/4/11/14/614/4284/                 |
+ ~, g; s/ r( I6 V- F|  6560 | 528  |      4284 | 1/4/11/14/614/4284/6560/            |+ {& N" n! k# s+ p
|  8054 | 67Q  |      6560 | 1/4/11/14/614/4284/6560/8054/       |
0 n8 `  b) X- F  A8 b| 14358 | B2U  |      6560 | 1/4/11/14/614/4284/6560/14358/      |
" ?& @* c, f+ y" g7 z' V| 51911 | 141Z |      4284 | 1/4/11/14/614/4284/51911/           |
" |7 O, w& `8 X3 b% r- ~| 55695 | 16Z3 |      4284 | 1/4/11/14/614/4284/55695/           |4 w' X' s# G- O( U, q0 w
| 80172 | 1PV0 |      8054 | 1/4/11/14/614/4284/6560/8054/80172/ |" I- P# w; c5 q
| 87101 | 1V7H |     51911 | 1/4/11/14/614/4284/51911/87101/     |) Y0 |" v( |1 H+ n5 B: x
PostgreSQL的
1 p" }/ c' B7 \+ f; p" m这也适用于PostgreSQL。只需更改字符串连接语法:2 ^2 _6 c; y8 Y& H9 i' n( ~* u
SELECT t.*# }5 w% J. G( d
FROM tree t4 j* E# I0 q& ]8 ]2 M
CROSS JOIN tree r -- root
' ^" e/ l  }1 l8 iWHERE r.CELL = 'AE'
- I% ^( N$ s$ T, `3 F  AND t.path LIKE r.path || '%';% U) c4 ~: B! |, N
演示: sqlfiddle -8 i0 y2 n# ]" J8 Q$ {  i2 S8 q2 S1 I
rextester
/ W1 n: W& q2 T- g7 ^* t搜索如何进行% k* q( M0 Y& i# B4 S; c
如果看一下测试示例,您将看到结果中的所有路径均以“ 1/4/11/14/614/42848 e: x: G8 |2 e6 @) a
/”开头。这是带有的子树根的路径CELL='3B0'。如果该path列已建立索引,则引擎将高效地找到它们,因为该索引按进行排序path。就像您要在包含10万个单词的字典中查找所有以’pol’开头的单词一样。您无需阅读整个词典。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则