OPENXML移植

原有SQL存储过程

/*------------------------------------
-- 用途:成批插入虚拟身份好友IP记录
-- 项目名称:中心6数据代理6.1.8
-- 说明:
-- 时间:2010-04-19 16:13:05
-- 编写者:
--------------------------------------
-- 修改记录:
-- 编号 修改时间 修改人 修改原因 修改标注
------------------------------------*/
CREATE PROCEDURE [dbo].[DataProxy_NIDC_log_FriendIP_BatchInsert]
(
	@xml varchar(max) = NULL -- 数据
)
AS
	SET NOCOUNT ON;
	DECLARE @TableName nvarchar(100)
	DECLARE @CreateTableSQL nvarchar(max)
	DECLARE @InsertSQL nvarchar(max)
	DECLARE @UpdateSQL nvarchar(max)
	DECLARE @Suffix nvarchar(100)

	DECLARE @docHandle int

CREATE TABLE #TEMP (
	[MyCyberID] [bigint] NULL,
	[FriendCyberID] [bigint] NULL,
	[IP] nvarchar(50) NULL ,
	[ClientID] UNIQUEIDENTIFIER NULL DEFAULT NEWID(),
	[Time] [datetime] NULL DEFAULT(GETDATE()),
	[UnitID] [int] NULL DEFAULT (0),
	[ClientNo] [int] NULL DEFAULT (0),
	[CyberType] [int] NULL,
	[FriendCyberCode] [nvarchar] (128) NULL
)

EXEC sp_xml_preparedocument @docHandle OUTPUT, @Xml
INSERT #TEMP
SELECT
	[MyCyberID]
	,[FriendCyberID]
	,[IP]
	,[ClientID]
	,MAX([Time])
	,[UnitID]
	,MAX([ClientNo])
	,MAX([CyberType])
	,MAX([FriendCyberCode])
FROM OPENXML(@docHandle, N'/ROOT/ROW')
	WITH (
	[MyCyberID] [bigint]
	,[FriendCyberID] [bigint]
	,[IP] nvarchar(50)
	,[ClientID] UNIQUEIDENTIFIER
	,[Time] [datetime]
	,[UnitID] [int]
	,[ClientNo] [int]
	,[CyberType] [int]
	,[FriendCyberCode] [nvarchar] (128)
	)
WHERE [MyCyberID] <> 0 AND [FriendCyberID] <> 0
AND ClientID <> '00000000-0000-0000-0000-000000000000' AND[IP] <> ''
GROUP BY
	[MyCyberID]
	,[FriendCyberID]
	,[IP]
	,[ClientID]
	,[UnitID]
EXEC sp_xml_removedocument @docHandle

SELECT
	@Suffix = CONVERT(VARCHAR(8),Time,112)
	,@TableName = N'[NIDCLOG' + LEFT(@Suffix,6) + '].[dbo].[NIDC_log_FriendIP' + @Suffix + ']'
	,@CreateTableSQL = ISNULL(@CreateTableSQL ,N'
	DECLARE @TableName nvarchar(200);') + N'
	EXEC [dbo].[DataProxy_NIDC_log_FriendIP_CreateTable] @TableName OUTPUT ,''' + @Suffix + ''';'
	,@UpdateSQL = ISNULL(@UpdateSQL,N'') + N'
	UPDATE D SET
		CyberType = T.CyberType
		,[FriendCyberCode] = T.[FriendCyberCode]
		,[ClientNo] = T.[ClientNo]
	FROM ' + @TableName + ' D
	,#TEMP T
	WHERE D.UnitID = T.UnitID
	AND D.ClientID = T.ClientID
	AND D.[MyCyberID] = T.[MyCyberID]
	AND D.[FriendCyberID] = T.[FriendCyberID]
	AND D.[IP] = T.[IP]
	AND T.Time >= ''' + @Suffix + '''
	AND T.Time < DATEADD(DAY,1,''' + @Suffix + ''')
	'
	,@InsertSQL = ISNULL(@InsertSQL,N'') + N'
	INSERT ' + @TableName + ' (
		[MyCyberID]
		,[FriendCyberID]
		,[IP]
		,[ClientID]
		,[Time]
		,[UnitID]
		,[ClientNo]
		,[CyberType]
		,[FriendCyberCode]
		)
	SELECT
		T.[MyCyberID]
		,T.[FriendCyberID]
		,T.[IP]
		,T.[ClientID]
		,T.[Time]
		,T.[UnitID]
		,T.[ClientNo]
		,T.[CyberType]
		,T.[FriendCyberCode]
	FROM #TEMP T
	WHERE T.Time >= ''' + @Suffix + '''
	AND T.Time < DATEADD(DAY,1,''' + @Suffix + ''')
	AND NOT EXISTS (
		SELECT 1
		FROM ' + @TableName + ' D --WITH(NOLOCK) --001
		WHERE D.UnitID = T.UnitID
		AND D.ClientID = T.ClientID
		AND D.[MyCyberID] = T.[MyCyberID]
		AND D.[FriendCyberID] = T.[FriendCyberID]
		AND D.[IP] = T.[IP]
		)
	'
FROM #TEMP
GROUP BY CONVERT(VARCHAR(8),Time,112)


-- 检查表是否存在,不存在则创建
IF ISNULL(@CreateTableSQL,N'') <> N''
	EXEC (@CreateTableSQL);

-- 更新顾客虚拟身份关系记录表
/*
IF ISNULL(@UpdateSQL,N'') <> N''
	EXEC (@UpdateSQL);
*/
-- 插入顾客虚拟身份关系记录表
IF ISNULL(@InsertSQL,N'') <> N''
	EXEC (@InsertSQL);

此存储过程的主要功能逻辑如下:

  1. 创建一个临时表#TEMP;

  2. 解析要导入的XML文件,XML中有多条record数据;

  3. 导入XML数据记录到临时表#TEMP中,只有满足条件的记录才导入到TEMP表中。

  4. 创建日志分库分表。获取当前系统时间,用NIDC_log_FriendIP + 日期得到表名称,用NIDCLOG+日期的年月 来得到数据库名,注意数据库不能自动创建,需要预先创建好。生成建表语句@CreateTableSQL。

  5. 调用存储过程[dbo].[DataProxy_NIDC_log_FriendIP_CreateTable] 来自动创建表。

  6. 从临时表TEMP中查询满足条件的记录,生成 INSERT 语句@InsertSQL。

  7. 执行@CreateTableSQL 和 @InsertSQL 语句。

  8. 到此,XML文件中的记录被插入到日志分库分表中

移植方案

  1. 由于TDS不支持XML导入,但是UDBTX支持XML的导入,因此XML解析和插入到临时表的工作可以放到UDBTX的函数中,在SQL Server存储过程中调用UDBTX封装的导入XML数据的函数来完成XML到表的处理。

  2. 由于SQL Server存储过程本身也有大量的代码,这部分代码UDB-TDS本身还是大部分支持的,所以这部分不需要全部改为UDBTX的函数来实现,即尽量保留原有的存储过程代码。

移植后的代码

  1. UDBTX 函数部分

    CREATE OR REPLACE FUNCTION nidc_dbo.importXmlToTable_DataProxy_NIDC_log_FriendIP_BatchInsert(xml_path

    TEXT)

    RETURNS INTEGER AS $$

    DECLARE

    data xml;
    records XML[];
    record XML;
    
    MyCyberID bigint;
    FriendCyberID bigint;
    IP varchar(50);
    ClientID varchar(64);
    TimeS TIMESTAMP;
    UnitID int;
    ClientNo int;
    CyberType int;
    FriendCyberCode varchar;
    BEGIN
    
    -- Load the XML file
    --data := pg_catalog.pg_read_binary_file(xml_path, 0, 10000000); 
    -- Adjust the size accordingly
    data := XMLPARSE(DOCUMENT convert_from(pg_catalog.pg_read_binary_file(xml_path), 'UTF8')); 
    -- Adjust the size accordingly
    
    -- Extract records from the XML data
    records := xpath('/dataset/record', data); -- Assuming records are wrapped in <record> tags under a
    <root> element
    
    -- Loop through each record
    FOREACH record IN ARRAY records LOOP
    	-- Extract the ID value using xpath
    	MyCyberID := (xpath('//MyCyberID/text()', record))[1]::text::bigint;
    	FriendCyberID := (xpath('//FriendCyberID/text()', record))[1]::text::bigint;
    	IP := (xpath('//IP/text()', record))[1]::text;
    	ClientID := (xpath('//ClientID/text()', record))[1]::text;
    	TimeS := (xpath('//Time/text()', record))[1]::text::datetime;
    	UnitID := (xpath('//UnitID/text()', record))[1]::text::int;
    	ClientNo := (xpath('//ClientNo/text()', record))[1]::text::int;
    	CyberType := (xpath('//CyberType/text()', record))[1]::text::int;
    	FriendCyberCode := (xpath('//FriendCyberCode/text()', record))[1]::text;
    
    -- Check if ID is greater than 100
    
    IF MyCyberID <> 0 AND FriendCyberID <> 0 AND ClientID <> '00000000-0000-0000-0000-000000000000' AND IP <> '' THEN
    
    	-- Build the INSERT query for the record
    	INSERT INTO TEMP (MyCyberID, FriendCyberID, IP, ClientID, Time, UnitID, ClientNo, CyberType,FriendCyberCode)
    		VALUES (MyCyberID, FriendCyberID, IP, ClientID, TimeS, UnitID, ClientNo, CyberType,FriendCyberCode);
    
    	END IF;
    END LOOP;
    
    RETURN 0;
    END;
    $$ LANGUAGE plpgsql;
    

    ​ 上述函数的功能是把指定的XML格式的数据保存到临时表TEMP中,需要注意的是临时表在SQL Server存储过程中创建和删除。

    ​ 此函数不太容易写成通用的函数,因为每个存储过程入库时有各种条件判断,根据客户现有的情况,针对每个OPENXML的存储过程定制写一个,函数的逻辑基本相同。

    ​ 上述函数中用到了pg_read_binary_file 系统函数,此函数需要额外授权。授权命令如下(注意不是授权给 sa用户,而是nidc_dbo):

    GRANT EXECUTE ON FUNCTION pg_read_binary_file(text,bigint,bigint,boolean) TO nidc_dbo;
    
    GRANT EXECUTE ON FUNCTION pg_read_binary_file(text,bigint,bigint) TO nidc_dbo;
    
    GRANT EXECUTE ON FUNCTION pg_read_binary_file(text) TO nidc_dbo;
    
  2. 存储过程部分

    CREATE PROCEDURE [dbo].[DataProxy_NIDC_log_FriendIP_BatchInsert]
    (
    	@xml varchar(max) = NULL -- 数据
    )
    AS
    	SET NOCOUNT ON;
    	DECLARE @TableName nvarchar(100)
    	DECLARE @CreateTableSQL nvarchar(max)
    	DECLARE @InsertSQL nvarchar(max)
    	DECLARE @UpdateSQL nvarchar(max)
    	DECLARE @Suffix nvarchar(100)
    
    	DROP TABLE IF EXISTS TEMP;
    
    	CREATE TABLE TEMP (
    		[MyCyberID] [bigint] NULL,
    		[FriendCyberID] [bigint] NULL,
    		[IP] nvarchar(50) NULL ,
    		[ClientID] UNIQUEIDENTIFIER NULL DEFAULT NEWID(),
    		[Time] [datetime] NULL DEFAULT(GETDATE()),
    		[UnitID] [int] NULL DEFAULT (0),
    		[ClientNo] [int] NULL DEFAULT (0),
    		[CyberType] [int] NULL,
    		[FriendCyberCode] [nvarchar] (128) NULL
    	);
    
    	SELECT [dbo].[importXmlToTable_DataProxy_NIDC_log_FriendIP_BatchInsert](@xml);
    
    	SELECT @Suffix = CONVERT(VARCHAR(8),GETDATE(),112)
    
    	SELECT @TableName = N'[NIDCLOG' + LEFT(@Suffix,6) + '].[dbo].[NIDC_log_FriendIP' + @Suffix + ']'
    
    	SELECT
    		-- @Suffix = CONVERT(VARCHAR(8),Time,112)
    		--,@TableName = N'[NIDCLOG' + LEFT(@Suffix,6) + '].[dbo].[NIDC_log_FriendIP' + @Suffix + ']'
    		--,@CreateTableSQL = ISNULL(@CreateTableSQL ,N'
    		@CreateTableSQL = ISNULL(@CreateTableSQL ,N'
    		DECLARE @TableName nvarchar(200);') + N'
    		EXEC [dbo].[DataProxy_NIDC_log_FriendIP_CreateTable] @TableName OUTPUT ,''' + @Suffix + ''';'
    		,@UpdateSQL = ISNULL(@UpdateSQL,N'') + N'
    		UPDATE D SET
    			CyberType = T.CyberType
    			,[FriendCyberCode] = T.[FriendCyberCode]
    			,[ClientNo] = T.[ClientNo]
    		FROM ' + @TableName + ' D
    		,TEMP T
    		WHERE D.UnitID = T.UnitID
    		AND D.ClientID = T.ClientID
    		AND D.[MyCyberID] = T.[MyCyberID]
    		AND D.[FriendCyberID] = T.[FriendCyberID]
    		AND D.[IP] = T.[IP]
    		AND T.Time >= @Suffix
    		AND T.Time < DATEADD(DAY,1, @Suffix)
    		'
    		,@InsertSQL = ISNULL(@InsertSQL,N'') + N'
    		INSERT ' + @TableName + ' (
    			[MyCyberID]
    			,[FriendCyberID]
    			,[IP]
    			,[ClientID]
    			,[Time]
    			,[UnitID]
    			,[ClientNo]
    			,[CyberType]
    			,[FriendCyberCode]
    			)
    		SELECT
    			T.[MyCyberID]
    			,T.[FriendCyberID]
    			,T.[IP]
    			,T.[ClientID]
    			,T.[Time]
    			,T.[UnitID]
    			,T.[ClientNo]
    			,T.[CyberType]
    			,T.[FriendCyberCode]
    		FROM TEMP T
    		WHERE T.Time >= ''' + @Suffix + '''
    		AND T.Time < DATEADD(DAY,1,''' + @Suffix + ''')
    		AND NOT EXISTS (
    			SELECT 1
    			FROM ' + @TableName + ' D --WITH(NOLOCK) --001
    			WHERE D.UnitID = T.UnitID
    			AND D.ClientID = T.ClientID
    			AND D.[MyCyberID] = T.[MyCyberID]
    			AND D.[FriendCyberID] = T.[FriendCyberID]
    			AND D.[IP] = T.[IP]
    			)
    		'
    	FROM TEMP
    	--GROUP BY CONVERT(VARCHAR(8),Time,112)
    	GROUP BY CONVERT(VARCHAR(8),GETDATE(),112)
    
    	-- 检查表是否存在,不存在则创建
    	IF ISNULL(@CreateTableSQL,N'') <> N''
    		EXEC (@CreateTableSQL);
    
    	-- 更新顾客虚拟身份关系记录表
    	/*
    	IF ISNULL(@UpdateSQL,N'') <> N''
    		EXEC (@UpdateSQL);
    	*/
    	-- 插入顾客虚拟身份关系记录表
    	IF ISNULL(@InsertSQL,N'') <> N''
    		EXEC (@InsertSQL);
    

    上述存储过程是在原有代码的基础上做了稍微做了一些修改而来:

    1. 创建临时表:由于SQL Server的临时表表名必须是#开头,比如#TEMP,而UDBTX不允许表名中包含特殊字符。因此无法与SQL Server兼容。所以在存储过程中,只能使用普通表,在存储过程执行完成时显式的删除表。

    2. 删除存储过程中原有的处理XML,并插入到临时表TEMP的代码。改为调用UDBTX的函数SELECT [dbo].importXmlToTable_DataProxy_NIDC_log_FriendIP_BatchInsert;

    3. 修改构造@CreateTableSQL、@UpdateSQL 和 @InsertSQL 语句的代码。此部分代码的执行结果与SQL Server不一致,应该是运算的优先级问题,原有的代码运行结果@Suffix、@TableName为NULL,解决方法是:把拼接语句中用到的变量放到前面单独的语句中,先进行赋值。如下:

      SELECT @Suffix = CONVERT(VARCHAR(8),GETDATE(),112)

      SELECT @TableName = N’[NIDCLOG’ + LEFT(@Suffix,6) + ‘].[dbo].[NIDC_log_FriendIP’ + @Suffix + ‘]’

    关于此类问题,可能需要对所有的存储过程进行测试验证,存在问题的都需要调整。

执行结果

  1. XML执行样例

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    	<dataset>
    		<record>
    			<MyCyberID>2</MyCyberID>
    			<FriendCyberID>2</FriendCyberID>
    			<IP>192.168.1.10</IP>
    			<ClientID>A4156412CB6A4F3FA68C88941EDB2B1C</ClientID>
    			<Time>2024-03-29 10:10:10</Time>
    			<UnitID>2</UnitID>
    			<ClientNo>2</ClientNo>
    			<CyberType>2</CyberType>
    			<FriendCyberCode>Friend cyber code</FriendCyberCode>
    		</record>
    		<record>
    			<MyCyberID>3</MyCyberID>
    			<FriendCyberID>3</FriendCyberID>
    			<IP>192.168.1.10</IP>
    			<ClientID>A4156412CB6A4F3FA68C88941EDB2B1C</ClientID>
    			<Time>2024-03-29 10:10:10</Time>
    			<UnitID>3</UnitID>
    			<ClientNo>3</ClientNo>
    			<CyberType>3</CyberType>
    			<FriendCyberCode>Friend cyber code</FriendCyberCode>
    		</record>
    	</dataset>
    
  2. 插入到TEMP的数据

    (略)

  3. 插入到分库分表中的数据

    (略)

总结

  1. OPENXML 相关的存储过程,功能都差不多,基本上按照上述思路可以解决。按照此方案进行移植,可以保持存储过程的接口不变,现有的应用软件也不修要修改。

  2. 在移植过程中,也发现UDB-TDS存在语句执行结果与原有的语句执行结果不一致的问题,所以所有存储过程都需要进行测试验证,不一致的地方要修改。