--帮助文档里对于每个函数都有详细的说明
select getdate()
print getdate()
--https://docs.microsoft.com/zh-cn/sql/t-sql/functions/dateadd-transact-sql?view=sql-server-ver16
--DATEADD(datepart,number,date),计算增加以后的日期。
--datepart为计量单位,哪一部分增加,单位,年/月/小时/分钟
--number 参数不能超出 int 的范围
select dateadd(day,200,getdate())
select dateadd(hour,200,getdate())
select dateadd(second,200,getdate())
select year(getdate())
--datediff(datepart,startdate,enddate)返回两个日期的差
--datepart(datepart,date)返回一个日期的特定部分
select datepart(second,getdate())
select datepart(ms,getdate())
select datepart(minute,getdate())
建表:
--创建一张表,记录电话呼叫员的工作流水,记录呼叫员编号、对方号码、通话开始时间、通话结束时间。
CREATE TABLE [CallRecords]
(
[Id] [int] NOT NULL identity(1,1),
[CallerNumber] [nvarchar](50), --三位数字,呼叫中心员工编号(工号)
[TelNum] [varchar](50),
[StartDateTime] [datetime] NULL,
[EndDateTime] [datetime] NULL --结束时间要大于开始时间,默认当前时间
)
--主键约束
alter table [CallRecords]
add constraint PK_CallRecords primary key(id)
--检查约束
alter table [CallRecords]
add constraint CK_CallRecords check(CallerNumber like '[0-9][0-9][0-9]')
alter table [CallRecords]
add constraint CK_CallRecords_EndDateTime check(EndDateTime > StartDateTime)
--默认约束
alter table [CallRecords]
add constraint DF_CallRecords default(getdate()) for EndDateTime
INSERT [dbo].[CallRecords] ([CallerNumber], [TelNum], [StartDateTime], [EndDateTime]) VALUES ('001', '0208888888', CAST(0x00009DAF00A4CB80 AS DateTime), CAST(0x00009DAF00A62E94 AS DateTime));
INSERT [dbo].[CallRecords] ([CallerNumber], [TelNum], [StartDateTime], [EndDateTime]) VALUES ('001', '0208888888', CAST(0x00009DB000D63BC0 AS DateTime), CAST(0x00009DB000D68DC8 AS DateTime));
INSERT [dbo].[CallRecords] ([CallerNumber], [TelNum], [StartDateTime], [EndDateTime]) VALUES ('001', '89898989', CAST(0x00009DB000E85C60 AS DateTime), CAST(0x00009DB000E92F50 AS DateTime));
INSERT [dbo].[CallRecords] ([CallerNumber], [TelNum], [StartDateTime], [EndDateTime]) VALUES ('002', '98987676', CAST(0x00009DB2015BB7A0 AS DateTime), CAST(0x00009DB2015C4DA0 AS DateTime));
INSERT [dbo].[CallRecords] ([CallerNumber], [TelNum], [StartDateTime], [EndDateTime]) VALUES ('002', '02188839389', CAST(0x00009DA4014C9C70 AS DateTime), CAST(0x00009DA4014E0308 AS DateTime));
INSERT [dbo].[CallRecords] ([CallerNumber], [TelNum], [StartDateTime], [EndDateTime]) VALUES ('001', '767676766', CAST(0x00009DB400DAA0C0 AS DateTime), CAST(0x00009DB400DD5FE0 AS DateTime));
INSERT [dbo].[CallRecords] ([CallerNumber], [TelNum], [StartDateTime], [EndDateTime]) VALUES ('003', '0227864656', CAST(0x00009DB200B9AB40 AS DateTime), CAST(0x00009DB200B9FC1C AS DateTime));
INSERT [dbo].[CallRecords] ([CallerNumber], [TelNum], [StartDateTime], [EndDateTime]) VALUES ('003', '676765777', CAST(0x00009DB8014042B8 AS DateTime), CAST(0x00009DB80141804C AS DateTime));
INSERT [dbo].[CallRecords] ([CallerNumber], [TelNum], [StartDateTime], [EndDateTime]) VALUES ('001', '89977653', CAST(0x00009D9A00FB9898 AS DateTime), CAST(0x00009D9A00FE6118 AS DateTime));
INSERT [dbo].[CallRecords] ([CallerNumber], [TelNum], [StartDateTime], [EndDateTime]) VALUES ('004', '400400400', CAST(0x00009D9A00FB9898 AS DateTime), CAST(0x00009D9A00FE6118 AS DateTime));
select * from CallRecords
练习:
use TestSchool
select * from CallRecords
--输出所有数据中通话时间最长的5条记录
select top 5 *,'通话时长(秒)'=datediff(second,StartDateTime,EndDateTime) from CallRecords order by datediff(second,StartDateTime,EndDateTime) desc
--输出所有数据中拨打长途号码(对方号码以0开头)的总时长
select sum(datediff(second,StartDateTime,EndDateTime))
from CallRecords where TelNum like '0%'
--输出本月通话总时长最多的前三个呼叫员的编号
--注意本月这个条件
select top 3 CallerNumber from CallRecords
where month(StartDateTime)=month(getdate())
group by CallerNumber
order by sum(datediff(second,StartDateTime,EndDateTime)) desc
--输出本月拨打电话次数最多的前三个呼叫员的编号
select top 3 CallerNumber,count(*) as '次数' from CallRecords
where month(StartDateTime)=month('2022-07-21')
group by CallerNumber
order by count(*) desc
--按照月份分组
--开始时间的月份,也可以是与当前时间相差的月份,只是函数上的不同
select datepart(month,StartDateTime) as m
from CallRecords group by datepart(month,StartDateTime)
就是在.net平台下操作数据库的一种方式,就是一组类库,通过程序的方式访问数据库
1、数据提供程序
Connection :连接 数据库
Command :执行 SQL语句
DataReader :只读、只进 的结果集,一条条读取数据
通过以上三个就可以实现所有功能,
DataAdapter :封装了以上三个对象的对象
2、数据集(DataSet)
临时数据库
断开式数据操作
ConnectionStringBuilder //自动生成连接字符串
Parameter //带参数的SQL语句
Transaction //在ADO.NET中使用事务
与DataSet相关的类:
DataView //视图类,DataTable中的数据以不同的视角查看
DataRowView //DataView中的行。
DataTable //DataSet中的数据表
DataRow //DataTable中的行
DataColumn //DataTable中的列
DataRealation //DataTable与DataTable的关系
Constraint //DataTable中建立的约束
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _01_连接数据库
{
internal class Program
{
static void Main(string[] args)
{
//1、连接字符串
//server=.;uid=sa;pwd=123456;database=education;
//使用windows验证方式,integrated security=true表示可以在不知道数据库用户名和密码的情况下时,依然可以连接数据库
//Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;Integrated Security=True
//使用 sql server身份验证方式
//连接数据库的字符串,Data Source 是数据源,Initial Catalog(初始目录)是数据库的名称,User ID是登录数据的用户名,Pwd是登录数据库的密码。
//DataSource也可以写. 表示本地数据库
string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
//2、根据连接字符串创建连接对象
/*SqlConnection con = new SqlConnection(constr);
con.Open();
//服务器如果没有打开会抛出异常
Console.WriteLine("打开成功");
con.Close();
con.Dispose();
Console.WriteLine("关闭成功");
Console.ReadKey();*/
using(SqlConnection con = new SqlConnection(constr))
{
con.StateChange += new System.Data.StateChangeEventHandler(Con_StateChange);
con.Open();//不能重复打开,否则会报错
//如果关闭就打开
if(con.State==System.Data.ConnectionState.Closed)
{
con.Open();
}
Console.WriteLine("打开成功");
con.Close();
con.Close();
//但是可以重复关闭
Console.WriteLine("关闭成功");
}
Console.ReadKey();
}
//连接对象状态改变时间
private static void Con_StateChange(object sender, System.Data.StateChangeEventArgs e)
{
Console.WriteLine(e.CurrentState.ToString());
}
}
}
执行结果:
连接字符串的获取方法还可以是:
其实是套用SqlConnectionStringBuilder生成的字符串
控制台使用代码生成如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder();
scsb.DataSource = "LAPTOP-CELEUP2E";
scsb.UserID = "sa";
scsb.Password = "123456";
scsb.ConnectTimeout = 30;
//连接超时设置
scsb.InitialCatalog = "TestSchool";
Console.WriteLine(scsb.ConnectionString);
Console.ReadKey();
}
}
}
运行结果如图所示:
桌面程序生成代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Data.SqlClient;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//SqlConnectionStringBuilder的属性添加到控件中
SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder();
propertyGrid1.SelectedObject = scsb;
}
private void button2_Click(object sender, EventArgs e)
{
SqlConnectionStringBuilder scsb = (SqlConnectionStringBuilder)propertyGrid1.SelectedObject;
MessageBox.Show(scsb.ConnectionString.ToString());
}
}
}
执行结果:
使用的控件是propertygrid,第一次用哈哈
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//增加
private void button1_Click(object sender, EventArgs e)
{
string constr="Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
//连接对象连接字符串
using(SqlConnection conn = new SqlConnection(constr))
{
string sql = string.Format("insert into Employees values('{0}','{1}','{2}','{3}','{4}')",
textBox1.Text.Trim(), textBox2.Text.Trim(), textBox3.Text.Trim(),
textBox4.Text.Trim(), textBox5.Text.Trim());
//命令对象,执行sql语句
using(SqlCommand cmd=new SqlCommand(sql,conn))
{
//cmd.CommandText = sql;
//cmd.Connection = conn;
//打开连接
conn.Open();
//执行sql语句,使用ExecuteNonQuery执行insert、delete、update语句,返回值是一个整数,表示当前语句执行后影响的行数,执行其它类的语句返回-1
int r = cmd.ExecuteNonQuery();
MessageBox.Show("插入"+r+"行。");
}
}
}
//删除
private void button2_Click(object sender, EventArgs e)
{
int r = 0;
string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
using(SqlConnection conn=new SqlConnection(constr))
{//创建连接对象连接字符串
string sql = string.Format("delete from Employees where EmpId={0}",textBox6.Text.Trim());
using (SqlCommand cmd=new SqlCommand(sql, conn))
{//创建执行对象
conn.Open();
//打开连接执行语句
//用之前打开,用完之后关闭
r = cmd.ExecuteNonQuery();
}
}
MessageBox.Show("删除"+r+"行。");
}
//修改
private void button3_Click(object sender, EventArgs e)
{
int r = 0;
string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
using (SqlConnection conn = new SqlConnection(constr))
{//创建连接对象连接字符串
string sql = string.Format("update Employees set EmpName='{0}',EmpAge={1} where EmpId={2}","小芳",30, textBox7.Text.Trim());
using (SqlCommand cmd = new SqlCommand(sql, conn))
{//创建执行对象
conn.Open();
//打开连接执行语句
//用之前打开,用完之后关闭
r = cmd.ExecuteNonQuery();
}
}
MessageBox.Show("修改" + r + "行。");
}
}
}
界面图:
查询:
特性:
1、是只进的,SqlDataReader每次读取一条就释放一条,即每次向前走一条都会将上一条记录销毁,所以只能向前不能后退,适合从数据库中读取大量数据
2、只能读取数据不能修改数据,查询出的结果集是一个独立在服务器内存中的数据,与源表没有任何关系了。
注:返回后数据在数据库服务器缓存,使用DataReader时必须保证连接对象是open状态。
如果数据库中有空数据,提前用reader.IsDbNull(index)方法判断是否为空
string s=reader.IsDBNull(0)?“”:reader.GetString(0);
默认情况下,SqlDataReader独享一个连接对象,除非将多活动结果集设置为true,但是一般不建议这么用。
SqlConnectionStringBuilder sb;
sb.MultipleActiveResultSets = true;
代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp2
{
internal class Program
{
static void Main(string[] args)
{
string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
//连接对象连接字符串
using (SqlConnection conn = new SqlConnection(constr))
{
string sql = "select * from Employees";
//命令对象,执行sql语句
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
conn.Open();
//调用ExecuteReader方法的时候执行了sql语句,并返回了一个用来获取数据的工具reader
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.HasRows)
{//判断是否查出来数据
//reader使用过程当中必须保证conn是打开状态
while (reader.Read())
{//查询出来,一条条循环
//这个while循环尽量不要做耗时的操作,因为一直连接着服务器
for(int i = 0; i < 6; i++)
{//一列一列地读取,不是按照行
//1、方法一
Console.Write(reader.GetValue(i).ToString() + "\t");
//2、方法二,也是根据列的索引
Console.Write(reader[i]);
//3、方法三,可以根据列的名称,实质上是根据列名获得索引
Console.Write(reader["EmpName"]);
//相当于
int index=reader.GetOrdinal("EmpName");
Console.Write(index);
//方法四,使用强类型方法直接获取相应列的值,
//需要注意的是,数据库中的float,这里用GetDouble方法获取
int EmpId=reader.GetInt(0);
string name=reader.GetString(1);
.......
//总结:============一般没有特殊情况使用索引获取信息,使用列名获取效率低下,如果非要用列名获取,在循环外面可以现根据列名获取列的索引,然后在循环里面还是通过索引获取
}
Console.WriteLine();
}
}
}
}
}
Console.ReadKey();
}
}
}
结果:
读取多个结果集的方法:
代码:
string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
using (SqlConnection conn = new SqlConnection(constr))
{
string sql = "select * from Employees;select * from Department";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
#region 读取一个结果集
/*if (reader.HasRows)
{
while (reader.Read())
{
for (int i = 0; i < 6; i++)
{
Console.Write(reader.GetValue(i).ToString() + "\t");
Console.WriteLine();
}
}
}*/
#endregion
#region 读取多个结果集
do
{
if (reader.HasRows)
{
while (reader.Read())
{
for(int i = 0; i < reader.FieldCount; i++)
{//循环列
string s=reader.GetDataTypeName(i);
switch (s)
{
case "varchar":
case "char":
Console.Write(reader.GetString(i)+"\t");
break;
case "int":
Console.Write(reader.GetInt32(i) + "\t");
break;
}
}
Console.WriteLine("");
}
}
} while (reader.NextResult());
#endregion
}
}
}
Console.ReadKey();
结果:
需要连接数据库的语句都可能会发生异常
string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
//连接对象连接字符串
using (SqlConnection conn = new SqlConnection(constr))
{
//命令对象,执行sql语句
using (SqlCommand cmd = new SqlCommand(sql, conn))
{//但是其实using中不需要写try catch
//因为发生异常using语句结束会调用dispose方法,也会释放资源
//但是有些情况using不能使用,必须使用try catch
try
{
conn.Open();
//调用ExecuteReader方法的时候执行了sql语句,并返回了一个用来获取数据的工具reader
..........
}
catch
{Console.WriteLine("异常处理");}
finally
{
conn.Close();
conn.Dispose();
}
}
}
Console.ReadKey();
}
}
}
返回单个值,内部也是调用ExecuteReader实现的,执行查询,返回结果集中第一行的第一列
执行任何sql语句调用command的ExecuteNonQuery、ExecuteReader、ExecuteScalar哪个方法都可以,只不过尽量根据自己想要的结果调用相应的方法会更方便而已。
string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
using (SqlConnection conn = new SqlConnection(constr))
{
string sql = "select count(*) from Employees";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
conn.Open();
int a = (int)cmd.ExecuteScalar();
//只找第一行的第一列,如果返回的是很多条数据,获得的最终对象仍然是第一行的第一列
Console.WriteLine(a);
}
}
Console.ReadKey();
using (SqlConnection conn = new SqlConnection(constr))
{
string sql = String.Format("insert into Employees output inserted.EmpId values('{0}','{1}','{2}','{3}','{4}')",
"夏春", "男", 48,"00@qq.com", "玄晶司");
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
conn.Open();
object a = Convert.ToInt32(cmd.ExecuteScalar());
//使用这种方法转整数范围更广,可以把"0"转换为整型,但是(int)a的方法进行类型转换叫做拆箱,如果装箱不是int类型就报错
Console.WriteLine(a);
}
}
Console.ReadKey();
string userName=textBox1.Text.Trim();
string userPwd=textBox2.Text;
string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
using (SqlConnection conn = new SqlConnection(constr))
{
#region 1、ExecuteScalar返回记录数
/*string sql = string.Format( "select count(*) from UserLogin where userName='{0}' and userPwd='{1}'"
,userName,userPwd);
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
conn.Open();
int a = Convert.ToInt32 (cmd.ExecuteScalar());
conn.Close();
if (a == 0) MessageBox.Show("登录失败");
else MessageBox.Show("登录成功");
}*/
#endregion
#region 2、SqlDataReader返回是否有数据的信息
//HasRows
#endregion
//或者取出用户名对应的密码是否相等
//根据不同的需要选择不同的方法
}
启用与否效果对比:
//默认情况下,启用连接池 00:00:00.1354495
//string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
//禁用连接池 00:00:04.7489992
string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456;Pooling=false";
Stopwatch st = new Stopwatch();
st.Start();
for (int i = 0; i < 2000; i++)
{
using (SqlConnection conn = new SqlConnection(constr))
{
conn.Open();
conn.Close();
}
}
st.Stop();
Console.WriteLine(st.Elapsed);
Console.WriteLine("打开又关闭");
Console.ReadKey();
使用它进行跟踪:
如果启用连接池,看似2000次的登录注销其实只有一次打开,当程序关闭后才会关闭,所以高性能
而禁用连接池后,是真正打开关闭了2000次。
调用close方法其实是并没有关闭,一直处于打开状态,只是把当前的连接对象放在了连接池中,当下次再创建一个连接对象时,如果使用的连接字符串与上次连接对象的连接字符串完全一致,这时并不会真正再创建一个到数据库的连接,而是会使用连接池中现有的连接。
第一次open会打开连接,真正连接数据库的是innerConnection,销毁的是conn对象,内部的innerConnection对象没有销毁
代码证明如下:
string constr = "Data Source=LAPTOP-CELEUP2E;Initial Catalog=TestSchool;User ID=sa;Pwd=123456";
PropertyInfo pinfo=typeof(SqlConnection).GetProperty("InnerConnection"
,BindingFlags.Instance | BindingFlags.NonPublic);
SqlConnection conn1 = new SqlConnection(constr);
object obj1 = null;
object obj2 = null;
using (conn1)
{
conn1.Open();
//获取内部innerconnection对象
obj1=pinfo.GetValue(conn1,null);
conn1.Close();
}
SqlConnection conn2 = new SqlConnection(constr);
using (conn2)
{
conn2.Open();
//获取内部innerconnection对象
obj2 = pinfo.GetValue(conn2, null);
conn2.Close();
}
if (obj1 == obj2)
{
Console.WriteLine("是同一个对象");
}
else
{
Console.WriteLine("不是同一个对象");
}
Console.ReadKey();
执行结果:是同一个对象
如果Pooling=false,那就不是同一个对象
使用总结:
第一次打开连接会创建一个连接对象,当这个连接关闭时(调用close方法)会将这个连接对象放入池中,下一个连接对象如果连接字符串与池中现有连接对象的连接字符串完全一致,则会使用池中的现有连接,而不会重新创建一个
只有对象调用close才会放入池中,如果一个连接对象一直在使用,则下次再创建一个连接对象发现池中没有,也会再创建一个新的连接对象。
在池中的连接对象,如果过一段时间没有被访问则会自动销毁。
如果连接池中有对象,则open的意思是去池中把对象取出来。
缺点:始终保持连接状态,但是总归利大于弊
什么情况下需要禁用连接池?
如果某个应用程序有多个刻划断,每个访问时采用的都是各自的连接字符串,这时如果采用连接池,虽然每次打开连接的速度会变快,但是由于池的问题,同时会保存多个打开的连接对象。
连接池清空:
Connection对象的ClearAllPools和ClearPool方法