Monday, 17 August 2015

MSSQL - How to replicate to readonly database?

First, I have to apologize, title of article lies - it is unfortunately impossible to set up replication into the READ-ONLY database.
One of the reasons why we want to have the target database read-only, is the need to avoid direct changes of data in it.
For this purpose, we can use another method - Triggers, which prohibit any changes except changes from the replication.

There such trigger:
  1. USE MySourceDB
  2. GO
  3. ALTER TRIGGER dbo.trig_mytable_CheckContext4Changes ON dbo.MyTable
  4. FOR INSERT, UPDATE, DELETE
  5. NOT FOR REPLICATION
  6. AS
  7. IF NOT (@@SERVERNAME = 'MySourceServer' AND DB_NAME = 'MySourceDB')
  8. ROLLBACK;

Very important is this expression: NOT FOR REPLICATION.
On the target database at the same time we have to
- prohibit all direct changes (INSERT/UPDATE/DELETE)
- but allow data changes which are comming from replication
Expression NOT FOR REPLICATION deactivates trigger for changes from replication.

Monday, 10 August 2015

MSSQL - Immediately PRINT big messages - more then 8000 bytes (4000 unicode characters)

Transact-SQL function PRINT has two disadvatages:
- is limited for 8000 bytes - 8000 VARCHAR characters or 4000 NVARCHAR (unicode) characters
- does not send messages immediately to the client, usually user have to wait until the procedure is complete before seeing messages

We can use alternate method - the RAISERROR function, this function can send messages immediately, but is limited for 2047 characters.

There the stored procedure which send any messages immediately to client, also with length more then 2047 (or 8000) characters:
  1. CREATE PROCEDURE [dbo].[sp_print_big_message]
  2. @message NVARCHAR(MAX)
  3. AS BEGIN
  4.  
  5. -- SET NOCOUNT ON to prevent extra log messages
  6. SET NOCOUNT ON;
  7.  
  8. DECLARE
  9. @CRLF CHAR(2) = CHAR(13)+CHAR(10),
  10. @message_len INT = LEN(@message),
  11. @i INT,
  12. @part NVARCHAR(2000),
  13. @part_len INT;
  14.  
  15. IF @message_len <= 2000 BEGIN
  16. -- Message ist enough short
  17. RAISERROR (@message, 0,1) WITH NOWAIT;
  18. END ELSE BEGIN
  19. -- Message is too long
  20. SET @i = 1;
  21. WHILE @i < LEN(@message) BEGIN
  22. -- Split to parts end send them to client immediately
  23. SET @part = SUBSTRING(@message, @i, 2000);
  24. SET @part_len = 2000 - CHARINDEX(CHAR(10) + CHAR(13), REVERSE(@part)) - 1;
  25. SET @part = CASE @i
  26. WHEN 1
  27. THEN ''
  28. ELSE '/* CRLF ' + CAST(@i AS VARCHAR(20)) + ':'
  29. + CAST(@part_len AS VARCHAR(20)) + ' */' + @CRLF
  30. END
  31. + REPLACE(SUBSTRING(@message, @i, @part_len), '%', '%%');
  32. RAISERROR (@part, 0,1) WITH NOWAIT;
  33. SET @i = @i + @part_len + 2;
  34. END;
  35. END;
  36.  
  37. END;

Usage:
  1. -- Declare long message
  2. DECLARE @LongMessage NVARCHAR(MAX) = '';
  3. -- Fill message with test data
  4. DECLARE @i INT = 1;
  5. WHILE @i < 200 BEGIN
  6. SET @LongMessage = @LongMessage
  7. + CASE @i WHEN 1 THEN '' ELSE CHAR(13) + CHAR(10) END
  8. + CAST(@i AS VARCHAR(10))
  9. + '. Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
  10. SET @i = @i + 1;
  11. END;
  12.  
  13. -- Display length of generated message
  14. DECLARE @len INT = LEN(@LongMessage);
  15. RAISERROR('Message length: %i', 0, 1, @len);
  16.  
  17. -- Use SP to print long message
  18. EXEC sp_print_big_message @LongMessage;

Monday, 3 August 2015

MSSQL - Convert speсial types to strings or HTML:
  • MONEY to string,
  • DATETIME to string,
  • GUID or UNIQUEIDENTIFIER to string,
  • VARBINARY or BINARY to string,
  • XML to string,
  • TIMESTAMP to string

Most of datatypes is relative simple convert to a string.
For this we can use the CONVERT() or even the CAST() function:
  1. SELECT int_as_string = CONVERT(VARCHAR(20), int_field) FROM MyTable;
  2.  
  3. SELECT int_as_string = CAST(int_field AS VARCHAR(20)) FROM MyTable;

But for some other datatypes convert is not so simple.
For example for MONEY, DATETIME, UNIQUEIDENTIFIER, BINARY, XML and TIMESTAMP datatypes.

There the script which shows how to convert such datatypes to strings:
  1. USE tempdb;
  2. GO
  3.  
  4. -- Drop test table if exists
  5. IF OBJECT_ID('test_converts') IS NOT NULL
  6. DROP TABLE test_converts;
  7. GO
  8.  
  9. -- Create test table
  10. -- The technique to generate random money value I have used from there:
  11. -- http://yabele.blogspot.de/2013/08/mssql-random-number-generator-with.html
  12. CREATE TABLE test_converts
  13. (
  14. mon MONEY NOT NULL DEFAULT RAND(CHECKSUM(NEWID())) * 1000,
  15. dt DATETIME NOT NULL DEFAULT GETDATE(),
  16. guid UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID(),
  17. bin VARBINARY(MAX) NOT NULL DEFAULT NEWID(),
  18. x XML NOT NULL,
  19. ts TIMESTAMP
  20. );
  21. GO
  22.  
  23. -- Fill test table with 3 records
  24. -- For all columns except "x" (xml) the default values will be used
  25. INSERT INTO test_converts (x)
  26. VALUES
  27. ('<a b="c"><d/></a>'),
  28. ('<e f="g"><h/></e>'),
  29. ('<i j="k"><l/></i>');
  30.  
  31. -- Show original values
  32. SELECT * FROM test_converts;
  33.  
  34. -- Convert values to string
  35. SELECT
  36. -- Third parameter (0) is default and generates result text
  37. -- in "0.00" format
  38. money_as_string = CONVERT(VARCHAR(20), mon, 0),
  39. -- Third parameter (121) generates result text
  40. -- in ODBC-canonical format: "yyyy-MM-dd HH:mm:ss.sss"
  41. datetime_as_string = CONVERT(VARCHAR(30), dt, 121),
  42. guid_as_string = CONVERT(VARCHAR(36), guid),
  43. -- Third parameter (1) generates result text
  44. -- in uppercase form with "0x" prefix
  45. binary_as_string = CONVERT(VARCHAR(MAX), bin, 1),
  46. xml_as_string = CONVERT(VARCHAR(MAX), x),
  47. timestamp_as_string = CONVERT(VARCHAR(MAX), CONVERT(VARBINARY(MAX), ts), 1)
  48. FROM
  49. test_converts;

After execution in SSMS (SQL Server Management Studio) we will have these two resultsets:



These techniques could be used for example to convert table-content to HTML.
In case you need convert values to HTML-string some converted string values will be necessary to HTML-encode:
  1. -- Convert values to HTML-string
  2. SELECT
  3. -- Third parameter (0) is default and generates result text
  4. -- in "0.00" format
  5. money_as_HTML = CONVERT(VARCHAR(20), mon, 0),
  6. -- Third parameter (121) generates result text
  7. -- in ODBC-canonical format: "yyyy-MM-dd HH:mm:ss.sss"
  8. datetime_as_HTML = CONVERT(VARCHAR(30), dt, 121),
  9. guid_as_HTML = CONVERT(VARCHAR(36), guid),
  10. -- Third parameter (1) generates result text
  11. -- in uppercase form with "0x" prefix
  12. binary_as_HTML = CONVERT(VARCHAR(MAX), bin, 1),
  13. -- Convert XML-string to HTML-encoded string
  14. xml_as_HTML = REPLACE(REPLACE(REPLACE(
  15. CONVERT(VARCHAR(MAX), x)
  16. , '&','&amp;'), '<', '&lt;'), '>', '&gt;'),
  17. timestamp_as_HTML = CONVERT(VARCHAR(MAX), CONVERT(VARBINARY(MAX), ts), 1)
  18. FROM
  19. test_converts;

Correspondent resultset in SSMS:



See also:
MSSQL - The random number generator with a random initialization (random seed)