SQL Server 2008 variable bit numbers - sql-server

Using SQL Server 2008.
Let's say I have 3 numbers to store.
First number is any 6bit number
Second is any 4bit
Third is also any 6bit number
For example:
declare #firstNumber_6bit tinyint = 50
declare #secondNumber_4bit tinyint = 7
declare #thirdNumber_6bit tinyint = 63
I need to store those 3 numbers in a 2 byte binary (16bit) variable. So, the binary values for the example are:
50 is 110010,
7 is 0111
63 is 111111
The 2 bytes to store are 11001001 11111111
So either one of the following lines should store those values:
declare #my3NumbersIn2Bytes binary(2) = 51711
or
declare #my3NumbersIn2Bytes binary(2) = 0xC9FF
(sorry if I'm messing the byte order in big / little endian, but that's not the point).
Storing and retriving those numbers is a trivial task with .net CLR, but I'm trying to solve this in pure T-SQL and as we know there is no bit shifting in SQL Server. I saw a lot of examples out there that use memory tables to solve problems like these, but that seems totally overkill for doing a simple bit shift... I was thinking something more like a substring for bits could do the trick. I just want to be sure there's no other way to solve this before going the overkill way.
So my question is, what is the most effective way to store those 3 numbers and recover them?
Thanks.-

declare #firstNumber_6bit tinyint = 50
declare #secondNumber_4bit tinyint = 7
declare #thirdNumber_6bit tinyint = 63
declare #my3NumbersIn2Bytes binary(2)
select #my3NumbersIn2Bytes = #firstNumber_6bit*1024 + #secondNumber_4bit *64 + #thirdNumber_6bit
--extract values
select #firstNumber_6bit = #my3NumbersIn2Bytes/1024
select #secondNumber_4bit = (#my3NumbersIn2Bytes%1024)/64
select #thirdNumber_6bit = (#my3NumbersIn2Bytes%1024)%64
select convert(varchar(max), #my3NumbersIn2Bytes, 1) -- just for display
, #firstNumber_6bit
, #secondNumber_4bit
, #thirdNumber_6bit
SQL Fiddle Demo

Related

How to split number and digit in SQL Server stored procedure

Please help me on the following calculation in a stored procedure.
My scenario is packing the magazine
Example:
Order = 6042 copies. 6042 have to be packed by 20 each, so I get 302 bundles and a balance of 2 copies.
If I divide 6042 / 20, result is 302.10. How can I store 302 and 2 in different columns that mean 302 is one column and 2 is another.
It is also differ the packing qty. Like some time 20 or 30 or 50 or 10
Also copies are also differ upto 9999 copies
Currently my stored procedure is:
UPDATE [dbo].[SOMaster_TEMP]
SET [BSTD] = [Qty] / [STDB]
UPDATE [dbo].[SOMaster_TEMP]
SET [BEND] = [Qty] / [STDB]
Qty is copies = 6042.
STDB = 20 per bundle.
BSTD is calculated as 302.10.
BEND is also 302.10.
If those are INT datatypes, then you can easily use integer division and the modulo operator:
DECLARE #Qty INT = 6042
DECLARE #PerBundle INT = 20
SELECT
NumberOfBundles = #Qty / #PerBundle,
BalanceCopies = #Qty % #PerBundle
This results in:

Check last bit in a hex in SQL

I have a SQL entry that is of type hex (varbinary) and I want to do a SELECT COUNT for all the entries that have this hex value ending in 1.
I was thinking about using CONVERT to make my hex into a char and then use WHERE my_string LIKE "%1". The thing is that varchar is capped at 8000 chars, and my hex is longer than that.
What options do I have?
Varbinary actually works with some string manipulation functions, most notably substring. So you can use eg.:
select substring(yourBinary, 1, 1);
To get the first byte of your binary column. To get the last bit then, you can use this:
select substring(yourBinary, len(yourBinary), 1) & 1;
This will give you zero if the bit is off, or one if it is on.
However, if you really only have to check at most the last 4-8 bytes, you can easily use the bitwise operators on the column directly:
select yourBinary & 1;
As a final note, this is going to be rather slow. So if you plan on doing this often, on large amounts of data, it might be better to simply create another bit column just for that, which you can index. If you're talking about at most a thousand rows or so, or if you don't care about speed, fire away :)
Check last four bits = 0001
SELECT SUM(CASE WHEN MyColumn % 16 IN (-15,1) THEN 1 END) FROM MyTable
Check last bit = 1
SELECT SUM(CASE WHEN MyColumn % 2 IN (-1,1) THEN 1 END) FROM MyTable
If you are wondering why you have to check for negative moduli, try SELECT 0x80000001 % 16
Try using this where
WHERE LEFT(my_string,1) = 1
It it's text values ending in 1 then you want the Right as opposed to the Left
WHERE RIGHT(my_string,1) = 1

How to get the count of digits after the decimal point in a float column in ms sql?

I have to count the digits after the decimal point in a database hosted by a MS Sql Server (2005 or 2008 does not matter), in order to correct some errors made by users.
I have the same problem on an Oracle database, but there things are less complicated.
Bottom line is on Oracle the select is:
select length( substr(to_char(MY_FIELD), instr(to_char(MY_FILED),'.',1,1)+1, length(to_char(MY_FILED)))) as digits_length
from MY_TABLE
where the filed My_filed is float(38).
On Ms Sql server I try to use:
select LEN(SUBSTRING(CAST(MY_FIELD AS VARCHAR), CHARINDEX('.',CAST(MY_FILED AS VARCHAR),1)+1, LEN(CAST(MY_FIELD AS VARCHAR)))) as digits_length
from MY_TABLE
The problem is that on MS Sql Server, when i cast MY_FIELD as varchar the float number is truncated by only 2 decimals and the count of the digits is wrong.
Can someone give me any hints?
Best regards.
SELECT
LEN(CAST(REVERSE(SUBSTRING(STR(MY_FIELD, 13, 11), CHARINDEX('.', STR(MY_FIELD, 13, 11)) + 1, 20)) AS decimal))
from TABLE
I have received from my friend a very simple solution which is just great. So I will post the workaround in order to help others in the same position as me.
First, make function:
create FUNCTION dbo.countDigits(#A float) RETURNS tinyint AS
BEGIN
declare #R tinyint
IF #A IS NULL
RETURN NULL
set #R = 0
while #A - str(#A, 18 + #R, #r) <> 0
begin
SET #R = #R + 1
end
RETURN #R
END
GO
Second:
select MY_FIELD,
dbo.countDigits(MY_FIELD)
from MY_TABLE
Using the function will get you the exact number of digits after the decimal point.
The first thing is to switch to using CONVERT rather than CAST. The difference is, with CONVERT, you can specify a format code. CAST uses whatever the default format code is:
When expression is float or real, style can be one of the values shown in the following table. Other values are processed as 0.
None of the formats are particularly appealing, but I think the best for you to use would be 2. So it would be:
CONVERT(varchar(25),MY_FIELD,2)
This will, unfortunately, give you the value in scientific notation and always with 16 digits e.g. 1.234567890123456e+000. To get the number of "real" digits, you need to split this number apart, work out the number of digits in the decimal portion, and offset it by the number provided in the exponent.
And, of course, insert usual caveats/warnings about trying to talk about digits when dealing with a number which has a defined binary representation. The number of "digits" of a particular float may vary depending on how it was calculated.
I'm not sure about speed. etc or the elegance of this code. it was for some ad-hoc testing to find the first decimal value . but this code could be changed to loop through all the decimals and find the last time a value was greater than zero easily.
DECLARE #NoOfDecimals int = 0
Declare #ROUNDINGPRECISION numeric(32,16) = -.00001000
select #ROUNDINGPRECISION = ABS(#ROUNDINGPRECISION)
select #ROUNDINGPRECISION = #ROUNDINGPRECISION - floor(#ROUNDINGPRECISION)
while #ROUNDINGPRECISION < 1
Begin
select #NoOfDecimals = #NoOfDecimals +1
select #ROUNDINGPRECISION = #ROUNDINGPRECISION * 10
end;
select #NoOfDecimals

datetime2 storage size

Using SQL Server 2008 R2, SP2
The docs says that datetime2 takes 6, 7 or 8 bytes depending witch precision you use
I need to store a large amount of data in binary form (concatenated values) and I love the idea of using only 6 bytes for each datetime, however when I try:
declare #_dt_p0 datetime2(0) = '2012-05-18 11:22:33'
select CONVERT(varbinary, #_dt_p0), LEN(CONVERT(varbinary, #_dt_p0))
declare #_dt_p4 datetime2(4) = '2012-05-18 11:22:33'
select CONVERT(varbinary, #_dt_p4), LEN(CONVERT(varbinary, #_dt_p4))
declare #_dt_p7 datetime2(7) = '2012-05-18 11:22:33'
select CONVERT(varbinary, #_dt_p7), LEN(CONVERT(varbinary, #_dt_p7))
It's clearly taking one extra byte, what I'm doing wrong?
I don't think I can explain why the length / datalength of a varbinary conversion is 7 instead of 6 (Mikael later found that the convert to varbinary adds the precision as an extra byte), but I don't know why you think that's a valid test anyway. I can confirm that 6 bytes are stored on the page when you are using an actual column (though null overhead for the row will be different depending on whether the column is nullable). How can I prove this?
USE tempdb;
GO
CREATE TABLE dbo.x
(
d1 DATETIME2(0) NULL,
v1 VARBINARY(32) NULL,
d2 DATETIME2(0) NOT NULL,
v2 VARBINARY(32) NOT NULL
);
declare #d datetime2(0) = '2012-05-18 11:22:33';
INSERT dbo.x(d1, v1, d2, v2)
SELECT #d, CONVERT(VARBINARY(32), #d), #d, CONVERT(VARBINARY(32), #d);
SELECT DATALENGTH(d1), DATALENGTH(v1),
DATALENGTH(d2), DATALENGTH(v2) FROM dbo.x;
Results:
6 7 6 7
So, the datetime2 columns are 6 bytes, but the varbinary columns are 7 bytes. Regardless of nullability. We can look closer by actually inspecting the page. Let's find all the pages in the heap for this table:
DBCC IND('tempdb', 'dbo.x', 0);
Partial results on my system (yours will be different):
PagePID PageType
283 10
311 1
So now let's look at Page 311:
DBCC TRACEON(3604, -1);
DBCC PAGE(2, 1, 311, 3);
And we can see that the datetime2 columns indeed occupy 6 bytes on the page:
Slot 0 Column 1 Offset 0x4 Length 6 Length (physical) 6
d1 = 2012-05-18 11:22:33
v1 = [Binary data] Slot 0 Column 2 Offset 0x19 Length 7 Length (physical) 7
v1 = 0x00f99f00b0350b
Slot 0 Column 3 Offset 0xa Length 6 Length (physical) 6
d2 = 2012-05-18 11:22:33
v2 = [Binary data] Slot 0 Column 4 Offset 0x20 Length 7 Length (physical) 7
v2 = 0x00f99f00b0350b
Good day,
firstly i want to say that this is the best question : "I don't know why you think that's a valid test anyway", since the answer is that this is not valid test!
You can read all about the issue, including the explanation of DateTime2 real stored format, and why this is mistake to examine the result of "CONVERT to binary" and assume that this is the same as the actual stored data! It is not accurate in variable-length data like varchar or nvarchar and it is not accurate in DateTime2 as well. The only way to examine and get the real stored format is examine the data itself using DBCC PAGE.
http://ariely.info/Blog/tabid/83/EntryId/162/Examine-how-DateTime2-type-stored-in-the-data-file.aspx
I hope this is useful :-)

Can I use a hash of fields instead of direct field comparison to simplify comparison of records?

I am integrating between 4 data sources:
InternalDeviceRepository
ExternalDeviceRepository
NightlyDeviceDeltas
MidDayDeviceDeltas
Changes flow into the InternalDeviceRepository from the other three sources.
All sources eventually are transformed to have the definition of
FIELDS
=============
IdentityField
Contract
ContractLevel
StartDate
EndDate
ContractStatus
Location
IdentityField is the PrimaryKey, Contract Key is a secondary Key only if a match exists, otherwise a new record needs to be created.
Currently I compare all the fields in a WHERE clause in SQL Statements and also in a number of places in SSIS packages. This creates some unclean looking SQL and SSIS packages.
I've been mulling computing a hash of ContractLevel, StartDate, EndDate, ContractStatus, and Location and adding that to each of the input tables. This would allow me to use a single value for comparison, instead of 5 separate ones each time.
I've never done this before, nor have I seen it done. Is there a reason that it should be used, or is that a cleaner way to do it?
It is a valid approach. Consider to introduce a calculated field with the hash and index on it.
You may use either CHECKSUM function or write your own hash function like this:
CREATE FUNCTION dbo.GetMyLongHash(#data VARBINARY(MAX))
RETURNS VARBINARY(MAX)
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
DECLARE #res VARBINARY(MAX) = 0x
DECLARE #position INT = 1, #len INT = DATALENGTH(#data)
WHILE 1 = 1
BEGIN
SET #res = #res + HASHBYTES('MD5', SUBSTRING(#data, #position, 8000))
SET #position = #position+8000
IF #Position > #len
BREAK
END
WHILE DATALENGTH(#res) > 16 SET #res= dbo.GetMyLongHash(#res)
RETURN #res
END
which will give you 16-byte value - you may take all the 16 bytes as Guid, or only first 8-bytes as bigint and compare it.
Adapt the function in your way - to accept string as parameter or even all the your fields instead of varbinary
BUT
be careful with strings casing, datetime formats
if using CHECKSUM - check also other fields, checksum produces dublicates
avoid using 4-byte hash result on relaively big table

Resources