12.10 sqlite学习

前端之家收集整理的这篇文章主要介绍了12.10 sqlite学习前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

转载:http://wangyaobeijing.blog.163.com/blog/static/15833032012429904518/


一。创建数据库

qlite3.exe test.db

二.双击sqlite-3_6_16目录下的程序sqlite3.exe,即可运行@H_404_10@ 三.退出@H_404_10@ .exit@H_404_10@ 或者@H_404_10@ .quit@H_404_10@ 四.sqlite支持如下5种数据类型@H_404_10@ 1.NULL:空值。@H_404_10@ 2.INTEGER:带符号的整型,具体取决有存入数字的范围大小。@H_404_10@ 3.REAL:浮点数字,存储为8-byte IEEE浮点数。@H_404_10@ 4.TEXT:字符串文本。@H_404_10@ 5.BLOB:二进制对象。

五.联系人表格结构如下@H_404_10@ create table contact(id integer primary key autoincrement,@H_404_10@ lastname varchar(20),firstname varchar(20),@H_404_10@ mobile varchar(30),telephone varchar(20),@H_404_10@ email varchar(30),company varchar(50),@H_404_10@ department varchar(16),address varchar(80),@H_404_10@ id1 interger,id2 integer,updatetime datetime);

六.查看数据库有哪些数据表@H_404_10@ 命令是:.tables@H_404_10@ 七.如何插入一条记录@H_404_10@ insert into contact(lastname,firstname,mobile,telephone,updatetime) values(‘刘’,'畅’,’13910128132′,’010-81749136′,’2009-07-22′);@H_404_10@ 八.查看数据表的结构

针对整个数据库@H_404_10@ .schema@H_404_10@ 针对仅仅是contact联系人该表@H_404_10@ .schema contact 注意没有分号

九.如何打开一个已经创建的数据库

sqlite3 test.db@H_404_10@ 十.如何解决如下问题@H_404_10@ sql error: near “sqlite3″: Syntax error

sql指令都是以分号(;)结尾的。如果遇到两个减号(–)则代表注解,sqlite3会略过去

十一.如何建立索引@H_404_10@ create index index_name on table_name(field_to_be_indexed);@H_404_10@ 十二.如何删除一张数据表@H_404_10@ drop table contact;

十三.查看当前的数据库@H_404_10@ .database

十四.如何删除一个数据表的数据@H_404_10@ delete from contact;@H_404_10@ 十五.如何导入一个文件到某个表中@H_404_10@ .import 文件路径 表名@H_404_10@ 注意这是非sql语句,所以不加分号@H_404_10@ 十六.如何设置文件字段的分隔符@H_404_10@ .separator “,”@H_404_10@ .import e:/contact.txt contact@H_404_10@ 十七.如何查看当前sqllite字段的分隔符是什么?@H_404_10@ .show

十八.如何将查询结果导出到一个文件

第一步:.output a.txt@H_404_10@ 第二步:执行要导出的sql语句@H_404_10@ 第三步:.output stdout@H_404_10@ 十九.SQL查询语句@H_404_10@ select * from film order by year limit 10;@H_404_10@ select * from film order by year desc limit 10;@H_404_10@ select count(*) from film;@H_404_10@ select * from film where starring like ‘Jodie%’;

select * from film where starring=’Jodie Foster’;@H_404_10@ select title,year from film order by year desc limit 10;@H_404_10@ select columns from table_name where expression;

最常见的用法,当然是倒出所有数据库内容

select * from film;

如果资料太多了,我们或许会想限制笔数:

select * from film limit 10;

或是照着电影年份来排列:

select * from film order by year limit 10;

或是年份比较近的电影先列出来:

select * from film order by year desc limit 10;

或是我们只想看电影名称跟年份:

select title,year from film order by year desc limit 10;

查所有茱蒂佛斯特演过的电影:

select * from film where starring=’Jodie Foster’;

查所有演员名字开头叫茱蒂的电影(‘%’ 符号便是 sql 的万用字符):

select * from film where starring like ‘Jodie%’;

查所有演员名字以茱蒂开头、年份晚于1985年、年份晚的优先列出、最多十笔,只列出电影名称和年份:

有时候我们只想知道数据库一共有多少笔资料:

select count(*) from film;

有时候我们只想知道1985年以后的电影有几部:

select count(*) from film where year >= 1985;

(进一步的各种组合,要去看sql专书,不过你大概已经知道sql为什么这么流行了:这种语言允许你将各种查询条件组合在一起──而我们还没提到「跨数据库的联合查询」呢!)

如何更改或删除资料@H_404_10@ 了解select的用法非常重要,因为要在sqlite更改或删除一笔资料,也是靠同样的语法。

例如有一笔资料的名字打错了:@H_404_10@ update film set starring=’Jodie Foster’ where starring=’Jodee Foster’;@H_404_10@ 就会把主角字段里,被打成’Jodee Foster’的那笔(或多笔)资料,改回成Jodie Foster。@H_404_10@ delete from film where year < 1970;@H_404_10@ 就会删除所有年代早于1970年(不含)的电影了。@H_404_10@ 其他sqlite的特别用法@H_404_10@ sqlite可以在shell底下直接执行命令:@H_404_10@ sqlite3 film.db “select * from film;”@H_404_10@ 输出 HTML 表格:@H_404_10@ sqlite3 -html film.db “select * from film;”@H_404_10@ 将数据库「倒出来」:@H_404_10@ sqlite3 film.db “.dump” > output.sql@H_404_10@ 利用输出的资料,建立一个一模一样的数据库加上以上指令,就是标准的sql数据库备份了):@H_404_10@ sqlite3 film.db < output.sql@H_404_10@ 在大量插入资料时,你可能会需要先打这个指令:

begin;@H_404_10@ 插入完资料后要记得打这个指令,资料才会写进数据库中:@H_404_10@ commit;

创建数据库文件:@H_404_10@ >sqlite3 d:\test.db 回车@H_404_10@ 就生成了一个test.db在d盘。@H_404_10@ 这样同时也sqlite3挂上了这个test.db@H_404_10@ 2)@H_404_10@ 用.help可以看看有什么命令@H_404_10@ >.help 回车即可@H_404_10@ 3)可以在这里直接输入sql语句创建表格 用;结束 ,然后回车就可以看到了@H_404_10@ 4)看看有创建了多少表@H_404_10@ >.tables@H_404_10@ 5)看表结构@H_404_10@ >.schema 表名@H_404_10@ 6)看看目前的数据库@H_404_10@ >.database@H_404_10@ 7)如果要把查询输出文件@H_404_10@ >.output 文件名@H_404_10@ > 查询语句;@H_404_10@ 查询结果就输出到了文件c:\query.txt@H_404_10@ 把查询结果用屏幕输出@H_404_10@ >.output stdout@H_404_10@ 8)把表结构输出,同时索引也会输出@H_404_10@ .dump 表名@H_404_10@ 9)退出@H_404_10@ >.exit 或者.quit@H_404_10@ 2。从http://sqlite.phxsoftware.com/ 下载Ado.net驱动。@H_404_10@ 下载了安装,在安装目录中存在System.Data.sqlite.dll@H_404_10@ 我们只需要拷贝这个文件到引用目录,并添加引用即可对sqlite数据库操作了@H_404_10@ 所有的Ado.net对象都是以sqlite开头的,比如sqliteConnection@H_404_10@ 连接串只需要如下方式@H_404_10@ Data Source=d:\test.db 或者DataSource=test.db–应用在和应用程序或者.net能够自动找到的目录@H_404_10@ 剩下的就很简单了~~@H_404_10@ 3。sql语法@H_404_10@ 由于以前用sqlServer或者ISeries,所以DDL的语法很汗颜@H_404_10@ 1)创建一个单个Primary Key的table@H_404_10@ CREATE TABLE [Admin] (@H_404_10@ [UserName] [nvarchar] (20) PRIMARY KEY NOT NULL,@H_404_10@ [Password] [nvarchar] (50) NOT NULL,@H_404_10@ [Rank] [smallint] NOT NULL,@H_404_10@ [MailServer] [nvarchar] (50) NOT NULL,@H_404_10@ [MailUser] [nvarchar] (50) NOT NULL,@H_404_10@ [MailPassword] [nvarchar] (50) NOT NULL,@H_404_10@ [Mail] [nvarchar] (50) NOT NULL@H_404_10@ ) ;@H_404_10@ 2)创建一个多个Primary Key的table@H_404_10@ CREATE TABLE [CodeDetail] (@H_404_10@ [CdType] [nvarchar] (10) NOT NULL,@H_404_10@ [CdCode] [nvarchar] (20) NOT NULL,@H_404_10@ [CdString1] [ntext] NOT NULL,@H_404_10@ [CdString2] [ntext] NOT NULL,@H_404_10@ [CdString3] [ntext] NOT NULL,@H_404_10@ PRIMARY KEY (CdType,CdCode)

) ;@H_404_10@ 3)创建索引@H_404_10@ CREATE INDEX [IX_Account] ON [Account]([IsCheck],[UserName]);

还可以视图等等。@H_404_10@ 4.还有很有用的sql@H_404_10@ Select * from sqlite_master@H_404_10@ Select datetime(‘now’)@H_404_10@ Select date(‘now’)@H_404_10@ Select time(‘now’)

sqlite 内建函数表@H_404_10@ 算术函数@H_404_10@ abs(X)@H_404_10@ 返回给定数字表达式的绝对值。@H_404_10@ max(X,Y[,...])@H_404_10@ 返回表达式的最大值。@H_404_10@ min(X,...])@H_404_10@ 返回表达式的最小值。@H_404_10@ random(*)@H_404_10@ 返回随机数。@H_404_10@ round(X[,Y])@H_404_10@ 返回数字表达式并四舍五入为指定的长度或精度。@H_404_10@ 字符处理函数@H_404_10@ length(X)@H_404_10@ 返回给定字符串表达式的字符个数。@H_404_10@ lower(X)@H_404_10@ 将大写字符数据转换为小写字符数据后返回字符表达式。@H_404_10@ upper(X)@H_404_10@ 返回将小写字符数据转换为大写的字符表达式。@H_404_10@ substr(X,Y,Z)@H_404_10@ 返回表达式的一部分。@H_404_10@ randstr()

quote(A)

like(A,B)@H_404_10@ 确定给定的字符串是否与指定的模式匹配。@H_404_10@ glob(A,B)

条件判断函数@H_404_10@ coalesce(X,...])

ifnull(X,Y)

nullif(X,sans-serif"> 集合函数@H_404_10@ avg(X)@H_404_10@ 返回组中值的平均值。@H_404_10@ count(X)@H_404_10@ 返回组中项目的数量。@H_404_10@ max(X)@H_404_10@ 返回组中值的最大值。@H_404_10@ min(X)@H_404_10@ 返回组中值的最小值。@H_404_10@ sum(X)@H_404_10@ 返回表达式中所有值的和。@H_404_10@ 其他函数@H_404_10@ typeof(X)@H_404_10@ 返回数据的类型。@H_404_10@ last_insert_rowid()@H_404_10@ 返回最后插入的数据的 ID 。@H_404_10@ sqlite_version(*)@H_404_10@ 返回 sqlite 的版本。@H_404_10@ change_count()@H_404_10@ 返回受上一语句影响的行数。@H_404_10@ last_statement_change_count()

oh,还有就是看到有人说,好像成批插入的时候,启动事务,比不启动事务快n倍@H_404_10@ 还有就是尽量使用参数化的sql,估计和商用DB一样能够自动Prepare.@H_404_10@ ===========@H_404_10@ sqlite可以在shell/dos command底下直接执行命令:@H_404_10@ sqlite3 film.db “select * from film;”@H_404_10@ 输出 HTML 表格:@H_404_10@ sqlite3 -html film.db “select * from film;”@H_404_10@ 将数据库「倒出来」:@H_404_10@ sqlite3 film.db “.dump” > output.sql@H_404_10@ 利用输出的资料,建立一个一模一样的数据库加上以上指令,就是标准的sql数据库备份了):@H_404_10@ sqlite3 film.db < output.sql@H_404_10@ 在大量插入资料时,你可能会需要先打这个指令:@H_404_10@ begin;@H_404_10@ 插入完资料后要记得打这个指令,资料才会写进数据库中:@H_404_10@ commit;@H_404_10@ sqlITE深入——常见问题@H_404_10@ 如何建立自动增长字段?@H_404_10@ 简短回答:声明为 INTEGER PRIMARY KEY 的列将会自动增长 。@H_404_10@ 长一点的答案: 如果你声明表的一列为 INTEGER PRIMARY KEY,那么, 每当你在该列上插入一NULL值时, NULL自动被转换为一个比该列中最大值大1的一个整数,如果表是空的, 将会是1。 (如果是最大可能的主键 9223372036854775807,那个,将键值将是随机未使用的数。) 如,有下列表:@H_404_10@ CREATE TABLE t1(@H_404_10@ a INTEGER PRIMARY KEY,@H_404_10@ b INTEGER@H_404_10@ );@H_404_10@ 在该表上,下列语句@H_404_10@ INSERT INTO t1 VALUES(NULL,123);@H_404_10@ 在逻辑上等价于:@H_404_10@ INSERT INTO t1 VALUES((SELECT max(a) from t1)+1,123);@H_404_10@ 有一个新的API叫做 sqlite3_last_insert_rowid(), 它将返回最近插入的整数值。 注意该整数会比表中该列上的插入之前的最大值大1。 该键值在当前的表中是唯一的。但有可能与已从表中删除的值重叠。要想建立在整个表的生命周期中唯一的键值,需要在 INTEGER PRIMARY KEY 上增加AUTOINCREMENT声明。那么,新的键值将会比该表中曾能存在过的最大值大1。如果最大可能的整数值在数据表中曾经存在过,INSERT将会失败, 并返回sqlITE_FULL错误代码。@H_404_10@ 多个应用程序或一个应用程序的多个实例可以同时访问同一个数据库文件吗?@H_404_10@ 多个进程可同时打开同一个数据库。多个进程可以同时进行SELECT 操作,但在任一时刻,只能有一个进程对数据库进行更改。@H_404_10@ sqlite使用读、写锁控制对数据库的访问。(在Win95/98/ME等不支持读、写锁的系统下,使用一个概率性的模拟来代替。)但使用时要注意: 如果数据库文件存放于一个NFS文件系统上,这种锁机制可能不能正常工作。 这是因为 fcntl() 文件锁在很多NFS上没有正确的实现。 在可能有多个进程同时访问数据库的时候,应该避免将数据库文件放到NFS上。在Windows上,Microsoft的文档中说:如果使用 FAT 文件系统而没有运行 share.exe 守护进程,那么锁可能是不能正常使用的。那些在Windows上有很多经验的人告诉我:对于网络文件文件锁的实现有好多Bug,是靠不住的。如果他们说的是对的,那么在两台或多台Windows机器间共享数据库可能会引起不期望的问题。@H_404_10@ 我们意识到,没有其它嵌入式的 sql 数据库引擎能象 sqlite 这样处理如此多的并发。sqlite允许多个进程同时打开一个数据库,同时读一个数据库。当有任何进程想要写时,它必须在更新过程中锁住数据库文件。但那通常只是几毫秒的时间。其它进程只需等待写进程干完活结束。典型地,其它嵌入式的sql数据库引擎同时只允许一个进程连接到数据库。@H_404_10@ 但是,Client/Server数据库引擎(如 Postgresql,MysqL,或 Oracle)通常支持更高级别的并发,并且允许多个进程同时写同一个数据库。这种机制在Client/Server结构的数据库上是可能的,因为总是有一个单一的服务器进程很好地控制、协调对数据库的访问。如果你的应用程序需要很多的并发,那么你应该考虑使用一个Client/Server 结构的数据库。但经验表明,很多应用程序需要的并发,往往比其设计者所想象的少得多。@H_404_10@ 当sqlite试图访问一个被其它进程锁住的文件时,缺省的行为是返回 sqlITE_BUSY。 可以在C代码中使用 sqlite3_busy_handler() 或 sqlite3_busy_timeout() API 函数调整这一行为。@H_404_10@ 在sqlite数据库中如何列出所有的表和索引?@H_404_10@ 如果你运行 sqlite3 命令行来访问你的数据库,可以键入 “.tables”来获得所有表的列表。或者,你可以输入 “.schema” 来看整个数据库模式,包括所有的表的索引。输入这些命令,后面跟一个LIKE模式匹配可以限制显示的表。@H_404_10@ 在一个 C/C++ 程序中(或者脚本语言使用 Tcl/Ruby/Perl/Python 等) 你可以在一个特殊的名叫 sqlITE_MASTER 上执行一个SELECT查询以获得所有 表的索引。每一个 sqlite 数据库都有一个叫 sqlITE_MASTER 的表, 它定义数据库的模式。 sqlITE_MASTER 表看起来如下:@H_404_10@ CREATE TABLE sqlite_master (@H_404_10@ type TEXT,@H_404_10@ name TEXT,@H_404_10@ tbl_name TEXT,@H_404_10@ rootpage INTEGER,@H_404_10@ sql TEXT@H_404_10@ );@H_404_10@ 对于表来说,type 字段永远是 ‘table’,name 字段永远是表的名字。所以,要获得数据库中所有表的列表,使用下列SELECT语句:@H_404_10@ SELECT name from sqlite_master@H_404_10@ WHERE type=’table’@H_404_10@ ORDER BY name;@H_404_10@ 对于索引,type 等于 ‘index’,name 则是索引的名字,tbl_name 是该索引所属的表的名字。不管是表还是索引,sql 字段是原先用 CREATE TABLE 或 CREATE INDEX 语句创建它们时的命令文本。对于自动创建的索引(用来实现 PRIMARY KEY 或 UNIQUE 约束),sql字段为NULL。@H_404_10@ sqlITE_MASTER 表是只读的。不能对它使用 UPDATE、INSERT 或 DELETE。 它会被 CREATE TABLE、CREATE INDEX、DROP TABLE 和 DROP INDEX 命令自动更新。@H_404_10@ 临时表不会出现在 sqlITE_MASTER 表中。临时表及其索引和触发器存放在另外一个叫 sqlITE_TEMP_MASTER 的表中。sqlITE_TEMP_MASTER 跟 sqlITE_MASTER 差不多,但它只是对于创建那些临时表的应用可见。如果要获得所有表的列表, 不管是永久的还是临时的,可以使用类似下面的命令:@H_404_10@ SELECT name from@H_404_10@ (SELECT * from sqlite_master UNION ALL@H_404_10@ SELECT * from sqlite_temp_master)@H_404_10@ WHERE type=’table’@H_404_10@ ORDER BY name@H_404_10@ 在sqlite中,VARCHAR字段最长是多少?@H_404_10@ sqlite 不强制 VARCHAR 的长度。 你可以在 sqlITE 中声明一个 VARCHAR(10),sqlite还是可以很高兴地允许你放入500个字符。 并且这500个字符是原封不动的,它永远不会被截断。@H_404_10@ sqlite支持二进制大对象吗?@H_404_10@ sqlite 3.0 及以后版本允许你在任何列中存储 BLOB 数据。 即使该列被声明为其它类型也可以。@H_404_10@ 在sqlite中,如何在一个表上添加删除一列?@H_404_10@ sqlite 有有限地 ALTER TABLE 支持。你可以使用它来在表的末尾增加一列,可更改表的名称。 如果需要对表结构做更复杂的改变,则必须重新建表。重建时可以先将已存在的数据放到一个临时表中,删除原表, 创建新表,然后将数据从临时表中复制回来。@H_404_10@ 如,假设有一个 t1 表,其中有 “a”,“b”,“c” 三列, 如果要删除列 c ,以下过程描述如何做:@H_404_10@ BEGIN TRANSACTION;@H_404_10@ CREATE TEMPORARY TABLE t1_backup(a,b);@H_404_10@ INSERT INTO t1_backup SELECT a,b from t1;@H_404_10@ DROP TABLE t1;@H_404_10@ CREATE TABLE t1(a,b);@H_404_10@ INSERT INTO t1 SELECT a,b from t1_backup;@H_404_10@ DROP TABLE t1_backup;@H_404_10@ COMMIT;@H_404_10@ 在数据库删除了很多数据,但数据库文件没有变小,是Bug吗?@H_404_10@ 不是。当你从sqlite数据库删除数据时, 未用的磁盘空间将会加入一个内部的“自由列表”中。 当你下次插入数据时,这部分空间可以重用。磁盘空间不会丢失,但也不会返还给操作系统。@H_404_10@ 如果删除了大量数据,而又想缩小数据库文件占用的空间,执行 VACUUM 命令。 VACUUM 将会从头重新组织数据库。这将会使用数据库有一个空的“自由链表”, 数据库文件也会最小。但要注意的是,VACUUM 的执行会需要一些时间(在sqlite开发时,在Linux上,大约每M字节需要半秒种),并且, 执行过程中需要原数据库文件至多两倍的临时磁盘空间。@H_404_10@ 对于 sqlite 3.1版本,一个 auto-vacumm 模式可以替代 VACUUM 命令。 可以使用 auto_vacuum pragma 打开。@H_404_10@ sqlITE_SCHEMA error是什么错误?为什么会出现该错误?@H_404_10@ 当一个准备好的(prepared)sql语句不再有效或者无法执行时, 将返回一个 sqlITE_SCHEMA 错误。发生该错误时,sql语句必须使用 sqlite3_prepare() API来重新编译. 在 sqlite 3 中,一个 sqlITE_SCHEMA 错误只会发生在用 sqlite3_prepare()/sqlite3_step()/sqlite3_finalize() API 执行 sql 时。而不会发生在使用 sqlite3_exec()时。 在版本2中不是这样。@H_404_10@ 准备好的语句失效的最通常原因是:在语句准备好后, 数据库的模式又被修改了。另外的原因会发生在:@H_404_10@ 数据库离线:DETACHed.@H_404_10@ 数据库被 VACUUMed@H_404_10@ 一个用户存储过程定义被删除或改变。@H_404_10@ 一个 collation 序列定义被删除或改变。@H_404_10@ 认证函数被改变。@H_404_10@ 在所有情况下,解决方法是重新编译并执行该sql语句。 因为一个已准备好的语句可以由于其它进程改变数据库模式而失效,所有使用 sqlite3_prepare()/sqlite3_step()/sqlite3_finalize() API 的代码都应准备处理 sqlITE_SCHEMA 错误。下面给出一个例子:@H_404_10@ int rc;@H_404_10@ sqlite3_stmt *pStmt;@H_404_10@ char zsql[] = “SELECT …..”;@H_404_10@ do {@H_404_10@ /* Compile the statement from sql. Assume success. */@H_404_10@ sqlite3_prepare(pDb,zsql,-1,&pStmt,0);@H_404_10@ while( sqlITE_ROW==sqlite3_step(pStmt) ){@H_404_10@ /* Do something with the row of available data */@H_404_10@ }@H_404_10@ /* Finalize the statement. If an sqlITE_SCHEMA error has@H_404_10@ ** occured,then the above call to sqlite3_step() will have@H_404_10@ ** returned sqlITE_ERROR. sqlite3_finalize() will return@H_404_10@ ** sqlITE_SCHEMA. In this case the loop will execute again.@H_404_10@ */@H_404_10@ rc = sqlite3_finalize(pStmt);@H_404_10@ } while( rc==sqlITE_SCHEMA );

如何在字符串中使用单引号(‘)?@H_404_10@ sql 标准规定,在字符串中,单引号需要使用逃逸字符,即在一行中使用两个单引号。在这方面 sql 用起来类似 Pascal 语言。 sqlite 尊循标准。如:@H_404_10@ INSERT INTO xyz VALUES(’5 O”clock’);@H_404_10@ sqlite中如何返回本地化当前时间?@H_404_10@ 在做ClinicOS的时候遇到一个问题,在保存病历登记时间时,我使用了“CURRENT_TIMESTAMP”,但这有个问题,它返回的是UTC Time,这对我们中国人没啥用,一直希望能想办法将它转为localtime。今天刚好有空,所以去查了查sqlite的Mail List,果然也有人遇到了这个问题,我从一篇名为《translate time comparison statement》(http://www.mail-archive.com/sqlite-users@sqlite.org /msg12350.html)中看到这样的回复:@H_404_10@ 二十.如何更新表中数据@H_404_10@ update contact set lastname=’江南七怪’where id = 1028

update contact set lastname=’江南七怪’,mobile=’13912345678′ where id=1028; 二十一.如何一次插入多个数据 Insert into SAMPLE(PRJNUM,PRJNAME,EMYNUM,EMYNAME,SALCATEGORY,SALPACKAGE) values(100001,‘TPMS’,200001,‘Johnson’,‘A’,2000),(100001,200002,‘Christine’,‘B’,3000),200003,‘Kevin’,‘C’,4000),(100002,‘TCT’,200004,‘Apple’,3000);

原文链接:https://www.f2er.com/sqlite/200022.html

猜你在找的Sqlite相关文章