t-sql 筆記(1)

Toad-for-SQL-Server-Freeware

1.查詢哪些數據庫對象使用了某個表

SELECT b.[name], a.[definition]
FROM sys.all_sql_modules a, sysobjects b
WHERE a.[object_id] = id AND definition LIKE '%表名%'

2.查詢表的某一列,將結果變成用逗號分隔的字符串

select col+',' from mytable for xml path('')

排序:html

create table pd(col1 varchar(5),col2 int)

insert into pd
 select 'A',3 union all
 select 'A',2 union all
 select 'A',1 union all
 select 'B',2 union all
 select 'B',1


select a.col1,
       stuff((select ','+rtrim(b.col2)
              from pd b
              where b.col1=a.col1
              order by b.col2 
              for xml path('')),1,1,'') 'col2' 
 from pd a
 group by a.col1

 

 

3.查詢有哪些表的表名包含「storeroom」

 select * from sysobjects obj where LOWER(obj.name) LIKE N'%storeroom%' and xtype='U'

4.分組條件求和

DECLARE @t1 TABLE ( c1   NUMERIC (12), c2   VARCHAR (30) )
INSERT INTO @t1 (c1, c2) VALUES (1, 'a');
INSERT INTO @t1 (c1, c2) VALUES (2, 'a');
INSERT INTO @t1 (c1, c2) VALUES (3, 'b');
INSERT INTO @t1 (c1, c2) VALUES (4, 'b');
SELECT CASE WHEN max (c1) > 3 THEN sum (c1) ELSE 0 END AS c
  FROM @t1
GROUP BY c2;
/*
結果:
c
0
7
*/

5.求某一天所在星期的週日

http://www.cnblogs.com/wsdj-ITtech/archive/2011/10/06/2199736.html sql

USE [MSSQL]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[My_OneDay_GetWeekFirstAndEndDay](@tmpDate DATETIME)
RETURNS  @tmpTable TABLE(FirstDay DATETIME , EndDay DATETIME)
AS
BEGIN
    INSERT INTO @tmpTable
    SELECT a.FirstDay,b.EndDay FROM (    
        SELECT 1 AS ID,DATEADD(wk, DATEDIFF(wk,0,@tmpDate), 0) AS FirstDAy
    ) a
    LEFT JOIN (
        SELECT 1 AS ID,DATEADD(wk, DATEDIFF(wk,0,@tmpDate), 6) AS EndDay
    ) b
    ON a.ID = b.ID
    RETURN
END
SELECT * from  My_OneDay_GetWeekFirstAndEndDay('2010-09-01')

 

6.求時間段內週日的數量

http://www.cnblogs.com/wsdj-ITtech/archive/2011/10/06/2199736.html shell

CREATE FUNCTION [dbo].[MY_Range_GetWeekFirstAndEndDays](@tmpDateSTART DATETIME,@tmpDateEND DATETIME)
RETURNS  @tmpTable TABLE(WeekOrder INT,FirstDay DATETIME , EndDay DATETIME)
AS
BEGIN   
 DECLARE   @tmpDate   DATETIME
 DECLARE   @index         INT
 SET       @tmpDate=@tmpDateSTART
 SET       @index=1
     WHILE     @tmpDate <=@tmpDateEND
        BEGIN 
             INSERT INTO @tmpTable
                SELECT @index,a.FirstDay,b.EndDay FROM (    
                    SELECT 1 AS ID,DATEADD(wk, DATEDIFF(wk,0,@tmpDate), 0) AS FirstDAy) a
                LEFT JOIN (
                    SELECT 1 AS ID,DATEADD(wk, DATEDIFF(wk,0,@tmpDate), 6) AS EndDay) b
                ON a.ID = b.ID

          SET   @tmpDate=DATEADD(DAY,7,@tmpDate)
          SET   @index=@index+1
         END 
     RETURN
END
SELECT * from  My_Range_GetWeekFirstAndEndDays('2011-09-01','2011-10-06')

不使用臨時表:數據庫

DECLARE @tmpDateSTART   DATETIME
DECLARE @tmpDateEND   DATETIME
SET @tmpDateSTART = '2015-1-1'
SET @tmpDateEND = '2015-1-21'

DECLARE @tmpDate   DATETIME
DECLARE @days   INT
SET @tmpDate = @tmpDateSTART
SET @days = 0

WHILE @tmpDate <= @tmpDateEND
BEGIN  
   DECLARE @theDate   DATETIME;
   SET @theDate = DATEADD (wk, DATEDIFF (wk, 0, @tmpDate), 6);
   IF @theDate > @tmpDateSTART AND @theDate < @tmpDateEND      
      SET @days = @days + 1  
   SET @tmpDate = DATEADD (DAY, 7, @tmpDate)
END

SELECT @days

 

7.查看正在執行的

SELECT [Spid] = session_Id,
       ecid,
       [Database] = DB_NAME (sp.dbid),
       [User] = nt_username,
       [Status] = er.status,
       [Wait] = wait_type,
       [Individual Query] =
          SUBSTRING (
             qt.text,
             er.statement_start_offset / 2,
               (  CASE
                     WHEN er.statement_end_offset = -1
                     THEN
                        LEN (CONVERT (NVARCHAR (MAX), qt.text)) * 2
                     ELSE
                        er.statement_end_offset
                  END
                - er.statement_start_offset)
             / 2),
       [Parent Query] = qt.text,
       Program = program_name,
       Hostname,
       nt_domain,
       start_time,
       datediff (second, start_time, getdate ())
  FROM sys.dm_exec_requests er
       INNER JOIN sys.sysprocesses sp ON er.session_id = sp.spid
       CROSS APPLY sys.dm_exec_sql_text (er.sql_handle) AS qt
 WHERE session_Id > 50                                 -- Ignore system spids.
                      AND session_Id NOT IN (@@SPID) -- Ignore this current statement.
ORDER BY datediff (second, start_time, getdate ()) DESC

 8.生成GUID

C#中用Guid.NewGuid().ToString()緩存

Sql中用NEWID() 安全

以上方法生成的是36位的GUID,若是須要轉換成32位,則須要替換掉其中的'-'字符。session

Sql中的方法:replace(newid(), '-', '')dom

 

9.時間段統計

--將時間轉換爲小時
--例如8:22分轉換爲9
--查詢語句以下
SELECT cast (
          datepart (
             hh,
             dateadd (
                mi,
                  (    datediff (mi,
                                 CONVERT (VARCHAR (10), getdate (), 112),
                                 getdate ())
                     / 60
                   + 1)
                * 60,
                CONVERT (VARCHAR (10), getdate (), 112))) AS INT)

 

http://bbs.csdn.net/topics/190127317

--環境
declare @t table (
時間 datetime,
金額 int
)
insert @t select
    '2007-1-1 10:00:23',           8 
union all select
    '2007-1-1 10:01:24',           4 
union all select
    '2007-1-1 10:05:00',           2   
union all select
    '2007-1-1 10:06:12',           3 
union all select
    '2007-1-1 10:08:00',           1 
union all select
    '2007-1-1 10:12:11',           5 

select dateadd(mi,(datediff(mi,convert(varchar(10),時間,112),時間)/5+1)*5,convert(varchar(10),時間,112)) as 時間段,
count(*) as 行數,sum(金額) as 總金額
from @t
group by dateadd(mi,(datediff(mi,convert(varchar(10),時間,112),時間)/5+1)*5,convert(varchar(10),時間,112))

--結果
時間段                                                    行數          總金額         
------------------------------------------------------ ----------- ----------- 
2007-01-01 10:05:00.000                                2           12
2007-01-01 10:10:00.000                                3           6
2007-01-01 10:15:00.000                                1           5

(所影響的行數爲 3 行)

 10.存儲過程,有時執行很慢

如今的解決辦法是,將存儲過程當中加個空格,alter一下。ide

exec 存儲過程 with recompile函數

 

http://bbs.csdn.net/topics/340185343

http://my.oschina.net/HenuToater/blog/177175

http://havebb.com/b/post/produce-suddenly-slow.aspx

http://www.cnblogs.com/luckylei66/archive/2012/07/30/2615000.html

    SQL優化之存儲過程強制編譯
     
    ASP.NET調用SQL後臺存儲過程時,有時忽然就變得很慢,在後臺直接執行存儲過程沒問題,但在前臺調用存儲過程時就是很慢,並且在前臺調用成功後,再次調用仍是同樣的慢,但更新一下存儲過程再調用就很快了。但這始終不能完全解決問題,過段時間又會出來一樣的問題。環境(NET4.0+SQL2008R2) 
     
    解決辦法: 
     
    方法一:在可能比較耗時的語句後面加上option(recompile) 
     
    方法二:強制編譯存儲過程 
     
    SQL Server 提供三種從新編譯存儲過程的方法: 
     
    (1)、sp_recompile 系統存儲過程強制在下次運行存儲過程時進行從新編譯。
     
      示例:exec sp_recompile 存儲過程名 
     
    (2)、建立存儲過程時在其定義中指定 WITH RECOMPILE 選項,代表 SQL Server 將不對該存儲過程計劃進行高速緩存;該存儲過程將在每次執行時都從新編譯。
     
      示例:Create Proc 存儲過程名 WITH RECOMPILE AS 參數 
     
    (3)、在執行存儲過程時指定 WITH RECOMPILE 選項,可強制對存儲過程進行從新編譯。僅當所提供的參數不典型,或者自建立該存儲過程後數據發生顯著更改時才應使用此選項。
      示例:存儲過程名 WITH RECOMPILE
查看詳細

參數探測(Parameter Sniffing)與影響計劃重用的SET選項

 http://www.cnblogs.com/bigholy/archive/2011/10/18/2216489.html

 SQL Server中存儲過程比直接運行SQL語句慢的緣由

http://blog.csdn.net/emili/article/details/2192081

 

在不少的資料中都描述說SQLSERVER的存儲過程較普通的SQL語句有如下優勢:

1.       存儲過程只在創造時進行編譯便可,之後每次執行存儲過程都不需再從新編譯,而咱們一般使用的SQL語句每執行一次就編譯一次,因此使用存儲過程可提升數據庫執行速度。

2.       常常會遇到複雜的業務邏輯和對數據庫的操做,這個時候就會用SP來封裝數據庫操做。當對數據庫進行復雜操做時(如對多個表進行 Update,Insert,Query,Delete時),可將此複雜操做用存儲過程封裝起來與數據庫提供的事務處理結合一塊兒使用。能夠極大的提升數據 庫的使用效率,減小程序的執行時間,這一點在較大數據量的數據庫的操做中是很是重要的。在代碼上看,SQL語句和程序代碼語句的分離,能夠提升程序代碼的 可讀性。

3.       存儲過程能夠設置參數,能夠根據傳入參數的不一樣重複使用同一個存儲過程,從而高效的提升代碼的優化率和可讀性。

4.       安全性高,可設定只有某此用戶才具備對指定存儲過程的使用權存儲過程的種類:

A.       系統存儲過程:以sp_開頭,用來進行系統的各項設定.取得信息.相關管理工做,如 sp_help就是取得指定對象的相關信息。

B.       擴展存儲過程 以XP_開頭,用來調用操做系統提供的功能
exec master..xp_cmdshell 'ping 10.8.16.1'

C.       用戶自定義的存儲過程,這是咱們所指的存儲過程經常使用格式

    模版:Create procedure procedue_name [@parameter data_type][output]
    [with]{recompile|encryption} as sql_statement

    解釋:output:表示此參數是可傳回的

with {recompile|encryption} recompile:表示每次執行此存儲過程時都從新編譯一次;encryption:所建立的存儲過程的內容會被加密。

 

   可是最近咱們項目組中有人寫了一個存儲過程,其計算時間爲1個小時47分鐘,而有的時候運行時間都超過了兩個小時,同事描述說若是將存儲過程當中的語句拿出來直接運行也就10分鐘左右就運行完畢,我沒當回事,可是今天我本身寫的存儲過程也遇到了這個問題,在查找資料後緣由終於找到了緣由,原來是Parameter sniffing問題。

    下面看我是如何將運行一個小時以上的存儲過程優化成在一分鐘以內完成的:

原存儲過程

CREATE PROCEDURE [dbo].[pro_ImAnalysis_daily]

@THEDATE VARCHAR(30)

AS

BEGIN

    IF @THEDATE IS NULL

    BEGIN

       SET @THEDATE=CONVERT(VARCHAR(30),GETDATE()-1,112);

    END

 

 

    DELETE FROM RPT_IM_USERINFO_DAILY WHERE THEDATE=@THEDATE;

 

    INSERT RPT_IM_USERINFO_DAILY (THEDATE,ALLUSER,NEWUSER)

    SELECT AA.THEDATE,ALLUSER,NEWUSER

    FROM

    ( ( SELECT THEDATE,COUNT(DISTINCT USERID) ALLUSER

       FROM FACT

       WHERE THEDATE=@THEDATE

        GROUP BY THEDATE

       ) AA

       LEFT JOIN

       (SELECT THEDATE,COUNT(DISTINCT USERID) NEWUSER

        FROM FACT T1

        WHERE NOT EXISTS(

                         SELECT 1

                         FROM FACT T2

                         WHERE T2.THEDATE<@THEDATE

                              AND T1.USERID=T2.USERID)

              AND T1.THEDATE=@THEDATE

        GROUP BY THEDATE

        ) BB

       ON AA.THEDATE=BB.THEDATE);

GO

每日執行:exec pro_ImAnalysis_daily @thedate=null
耗時:1小時47分~2小時13分

通過查找資料,緣由以下(因爲源文是一篇英文,有些地方寫的我不是特別清楚,原文見http://groups.google.com/group/microsoft.public.sqlserver.server/msg/ad37d8aec76e2b8f?hl=en&lr=&ie=UTF-8&oe=UTF-8):

    在SQL Server中有一個叫作 「Parameter sniffing」的特性。SQL Server在存儲過程執行以前都會制定一個執行計劃。在上面的例子中,SQL在編譯的時候並不知道@thedate的值是多少,因此它在執行執行計劃的時候就要進行大量的猜想。假設傳遞給@thedate的參數大部分都是非空字符串,而FACT表中有40%的thedate字段都是null,那麼SQL Server就會選擇全表掃描而不是索引掃描來對參數@thedate制定執行計劃。全表掃描是在參數爲空或爲0的時候最好的執行計劃。可是全表掃描嚴重影響了性能。

    假設你第一次使用了Exec pro_ImAnalysis_daily @thedate=’20080312’那麼SQL Server就會使用20080312這個值做爲下次參數@thedate的執行計劃的參考值,而不會進行全表掃描了,可是若是使用@thedate=null,則下次執行計劃就要根據全表掃描進行了。

    有兩種方式可以避免出現「Parameter sniffing」問題:

(1)經過使用declare聲明的變量來代替參數:使用set @variable=@thedate的方式,將出現@thedate的sql語句所有用@variable來代替。

(2)  將受影響的sql語句隱藏起來,好比:

a)      將受影響的sql語句放到某個子存儲過程當中,好比咱們在@thedate設置成爲今天后再調用一個字存儲過程將@thedate做爲參數傳入就能夠了。

b)      使用sp_executesql來執行受影響的sql。執行計劃不會被執行,除非sp_executesql語句執行完。

c)      使用動態sql(」EXEC(@sql)」來執行受影響的sql。

採用(1)的方法改造例子中的存儲過程,以下:

    ALTER PROCEDURE [dbo].[pro_ImAnalysis_daily]

@var_thedate VARCHAR(30)

 

AS

BEGIN

    declare @THEDATE VARCHAR(30)

    IF @var_thedate IS NULL

    BEGIN

       SET @var_thedate=CONVERT(VARCHAR(30),GETDATE()-1,112);

    END

 

 

    SET @THEDATE=@var_thedate;

    DELETE FROM RPT_IM_USERINFO_DAILY WHERE THEDATE=@THEDATE;

 

   INSERT RPT_IM_USERINFO_DAILY (THEDATE,ALLUSER,NEWUSER)

    SELECT AA.THEDATE,ALLUSER,NEWUSER

    FROM

    ( ( SELECT THEDATE,COUNT(DISTINCT USERID) ALLUSER

       FROM FACT

       WHERE THEDATE=@THEDATE

        GROUP BY THEDATE

       ) AA

       LEFT JOIN

       (SELECT THEDATE,COUNT(DISTINCT USERID) NEWUSER

        FROM FACT T1

        WHERE NOT EXISTS(

                         SELECT 1

                         FROM FACT T2

                         WHERE T2.THEDATE<@THEDATE

                              AND T1.USERID=T2.USERID)

              AND T1.THEDATE=@THEDATE

        GROUP BY THEDATE

        ) BB

       ON AA.THEDATE=BB.THEDATE);

GO

 

測試執行速度爲10分鐘,我又檢查了一下這個SQL,發現這個SQL有問題,這個SQL使用了not exists,在一個大表裏面使用not exists是不太明智的,因此,我又對這個sql進行了改進,改爲以下:

    ALTER PROCEDURE [dbo].[pro_ImAnalysis_daily]

@var_thedate VARCHAR(30)

 

AS

BEGIN

    declare @THEDATE VARCHAR(30)

    IF @var_thedate IS NULL

    BEGIN

       SET @var_thedate=CONVERT(VARCHAR(30),GETDATE()-1,112);

    END

 

 

    SET @THEDATE=@var_thedate;

    DELETE FROM RPT_IM_USERINFO_DAILY WHERE THEDATE=@THEDATE;

 

    INSERT RPT_IM_USERINFO_DAILY(THEDATE,ALLUSER,NEWUSER)

    select @thedate as thedate,

           count(distinct case when today>0 then userid else null end) as alluser,

           count(distinct case when dates=0 then userid else null end) as newuser

    from

    (

       select userid,

              count(CASE WHEN thedate>=@thedate then null else thedate end) as dates,

              count(case when thedate=@thedate then thedate else null end) as today

       from   FACT

       group by userid

    )as fact

GO

測試結果爲30ms如下。
SQL Server中存儲過程比直接運行SQL語句慢的緣由

 

11.表變量及臨時表

http://support.microsoft.com/kb/305977/zh-cn

問題 1:爲何在已經有了臨時表的狀況下還要引入表變量?

解答 1:與臨時表相比,表變量具備下列優勢:

    如 SQL Server 聯機叢書「表」(Table) 一文中所述,表變量(如局部變量)具備明肯定義的範圍,在該範圍結束時會自動清除這些表變量。
    與臨時表相比,表變量致使存儲過程的從新編譯更少。
    涉及表變量的事務僅維持表變量上更新的持續時間。所以,使用表變量時,須要鎖定和記錄資源的狀況更少。由於表變量具備有限的範圍而且不是持久性數據庫的一部分,因此事務回滾並不影響它們。

問題 2:若是說使用表變量比使用臨時表致使存儲過程的從新編譯更少,這意味着什麼?

解答 2:下面的文章討論了從新編譯存儲過程的一些緣由:

243586 存儲過程從新編譯的疑難解答
「因爲某些臨時表操做引發的從新編譯」一節還列出了爲避免一些問題(例如使用臨時表致使從新編譯)而須要知足的一些要求。這些限制不適用於表變量。

表變量徹底獨立於建立這些表變量的批,所以,當執行 CREATE 或 ALTER 語句時,不會發生「從新解析」,而在使用臨時表時可能會發生「從新解析」。臨時表須要此「從新解析」,以便從嵌套存儲過程引用該表。表變量徹底避免了此問題,所以存儲過程可使用已編譯的計劃,從而節省了處理存儲過程的資源。

問題 3:表變量有哪些缺陷?

解答 3:與臨時表相比,它存在下列缺陷:

    在表變量上不能建立非彙集索引(爲 PRIMARY 或 UNIQUE 約束建立的系統索引除外)。與具備非彙集索引的臨時表相比,這可能會影響查詢性能。
    表變量不像臨時表那樣能夠維護統計信息。在表變量上,不能經過自動建立或使用 CREATE STATISTICS 語句來建立統計信息。所以,在大表上進行復雜查詢時,缺乏統計信息可能會妨礙優化器肯定查詢的最佳計劃,從而影響該查詢的性能。
    在初始 DECLARE 語句後不能更改表定義。
    表變量不能在 INSERT EXEC 或 SELECT INTO 語句中使用。
    表類型聲明中的檢查約束、默認值以及計算所得的列不能調用用戶定義的函數。
    若是表變量是在 EXEC 語句或 sp_executesql 存儲過程外建立的,則不能使用 EXEC 語句或 sp_executesql 存儲過程來運行引用該表變量的動態 SQL Server 查詢。因爲表變量只能在它們的本地做用域中引用,所以 EXEC 語句和 sp_executesql 存儲過程將在表變量的做用域以外。可是,您能夠在 EXEC 語句或 sp_executesql 存儲過程內建立表變量並執行全部處理,由於這樣表變量本地做用域將位於 EXEC 語句或 sp_executesql 存儲過程當中。

問題 4:與臨時表或永久表相比,表變量的僅存在於內存中的結構保證了更好的性能,是否由於它們是在駐留在物理磁盤上的數據庫中維護的?

解答 4:表變量不是僅存在於內存中的結構。因爲表變量可能保留的數據較多,內存中容納不下,所以它必須在磁盤上有一個位置來存儲數據。與臨時表相似,表變量是在 tempdb 數據庫中建立的。若是有足夠的內存,則表變量和臨時表都在內存(數據緩存)中建立和處理。

問題 5:必須使用表變量來代替臨時表嗎?

解答 5:答案取決於如下三個因素:

    插入到表中的行數。
    從中保存查詢的從新編譯的次數。
    查詢類型及其對性能的指數和統計信息的依賴性。

在某些狀況下,可將一個具備臨時表的存儲過程拆分爲多個較小的存儲過程,以便在較小的單元上進行從新編譯。

一般狀況下,應儘可能使用表變量,除非數據量很是大而且須要重複使用表。在這種狀況下,能夠在臨時表上建立索引以提升查詢性能。可是,各類方案可能互不相同。Microsoft 建議您作一個測試,來驗證表變量對於特定的查詢或存儲過程是否比臨時表更有效。
表變量

 

 

 

x.待續

相關文章
相關標籤/搜索