讓 Execute 可以搭配 Select Into,而不再只有 Insert into

https://dotblogs.com.tw/rainmaker/2015/02/02/148355

select a.[name]
from openrowset('SQLNCLI', 'Server=localhost;Trusted_Connection=yes;',
'SET NOCOUNT ON;SET FMTONLY OFF;exec msdb..sp_help_job') AS a
where [name] in ('JOB-113720-CTLFZ_1',
				'JOB-113720-CTLFZ_2',
				'JOB-113720-CTLFZ_3')
and current_execution_status=4

 

--建立一個 LinkedServer 連自已
sp_addlinkedserver @server = 'LOCALSERVER',  @srvproduct = '', @provider = 'SQLOLEDB', @datasrc = @@servername

select a.[name]
FROM OPENQUERY(LOCALSERVER, 'EXEC(''exec msdb..sp_help_job'')')a
where [name] in ('JOB-113720-CTLFZ_1',
				'JOB-113720-CTLFZ_2',
				'JOB-113720-CTLFZ_3')
and current_execution_status=4

 

預存程序中SELECT與SET

http://fecbob.pixnet.net/blog/post/39081221-%E9%A0%90%E5%AD%98%E7%A8%8B%E5%BA%8F%E4%B8%ADselect%E8%88%87set%E5%B0%8D%E8%AE%8A%E6%95%B8%E8%B3%A6%E5%80%BC
SQL Server 中對已經定義的變數賦值的方式用兩種,分別是 SET 和 SELECT。
下表列出 SET 與 SELECT 的區別。

set

select

同時對多個變數同時賦值

不支持

支持

運算式返回多個值時

出錯

將返回的最後一個值賦給變數

運算式未傳回值

變數保持原值

[SQL]將多筆資料合併為一筆顯示(FOR XML PATH)

原文:[SQL]將多筆資料合併為一筆顯示(FOR XML PATH)

用北風資料庫舉例

step1:先讓我們看看orderID = ‘10248’的產品有哪些


SELECT * FROM [Order Details] 
where OrderID = '10248'

有這些

step2:然後我們利用for xml path把他合併顯示


SELECT cast(ProductID AS NVARCHAR ) + ',' from [Order Details] 
where OrderID = '10248'
FOR XML PATH('')

輸出是這樣,可以發現果然productID都合併了

step3:然後改成顯示所有的order的productID


SELECT OrderID,(SELECT cast(ProductID AS NVARCHAR ) + ',' from [Order Details] 
where OrderID = ord.OrderID
FOR XML PATH('')) as productIDs
from orders ord
GROUP BY orderid

輸出像是這樣,所有的order的productID,但是productID還多一個逗號

step4:最後動些手腳,輸出就完美了,成功的將多筆資料合併為一筆


SELECT m.OrderID ,left(m.productIDs,len(m.productIDs)-1) as productIDsFinal from 
(SELECT OrderID,(SELECT cast(ProductID AS NVARCHAR ) + ',' from [Order Details] 
where OrderID = ord.OrderID
FOR XML PATH('')) as productIDs
from orders ord
GROUP BY orderid) M --這個M一定要加,不知道為啥
ORDER by M.OrderID 

最後輸出:

sp_MsForEachTable使用說明

sp_MsForEachTable這指令是微軟沒有記錄在公開文件的預儲程序,在Master資料庫中的系統預儲程序中可以找到這個指令,透過這指令可以輕鬆取得每一資料表的詳細資訊,也可透過這個指令執行資料表的維護作業。

於目前常見SQL Server 版本中,透過下列指令可以取得使用者自訂資料表的相關訊息。

--SQL2000、SQL2005、SQL2008、SQL2008R2適用

EXEC sp_spaceused 'Production.ProductProductPhoto'

GO

 

sp_spaceused-v2008r2.png

如果需要列出資料庫中所有資料表的訊息,就必須於上列指令一一指定每個資料表,而無法在單次查詢中就取得所有訊息,透過sp_MsForEachTable指令可以幫助你將每個資料庫中的資料表的訊息彙總起來。

 

執行sp_MsForEachTable必須提供參數,若未提供錯誤訊息如下所示:

在SQL Server 2000中執行sp_MsForEachTable未提供參數的錯誤訊息

sp_MsForEachTable_error-v2000.png

在SQL Server 2008R2中執行sp_MsForEachTable未提供參數的錯誤訊息

sp_MsForEachTable_error-v2008r2.png

接著借由sp_MsForEachTable指令幫助我們執行指令sp_spaceused,

完整指令如下:

--SQL2000、SQL2005、SQL2008、SQL2008R2適用

EXEC sp_MsForEachTable "Sp_SpaceUsed '?'"

GO

--或

EXEC sp_MsForEachTable "Sp_SpaceUsed [?]"

GO

--或

EXEC sp_MsForEachTable 'EXEC Sp_SpaceUsed [?]'

GO

 

sp_spaceused-AdventureWorks-v2008r2.png

接著我們再來列出單一資料庫內所有資料表資料列筆數

-- SQL2000、SQL2005、SQL2008、SQL2008R2適用

USE AdventureWorks

GO

EXEC sp_MsForEachTable "SELECT '?' AS 'TableName',COUNT(*) AS 'RowCount' FROM ?"

GO

 

顯示每個資料表列數-v2008r2.png

更進階的應用是我們可以把SP_SPACEUSED產生的資料彙總儲存到一個資料表,

範例如下:

USE AdventureWorks

GO

CREATE TABLE #MyTblInfo

(

[name] nvarchar(256),--資料表名稱

[rows] int,--現有資料列數量

[reserved] varchar(18),--資料表磁碟保留空間大小

[reserved_int] int default(0),--資料表磁碟保留空間大小整數值

[data] varchar(18),--資料表實體資料使用磁碟空間大小

[data_int] int default(0),--資料表實體資料使用磁碟空間大小整數值

[index_size] varchar(18),--資料表索引使用磁碟空間大小

[index_size_int] int default(0),--資料表索引使用磁碟空間大小整數值

[unused] varchar(18),--保留給資料表未使用的磁碟空間大小

[unused_int] int default(0)--保留給資料表未使用的磁碟空間大小整數值

)

GO

--將產生的資料寫入暫存資料表#MyTblInfo

EXEC sp_MSforeachtable "INSERT INTO #MyTblInfo ([name],[rows],[reserved],[data],[index_size],[unused]) EXEC sp_spaceused '?'"

GO

--從[reserved]、[data]、[index_size]、[unused]取出數字並更新到對應的整數值欄位

UPDATE #MyTblInfo

SET [reserved_int] = CAST(SUBSTRING([reserved], 1, CHARINDEX(' ', [reserved])) AS int),

[data_int] = CAST(SUBSTRING([data], 1, CHARINDEX(' ', [data])) AS int),

[index_size_int] = CAST(SUBSTRING([index_size], 1, CHARINDEX(' ', [index_size])) AS int),

[unused_int] = CAST(SUBSTRING([unused], 1, CHARINDEX(' ', [unused])) AS int)

GO

--顯示資料

SELECT [name],[rows],[reserved],[reserved_int],

[data],[data_int],[index_size],[index_size_int],

[unused],[unused_int],

CAST(([reserved_int] + [data_int] + [index_size_int] + [unused_int]) /1024.0 AS VARCHAR) + ' MB' AS 'TABLESIZE',

([reserved_int] + [data_int] + [index_size_int] + [unused_int]) /1024.0 AS 'TABLESIZE_FLOAT'

FROM #MyTblInfo

ORDER BY data_int DESC

GO

--//TABLE SIZE

SELECT (CAST(SUM(data_int)/1024.0 AS VARCHAR) + ' MB') AS 'DATA_FILE_SIZE',

(CAST(SUM(index_size_int)/1024.0 AS VARCHAR) + ' MB') AS 'INDEX_FILE_SIZE',

(CAST(SUM(unused_int)/1024.0 AS VARCHAR) + ' MB') AS 'UNUSED_FILE_SIZE',

(CAST((SUM(data_int) + SUM(index_size_int) + SUM(unused_int))/1024.0 AS VARCHAR) + ' MB')AS 'TOTAL_FILE_SIZE'

FROM #MyTblInfo

GO

DROP TABLE #MyTblInfo

GO

 

彙總每個資料表列數-v2008r2.png

參考資料:

SQL Server Undocumented Stored Procedures sp_MSforeachtable and sp_MSforeachdb

By Gregory A. Larsen

http://www.databasejournal.com/features/mssql/article.php/3441031/SQL-Server-Undocumented-Stored-Procedures-spMSforeachtable-and-spMSforeachdb.htm

set IDENTITY_INSERT [tablename] ON注意事項

https://jonesyeh.wordpress.com/2014/01/20/set-identity_insert-tablename-on%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85/

–step 1:建立暫存資料表
CREATE TABLE [#tp](
    [user_seq] [int] IDENTITY(1,1) NOT NULL,
    [user_id] [varchar](10) NOT NULL,
    [user_name] [nvarchar](30) NOT NULL,
    [org_key] [varchar](10) NULL,
    [org_relation_seq] [int] NULL,
    [org_path] [varchar](255) NULL,
    [org_path_desc] [nvarchar](255) NULL,
    [user_type_key] [varchar](10) NULL,
    [job_key] [varchar](10) NULL,
    [level_key] [varchar](10) NULL,
    [code_group_key] [varchar](10) NOT NULL,
    [code_group_type_key] [varchar](10) NOT NULL,
    [position_date] [date] NULL,
    [begin_time] [datetime2](7) NOT NULL,
    [end_time] [datetime2](7) NULL,
  PRIMARY KEY NONCLUSTERED 
(
    [user_seq] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

–step2:從實體資料表匯入到暫存資料表
insert into [#tp]
select  * from [comm].[tb_user_dim]
–執行失敗(位於資料表 ‘#tp’ 的識別欄位其外顯值只有當使用了資料行清單且 IDENTITY_INSERT 為 ON 時才能指定。)

–step3:設定IDENTITY_INSERT=ON
set IDENTITY_INSERT [#tp] ON

–step4:從實體資料表匯入到暫存資料表
insert into [#tp]
select  * from [comm].[tb_user_dim]
–執行失敗(位於資料表 ‘#tp’ 的識別欄位其外顯值只有當使用了資料行清單且 IDENTITY_INSERT 為 ON 時才能指定。)
–從以上語法還是會失敗,原因是insert into 的資料表需要指定資料行名稱

–step5 修改insert into語法
set IDENTITY_INSERT [#tp] ON
insert into [#tp]
([user_seq]
      ,[user_id]
      ,[user_name]
      ,[org_key]
      ,[org_relation_seq]
      ,[org_path]
      ,[org_path_desc]
      ,[user_type_key]
      ,[job_key]
      ,[level_key]
      ,[code_group_key]
      ,[code_group_type_key]
      ,[position_date]
      ,[begin_time]
      ,[end_time])
select  * from [comm].[tb_user_dim]
–(862 個資料列受到影響)

drop table [#tp]

SQL 資料庫還原到到另一台後無法登入要怎麼解決

--1 .列出孤立用戶
sp_change_users_login @Action='Report'

--2.自動將用戶名對應同名登入,並添加到syslogins
sp_change_users_login'Auto_Fix','Trust_User'

--3.指定登入名
sp_change_users_login 'update_one','Trust_User','Trust_User'

參考文章:
http://blog.miniasp.com/post/2007/11/18/SQL-Backup-and-Restore-cause-ASPNET-Can-not-Login.aspx

http://www.cnblogs.com/shanyou/archive/2008/10/11/1308799.html

使用 Trigger 紀錄資料表的新增、修改、刪除的行為

原文: http://www.dotblogs.com.tw/jameswu/archive/2009/07/23/9643.aspx


備份如下:

常被要求當資料表內的紀錄有被新增修改或刪除的時候,需要同步紀錄每次資料表變動的前後資料,這時候我們可以利用 Trigger 便可做到符合這個功能的需求,而且可以適用到大部份的使用情境,又因為 Trigger 本身也可以捕捉到資料庫的 DML 的事件,如 : INSERT、UPDATE 、DELETE ,我們也能捕捉在該資料列被修改之後 (AFTER DML) 才來執行 Trigger ,所以剛好滿足我們要記錄的資料表變動的行為。

Microsoft SQL Server 的 Trigger 內會包含有兩個特殊的虛擬資料表 inserted 與 deleted ; 這兩個資料表分別會擷取引發 Trigger 的資料列修改之前、後資料內容,當然它們會儲存單筆或多筆資料列更新的資料,下表說明了每個 DML 的操作內的 inserted、deleted 會包含的資料列之內容。

資料庫 DML inserted deleted
INSERT 插入的新資料內容
UPDATE 欲更新的新資料內容 更新前的舊資料內容
DELETE 被刪除前的資料內容

當有新的資料列插入該資料表時,會觸發  INSERT 事件,會複製該新增資料列的內容到虛擬資料表 inserted ,但不會存在有虛擬資料表 deleted ;
若是某筆資料列被更新了,那麼會觸發 UPDATE 事件,並且會複製欲更新的新資料內容到虛擬資料表 inserted ,同時將舊資料列內容複製到虛擬資料表 deleted ;
刪除某筆資料列的話,將觸發 DELETE 事件,此時會將刪除前的資料列內容複製到虛擬資料表 deleted ,因為沒有資料新增或更新,所以,不會存在有虛擬資料表 inserted 。

在後面附上的面的 Trigger 內容中的 6~14 行,我便利用上述的原理來判斷觸發此 Trigger 的 DML 事件為何? 所以,我們需要對於資料庫的運作原理是有進一步了解,才有辦法靈活運用的。

上面的一堆資料庫的運作原理,主要是我想在同一個 Trigger 中同時使用多個 AFTER DML 事件,因為我不想維護同一類的 Trigger 內容,當然,也得看使用的情境,如果你的 AFTER INSERT 還包含其他的運算邏輯,那建議還是將內容分開兩份 Trigger 比較恰當。

再者,之前看過很多範例 (包含我以前自己寫的) ,若要紀錄所有的歷史紀錄大部分是新增跟原始資料表相同的欄位 (Schema) ,頂多在後面新增一個資料欄位來記錄該筆是,update、insert 或 delete,這樣雖然可行,但我想到萬一原始資料表的欄位有新增、修改或刪除,那麼這個歷史資料表也要同步更新,而我們又有可能忘了同步更新,而造成某些欄位的資料內容沒有被記錄到,我考量到這個問題,且想提高此 Trigger 的重複利用性,因此,我想將該資料列內的資料內容全部存成 XML 資料格式,將此 XML 資料放在一個 XML 資料型態的欄位即可,當然這有個缺點是,當要調閱某筆歷史資料時,你可能沒辦法很簡單的就查詢到該筆記錄的內容,可能需要再使用其他方法來解析出 XML 的內容才行,這部分在稍後我也會提供一個方法技巧來做查詢的。

要將資料列轉換成 XML 格式的資料其實不難,只要利用 SQL Server 2000 以後提供的  FOR XML ,便可輕易的將資料表資料轉換成需要的任何 XML 內容格式了 ,在下方的 Trigger 內容中的 17 與 20 行便有使用到此功能。

但為了對 XML 內容的查詢方便,我選擇了使用 FOR XML RAW, ELEMENTS, ROOT 來產出我要的格式。

下方是我實作的一個範例,首先要建立一個新的 LOG 資料表來存放紀錄 :

CREATE TABLE [dbo].[LOG](
     [id] [uniqueidentifier] NOT NULL,
     [dbname] [varchar](25) NULL,
     [recoder] [xml] NULL,
     [istype] [tinyint] NULL,
     [updatedate] [datetime] NULL,
  CONSTRAINT [PK_LOG] PRIMARY KEY CLUSTERED([id] ASC)
  WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
 ) ON [PRIMARY]
 GO
 ALTER TABLE [dbo].[LOG] ADD  CONSTRAINT [DF_LOG_id]  DEFAULT (newid()) FOR [id]
 GO
 ALTER TABLE [dbo].[LOG] ADD  CONSTRAINT [DF_LOG_updatedate]  DEFAULT (getdate()) FOR [updatedate]
 GO

再來我們使用在 NorthWind 資料庫的 Customers 資料表內建立一個新的 Trigger ,此 Trigger 會在 Customers 資料表有被修改後觸發 :

CREATE TRIGGER [dbo].[trCustomers_UPDATE_INSERT_DELETE] ON [dbo].[Customers] AFTER UPDATE,INSERT,DELETE
AS
BEGIN
   DECLARE @record XML
   DECLARE @IsType TINYINT
   SET @IsType=''
   IF EXISTS(SELECT 1 FROM inserted) AND NOT EXISTS(SELECT 1 FROM deleted)
       SET @IsType = 1    --Insert

   IF EXISTS(SELECT 1 FROM inserted) AND EXISTS(SELECT 1 FROM deleted)
       SET @IsType = 2    --Update

   IF NOT EXISTS(SELECT 1 FROM inserted) AND EXISTS(SELECT 1 FROM deleted)
       SET @IsType = 3    --Delete

   --只想記錄 UPDATE 事件更新資料前的舊資料
   IF (@IsType = 1)
       SET @record=(SELECT * FROM inserted FOR XML RAW('Customers'), ELEMENTS,ROOT)
   ELSE
       SET @record=(SELECT * FROM deleted FOR XML RAW('Customers'), ELEMENTS,ROOT)

   IF (@IsType <>'')
   BEGIN
       INSERT INTO [log]([dbname],[recoder],[istype])VALUES('Customers',@record  ,@IsType)
   END
END

上面的就已經做到我們要的儲存修改紀錄的功能了,不過美中不足的是這樣的資料結構不是那麼的方便查詢,必須要再利用 OPENXML 將 XML 資料取出來。

假設要追蹤某一筆記錄,你可以先使用 SQL 指令查詢到該筆記錄後,將 Log 資料表的 recoder 欄位的 XML 資料丟給 OPENXML 處理即可,如下方法 : (其實這方法可以應用在 Master and Detail view 情境中, 也就是你將之前的紀錄先以類似 GridView 的列表顯示所有紀錄後, 再使用 DetailsView 等將詳細的紀錄顯示出來)

DECLARE @xmldoc XML
SELECT @xmldoc = recoder FROM Log WHERE [id]='AF35C6FF-E36E-41D5-9158-911931ABDB19'

DECLARE @hDoc AS INT
EXEC sp_xml_preparedocument @hDoc OUTPUT, @xmldoc
SELECT *
FROM OPENXML(@hDoc, '/root/Customers', 2)
WITH ([CustomerID] [nchar](5),
    [CompanyName] [nvarchar](40),
    [ContactName] [nvarchar](30),
    [ContactTitle] [nvarchar](30),
    [Address] [nvarchar](60),
    [City] [nvarchar](15),
    [Region] [nvarchar](15),
    [PostalCode] [nvarchar](10),
    [Country] [nvarchar](15),
    [Phone] [nvarchar](24),
    [Fax] [nvarchar](24))

EXEC sp_xml_removedocument @hDoc