查询之order by,group by和having的使用 (转)

上一篇 / 下一篇  2009-09-25 19:26:47 / 个人分类:数据库

(1)order by51Testing软件测试网"e fn&bF6n,p#Y#GI

st|A)e4W0 ORDER BY子句的语法为:
6b+vN N&tX/W3_*F0Y9]0
!R$f_ _N!t)@?0SELECT column1, SUM(column2) FROM "list-of-tables"
N'{L.~#t.Z6j E051Testing软件测试网7u7E |C)|a!z%m
ORDER BY "column-list" [ASC | DESC];
6XT(pl#][0
Vw3l?[;a0[ ] = optional
[(m\!XB#~3f.JEbO v0
NbX&uV xXxX0ORDER BY是一个可选的子句,它允许你根据指定要order by的列来以上升或者下降的顺序来显示查询的结果。例如:51Testing软件测试网.q ` e#Plv$k ?6A)G
51Testing软件测试网-o7B8h GP
ASC = Ascending Order – 这个是缺省的
8Ze ["P#qyxk/l051Testing软件测试网.{o9zmrO L7?O?D
DESC = Descending Order51Testing软件测试网;ls C7ljVy&G
51Testing软件测试网 U L$fR#h'B
下面举个例子:51Testing软件测试网9Tx/X}-F7?\dB
51Testing软件测试网8GT#Vw r ||j
SELECT employee_id, dept, name, age, salary51Testing软件测试网D8EQ'VAn]5p:]

$MU1m1q|2y0FROM employee_info51Testing软件测试网N9mm7uuG

oS.^,d s)w!p_0WHERE dept = 'Sales'51Testing软件测试网[?'[ ~\UI

j/ID d-d\;p'Y0ORDER BY salary;51Testing软件测试网P^ Do2c*Gq;_ tZ2}

+a Xb-|$H+_0这条SQL语句将从employee_info表中列dept等于'Sales'选择employee_id,、dept、 name、 age和 salary,并且根据他们的salary按升序的顺序来列出检索结果。51Testing软件测试网h"g2Dc,Pk Ao
51Testing软件测试网]C0ekw/PU
如果你想对多列排序的话,那么在列与列之间要加上逗号,比如 :51Testing软件测试网?0[!~g [?|0K
51Testing软件测试网0h![5_No
SELECT employee_id, dept, name, age, salary51Testing软件测试网/?$F2^C o!^7x

GO"ya9U.gy)S!Q(d0FROM employee_info
h~7HTLI;Q;e.b4ve051Testing软件测试网.~fLi)Y"l
WHERE dept = 'Sales'
G;L5G7i@0
S3NY J7OV0ORDER BY salary, age DESC;
yL^ x2A,H!\%LH,I051Testing软件测试网 thL6Aj[$B:lS2H
(2)group by51Testing软件测试网*D,|nt8DbAD
51Testing软件测试网dz;Q\'su)J e:`O
一、首先讲讲GROUP BY 子句语法:51Testing软件测试网0w8DT(V:lJ`$U:Zg
51Testing软件测试网8`J+D.zeV:\;\!r
SELECT column1, SUM(column2) FROM "list-of-tables"51Testing软件测试网` ]9M%I/o1\Ul

D#GSoE@#m9~:G0GROUP BY "column-list";51Testing软件测试网Lh)^Z aJ
51Testing软件测试网N)Ab'L Hj"j
这个GROUP BY子句将集中所有的行在一起,它包含了指定列的数据以及允许合计函数来计算一个或者多个列。当然最好解释的方法是给出一个例子啦:51Testing软件测试网+L'r4J,tg9@#~:oc
51Testing软件测试网6ca K-T*y$R.P^j
假设我们将从employee表中搜索工资最高的列,可以使用以下的SQL语句:51Testing软件测试网|F8M3bA^ @

*Mm5gm7X fUJ0SELECT max(salary), dept51Testing软件测试网 A'j|dW\:wLb4Ke

/NB|+iv3n5e0FROM employee51Testing软件测试网*V s?3uL+q [

g$UgB3?0GROUP BY dept;51Testing软件测试网8Z"KHo-x yG7L W

0G.jLy)f4E8Ci:x0这条语句将在每一个单独的部门中选择工资最高的工资。结果他们的salary和dept将被返回。
eLb.IK7D|w$Q ?&U0
Q `N9c3Y\0二、group by 从英文里理解就是分组。必须有“聚合函数”来配合才能使用,使用时至少需要一个分组标志字段。51Testing软件测试网]L,p2f!fd
 
\Ak1dI4s0什么是“聚合函数”?
hdLd:q-U.Q Rt0  像sum()、count()、avg()等都是“聚合函数”
c gW.[0NRn0  使用group by 的目的就是要将数据分类汇总。51Testing软件测试网;\N])Tcl/W
51Testing软件测试网g,l;Fv3N*c K w+l.T
  一般如:51Testing软件测试网~A lA7y
    select 单位名称,count(职工id),sum(职工工资) form. [某表]
#P/@g1p8e3A4A ` ML/O0    group by 单位名称
+{1^aBr0b0    这样的运行结果就是以“单位名称”为分类标志统计各单位的职工人数和工资总额。
&Ym2b#YeY\051Testing软件测试网kV;Vi-^UO:\
  在sql命令格式使用的先后顺序上,group by 先于 order by。51Testing软件测试网[$of hJg
 
Fi-W8J*r4|6F \0  select 命令的标准格式如下:51Testing软件测试网^Dh ml?%iA3w
 51Testing软件测试网 c0sz HNs\
   SELECT select_list
cY)]"YD}np0   [ INTO new_table ]51Testing软件测试网l5dr@ D g"v
   FROM table_source51Testing软件测试网1`{7Cx'r9W`/Y
   [ WHERE search_condition ]
~7e0\1}6dJF#g0   [ GROUP BY group_by_expression ]51Testing软件测试网5j}:SIf2X)H5e
   [ HAVING search_condition ]51Testing软件测试网/ZQJ7d$@+F%p$e
   [ ORDER BY order_expression [ ASC | DESC ] ]
7w3h k w6Ez@0
9Py2e~w0三、理解group by:51Testing软件测试网ae4c0F3`Jyc+\U

+?+pbDyy8RWw/R0表1:51Testing软件测试网XcpBY l+f

!Y^nA/uca ?0 type51Testing软件测试网H4S Hr9J/|4O$F^
   51Testing软件测试网)G2L3nfmP @I
 udate51Testing软件测试网J;l:?8d hU5B9i

SQ8j|/T;~2N0 mp351Testing软件测试网/^eT#JBqg
   
'Uuf`DP`Qt4}5R0 2006-3-251Testing软件测试网'_.Mf _F}+a%R^?J#s
51Testing软件测试网"N3F;W#[1aMG:M
 flash  
*x z^ c/M i1J(r5n;i.A$L051Testing软件测试网mrV*A'v(\R#~b
 2006-1-2
}cc3T v`h4X*Yy0
)rt K+\_S'},D0 mp3
e{#P'Ug8B3K+N.K$\0   
rp'PC9S_'B0 2006-2-251Testing软件测试网 O q3\V$v'jIcJR
 
[ M*Vp9Q.x0在表1中,假设要取出最后更新的某一类型(type)产品的日期51Testing软件测试网%}%U!Z;\1e|`;NS"O
那么就要使用group by type的方式51Testing软件测试网6_V2o.CS D f?Yz
select type from 表1 group by type
,^z0Z*?4Y~n0T P0但这样就无法读到udate
ZhC#q8KL\Kz0 51Testing软件测试网;@ ~O ^?
再来
g Fwp7u A d h2c0select type,udate from 表1 group by type
:C$joOa`0这种写法是错误的。
\$CL1D0r$i3O${)tJ0原因是type和udate 不是一对一的关系。就是说一种type有几个udate
G;}b|4qd0所以
aYy1EX*}l;E0selct ** group by ** 之后没办法组织形成新的表,不知道要取哪个udate51Testing软件测试网#{Z4C2|!V rn
 
UZ-U3RC&[0再来
{*C S6[_&K;d9D;D0select type,max(udate) from 表1 group by type51Testing软件测试网i/C1F}$}"U(ymY
这样就正确了。
5MIoMEPF1?0取的是最大的udate ,一对一成立。51Testing软件测试网P"K,bE%c G"` @

!_8{3S p9o(a:Z4PO:E0四、GROUP BY... 被附加于SQL 是因为聚会函数 (如 SUM) 每次都返回所有列值的合计,没有GROUP BY函数是不可能找到列值的单个分组的合计数。
6w_ Z~(p#l}^L&ox051Testing软件测试网Q&?5G^0K/m
语法51Testing软件测试网B^6GU?#W
51Testing软件测试网Z!w9@*Oy
SELECT column,SUM(column) FROM table GROUP BY column
.z^9rF p/[0
PdI ReUYE0-----------------------------------------------51Testing软件测试网Ge;C*n5]
GROUP BY 示例51Testing软件测试网Yz-a"c(fJ I
这个 "Sales" 表:
G-d-] I*P\P0fP~,je0
cV)ES_v/N0Company     Amount51Testing软件测试网.o gx$] R!wk
W3Schools     5500
:vsnpO"HB0IBM    4500
(VEa:dI%o2p1YL^0W3Schools     710051Testing软件测试网/_uBR&z ~!u
51Testing软件测试网'^)c:W"p"X,ar
和这个SQL语句:
A:l&ZW:fgx0YD051Testing软件测试网3F_~*`b3H
SELECT Company, SUM(Amount) FROM Sales51Testing软件测试网v z@yf
51Testing软件测试网6R v N6C.W:tk
返回这个结果:
!mu8L~+K%_"`0
1A e2pj V S,R Y"Q0Company     SUM(Amount)
9u*^ a2_m@ ]3V"Z'd { ];rP0W3Schools     1710051Testing软件测试网 dK3vxg x&Kd!c
IBM     1710051Testing软件测试网0Q a2H-`)QU&Kh k^
W3Schools     1710051Testing软件测试网'[N#F`^;J G$b4CY
51Testing软件测试网Bw0I [5A
上面的代码是不正确的,因为被返回的列不是部分合计。GROUP BY 子句将解决这个问题。51Testing软件测试网.S!~6D5} ?M/j

uG&`k^ \ G9J$k9Z0SELECT Company,SUM(Amount) FROM Sales            GROUP BY Company
C+l bGRb051Testing软件测试网0Z(mQ;{5yA;]
返回结果:
q]3\)]e`_G051Testing软件测试网(tSe&T6BF
Company     SUM(Amount)51Testing软件测试网|.Ql S'U!wt
W3Schools     1260051Testing软件测试网e`u$o3w
IBM     4500
/{S Y9L;?op*I)[0
,p.~)P[)gi @7v0五、
4s+dFhL`0SQL Group by学习及相关应注意的地方51Testing软件测试网x}M,D(|j
 
O0Q&} gi-kU0    在select 语句中可以使用group by 子句将行划分成较小的组,然后,使用聚组函数返回每一个组的汇总信息,另外,可以使用having子句限制返回的结果集。group by 子句可以将查询结果分组,并返回行的汇总信息Oracle 按照group by 子句中指定的表达式的值分组查询结果。51Testing软件测试网hZ1H(|1U

lh$@aOl0   在带有group by 子句的查询语句中,在select 列表中指定的列要么是group by 子句中指定的列,要么包含聚组函数
&K"p$w%g H mb051Testing软件测试网,I,C4DY:A:v)SD9H
   select max(sal),job emp group by job;51Testing软件测试网eg2R#U'F
   (注意max(sal),job的job并非一定要出现,但有意义)
J&enw{}3C$@7N051Testing软件测试网(@/w2_PG/Z^-WV
   查询语句的select 和group by ,having 子句是聚组函数唯一出现的地方,在where 子句中不能使用聚组函数。
's;zq2_L_R3O$x051Testing软件测试网-G)y)c@r/T1FY
  select deptno,sum(sal) from emp where sal>1200 group by deptno having sum(sal)>8500 order by deptno;
z_xWo.`%@(p }+P;s051Testing软件测试网j+JT?k
  当在gropu by 子句中使用having 子句时,查询结果中只返回满足having条件的组。在一个sql语句中可以有where子句和having子句。having 与where 子句类似,均用于设置限定条件51Testing软件测试网+b|l? }%KSs]
 51Testing软件测试网z(i/uk?/~}$aC,`
  where 子句的作用是在对查询结果进行分组前,将不符合where条件的行去掉,即在分组之前过滤数据,条件中不能包含聚组函数,使用where条件显示特定的行。
Dhp0scZk0  having 子句的作用是筛选满足条件的组,即在分组之后过滤数据,条件中经常包含聚组函数,使用having 条件显示特定的组,也可以使用多个分组标准进行分组。51Testing软件测试网L|;p(q-I tO

-^P!@'fS;R5{cI:Q0  查询每个部门的每种职位的雇员数51Testing软件测试网 z2i!pW/B4O.Z&?A
  select deptno,job,count(*) from emp group by deptno,job;51Testing软件测试网)l Y v9l)J D_

,Of6l&ia8az0注意:51Testing软件测试网h/\7e}8GuZ;_W1X2f

dz `7Jc2q e1C P0        group by 不能对别名进行分组排序.举例如下:
9p0H:L { e;EfSd0
E!Xg3]#|"_0错误SQL:
$I5\ ^j\r3j$j3r:L0
]h%n9l\.T&dJU0SELECT to_char(BASICROLL.ROLLDATE,'yyyy-mm') AS YEARDATE,51Testing软件测试网z/Q.C3_JsqG*glg

(AhQ.Y0|3\ s]0f0   DEPTDICT.CNNAME DEPTNAME,
-J.AH Ch5rj0    COUNT(BASICROLL.ID) AS PROJCOUNT
u`CgzC5o051Testing软件测试网p pD| Fs N
FROM BASICROLL LEFT JOIN DEPTDICT ON BASICROLL.ARRDEPTDICTID=DEPTDICT.ID
,u0Gz-^Q6cK0
$T'Pp^dk?[\0WHERE DEPTDICT.CNNAME <> '无'51Testing软件测试网(hW*]"z8iw0^ u"z
51Testing软件测试网 c8n;{#i&@)f ~7Z
GROUP BY YEARDATE, DEPTDICT.CNNAME                      151Testing软件测试网'NmD\"p$r`']
ORDER BY YEARDATE DESC                                                  251Testing软件测试网+QEaK bVq'D
51Testing软件测试网Ma&T.j;H
 作者原意是将记录按年月,部门进行分组.并倒序排序.但上面的GROUP BY因为用了别名,所以会提示SQL语法错误.如果将上面1,2二句改成:   
SK$~.K_$dj3p'C3B051Testing软件测试网!\l#Y/L/V3}sV3f
GROUP BY BASICROLL.ROLLDATE, DEPTDICT.CNNAME                      3
\n1}}oXk r~ A051Testing软件测试网%T(U DTj_-MQpG
ORDER BY BASICROLL.ROLLDATE  DESC                                                451Testing软件测试网1\hcdvuC3^
51Testing软件测试网(B0}3]FZ _
如上3,4二句,又会不合原意,因为BASICROLL.ROLLDATE  包括了年月日.这样分组得出的是按年月日来分组,而不是单纯的年月了.所以正确的SQL应该是这样的.
N&Ifm.c&|5d(pE051Testing软件测试网1`piz.O(~
正确的SQL
R0H:oX9a5P/q#N0
!Cl/AqC[5aE0SELECT to_char(BASICROLL.ROLLDATE,'yyyy-mm') AS YEARDATE,51Testing软件测试网l@h%@N

sH{jBL.FjU0   DEPTDICT.CNNAME DEPTNAME,
[?'f$O8E(C9jk0    COUNT(BASICROLL.ID) AS PROJCOUNT
9\ e!R2URH$v051Testing软件测试网-xV7w2k_1n8x2~
FROM BASICROLL LEFT JOIN DEPTDICT ON BASICROLL.ARRDEPTDICTID=DEPTDICT.ID
W*rp;f,h#qi051Testing软件测试网|W/CM7e,u,f
WHERE DEPTDICT.CNNAME <> '无'
J4~Au2v8g0
2C^)@fA ul0zw0GROUP BY to_char(BASICROLL.ROLLDATE,'yyyy-mm'), DEPTDICT.CNNAME                     51Testing软件测试网Y ?2QO1k
51Testing软件测试网!s ]Zr@%_1e sV
ORDER BY to_char(BASICROLL.ROLLDATE,'yyyy-mm') DESC                                                 51Testing软件测试网,yz8K/vN4FeWJ _7@
以上的注意部分为本人在项目开发过程中经验所得.
{)D*I)]]d [,E0
!O"fK*A5n&q0来自:http://gyapollo.yculblog.com/post.1116750.html
7jnJaG:H+Q!K051Testing软件测试网M9I9]-d1_-l
六、例子
.r1Sh)w;K0
,@)E}3KdZ;B01> select * from bank_info51Testing软件测试网T9[[7C"Y
2> go51Testing软件测试网!~6k-Jg'nr
 bankno acctround51Testing软件测试网:Ljk _sc%x#@D
 ------ ---------
'h{ @;{8m0 123456 1        51Testing软件测试网^0V^a-Q
 123456 2       
)n,p |*ca0
kw8Q"`;Gf CviQ0E0(2 rows affected)
$wIPr^F?01> select * from bank_info where acctround='1' group by bankno order by bankno51Testing软件测试网 rR}*w P mL\;JY
2> go51Testing软件测试网)y @ w{-`5j u7c}
 bankno acctround51Testing软件测试网@!J|jUlj V9N p(xs
 ------ ---------51Testing软件测试网[/lc9}/l^v2aXf$Q
 123456 1        51Testing软件测试网x v1~s6Z8V s
 123456 2        
'G|s y*K$U+_e2Fv0
Rt/r9DhPMz0(2 rows affected)51Testing软件测试网[X;w/Eg_R6C
1> select * from bank_info where acctround='1' order by bankno51Testing软件测试网[v0I#s'? N2R
2> go51Testing软件测试网'O I&w$[[H
 bankno acctround
plo\ MW F!Qg0 ------ ---------51Testing软件测试网.P@I? fJ
 123456 1        
Ocx/pd6l-a m0
7xNdm$n&f6d s0(1 row affected)51Testing软件测试网~%GOFU6? Zr
1> select * from bank_info
&m3]5mJw-w02> go
2zjB'MA)?av q0 bankno acctround51Testing软件测试网wt-D{Qc1D
 ------ ---------
T)L/[P3mCe|;q(b0 123456 1        51Testing软件测试网l }l4Ye u
 123456 2        51Testing软件测试网?Z-le6W,a
51Testing软件测试网 [}l ~5}*pCg
(2 rows affected)51Testing软件测试网Z\ Y!Luc
1> select * from bank_info where acctround='1' group by bankno,acctround order by bankno
,yFR-}/ky02> go
\w+~|%v9eKY0 bankno acctround51Testing软件测试网.E$m(g+R @!x_*v,uO-B%k
 ------ ---------
7y&?&_ [5}~"y#xbP{0 123456 1        51Testing软件测试网6p&b3H$X7V9G|8S

#lSd N;OG-q*e"A0(1 row affected)51Testing软件测试网kN8|eZ7U

'? y/WCnE0group by bankno,acctround的意思是找出bankno,acctround两列完全相同的不同行作为一组.那上面的数据就分成两组了,因为 acctround不同,而结果需要acctround='1'的组.所以只有一行结果.如果是group by bankno,那么两行会合成一行.它并没有先通过where分析出只有一行符合结果集,再group就只有一行结果了.这里的问题是where和 group谁先谁后分析的问题?在这个问题的上下文中,本来的group by bankno就没有任何意义.其实是这样的:51Testing软件测试网^ R3H2V*E/O;b
51Testing软件测试网 @cEd8j cEB
或者是:
D0w9o(mao,{C051Testing软件测试网%K ns2a/]ER
select bankno,sum(money) from bankdiff group by bankno;51Testing软件测试网"h,t Mc"X

c,y0dm#} ih0又或者是:51Testing软件测试网'X.zj J?9e+} M9{

IN6D+d'C0select bankno,acctround,sum(money) from bankdiff where acctround='1' group by bankno,acctround;51Testing软件测试网 P\Qa`/{ O3P
 
btb5c`)w(u&_a)G0? wp0不可能指定acctround又不把它放在groub by中的.
,}/m:D+L7Y:Z051Testing软件测试网#m%vk jA3K~
1> select * from bank_info group by bankno51Testing软件测试网n Wy2]X#aIe
2> go
\*V;JN'I\v P0 bankno acctround51Testing软件测试网\C6H0h[)pn
 ------ ---------
%oiY.Z$r h.|0 123456 1        51Testing软件测试网 U2}~,I.u~;F0^ }5q
 123456 251Testing软件测试网L-s-pa V8dc

PQ(|,v)Y&jeV[!Z0可见这个group by没有意义.因为它有select *,而acctround不一样,就不能只显示一行出来.把select * 改成 select bankno就只有一行结果了.
:h"hf5Th)doX G0
O9l%N1e,~.kWhS%R0总 结:使用group by必需明确你分组的意义,是否有必要这一个列或几个列来确定一个有意义的组,而需要显示的结果行的列是否和group by子句中的列名相冲突.一般不在group by子句中的列名都不会直接在select中显示出来.因为它们要分成一组来或者做些运算再显示.如sum(money)等.51Testing软件测试网5k3r$e6\.o2A

`,_0v1T8A,{c0eb0(3)having51Testing软件测试网(kobL Z-d/Ma|j+{
51Testing软件测试网*O@)tD t2R u$V
一、下面先给出HAVING子句的语法:
_[k/I XKnJP0
R7F#x{$D m0SELECT column1, SUM(column2) FROM "list-of-tables"
:v+SZ O2?e0
3N;q wuu0GROUP BY "column-list"
,`#\uvN[$b WR!?o x051Testing软件测试网:_7[W:eO2Q-}b
HAVING "condition";
pIn6h@ |'b0
8^2Z}2_J@0这个HAVING子句允许你为每一个组指定条件,换句话说,可以根据你指定的条件来选择行。如果你想使用HAVING子句的话,它应该处再GROUP BY子句之后。51Testing软件测试网m_Y0pw g e

I o-K'yF4Dy T0下面将以一个例子来解释HAVING子句。假设我们的employee表中包含雇员的name、departmen、salary和age。如果你想为每个部门中每个雇员选择平均工资的话,你可以使用下面的SQL语句:51Testing软件测试网i2IQrl
51Testing软件测试网:d gShm*|4p
SELECT dept, avg(salary)
/{.``m O0F:@d7q0
`8Y9X$bax"kY0FROM employee51Testing软件测试网 ?%x Z;R&k
51Testing软件测试网,B UnH9N.q:\
GROUP BY dept;
Nd p)}"@0
|N"s/u1Y}3Y0当然,如果你还想只计算和显示salary大于20000的平均工资的话,你还可以加上HAVING子句:51Testing软件测试网H ]y~b2k U
51Testing软件测试网-Z/G/~7]*n%l(hkv
SELECT dept, avg(salary)51Testing软件测试网 U-~o7k ^`/@-e
51Testing软件测试网 F1UZ*V5e/_ T
FROM employee51Testing软件测试网 G(rn)Z1h0I%_qC

0IL7C(R*u0GROUP BY dept51Testing软件测试网?@nNp R&Vck6G~&`$GE

u|},J^ qUx#GQ0HAVING avg(salary) > 20000;51Testing软件测试网|y&u} l%~pi/Av6b2p}
51Testing软件测试网3AxH&z2y9l
二、HAVING... 被附加到SQL语句是因为WHER关键字不能被用于聚会函数 (如 SUM),并且没有HAVING...它将不可能测试结果条件。
)@X(n \%}T,t0
MRZira4k0语法:51Testing软件测试网E5xyA!F2r

SXp%rib0SELECT column,SUM(column) FROM table            GROUP BY column            HAVING SUM(column) condition value51Testing软件测试网e2bR/?m`8s{.D%u
51Testing软件测试网2d vbFQ OSt Q
这个 "Sales" 表:
/r [Q^W(d] h0
,q {0\;}*MbUs_0Company     Amount
*Y_d9Yh)gR?%]0W3Schools     5500
+v Av1Ii2xRJ fl0IBM     450051Testing软件测试网!Q8Q6B`eq` i9`
W3Schools     7100
qi-GRa3O051Testing软件测试网Z:d)gjg
和这个SQL语句:51Testing软件测试网1uP9{!CqG0SY

)vjp1?!p0SELECT Company,SUM(Amount) FROM Sales            GROUP BY Company            HAVING SUM(Amount)>1000051Testing软件测试网 L(f8|"Eza
51Testing软件测试网 hw}\-Wg*N
返回这个结果
y#\.e1K+?(KF051Testing软件测试网:W)A,o8_ @PU~Fd
Company     SUM(Amount)
L_ jq? w0W3Schools     12600

TAG: 数据库 having

 

评分:0

我来说两句

Open Toolbar