Sql server query using function and view is slower - sql-server

I have a table with a xml column named Data:
CREATE TABLE [dbo].[Users](
[UserId] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [nvarchar](max) NOT NULL,
[LastName] [nvarchar](max) NOT NULL,
[Email] [nvarchar](250) NOT NULL,
[Password] [nvarchar](max) NULL,
[UserName] [nvarchar](250) NOT NULL,
[LanguageId] [int] NOT NULL,
[Data] [xml] NULL,
[IsDeleted] [bit] NOT NULL,...
In the Data column there's this xml
<data>
<RRN>...</RRN>
<DateOfBirth>...</DateOfBirth>
<Gender>...</Gender>
</data>
Now, executing this query:
SELECT UserId FROM Users
WHERE data.value('(/data/RRN)[1]', 'nvarchar(max)') = #RRN
after clearing the cache takes (if I execute it a couple of times after each other) 910, 739, 630, 635, ... ms.
Now, a db specialist told me that adding a function, a view and changing the query would make it much more faster to search a user with a given RRN. But, instead, these are the results when I execute with the changes from the db specialist: 2584, 2342, 2322, 2383, ...
This is the added function:
CREATE FUNCTION dbo.fn_Users_RRN(#data xml)
RETURNS nvarchar(100)
WITH SCHEMABINDING
AS
BEGIN
RETURN #data.value('(/data/RRN)[1]', 'varchar(max)');
END;
The added view:
CREATE VIEW vwi_Users
WITH SCHEMABINDING
AS
SELECT UserId, dbo.fn_Users_RRN(Data) AS RRN from dbo.Users
Indexes:
CREATE UNIQUE CLUSTERED INDEX cx_vwi_Users ON vwi_Users(UserId)
CREATE NONCLUSTERED INDEX cx_vwi_Users__RRN ON vwi_Users(RRN)
And then the changed query:
SELECT UserId FROM Users
WHERE dbo.fn_Users_RRN(Data) = #RRN
Why is the solution with a function and a view going slower?

the point of the view was to pre-compute the XML value into a regular column. To then use that precomputed value in the index on the view, shouldn't you actually query the view?
SELECT
UserId
FROM vwi_Users
WHERE RRN= '59021626919-61861855-S_FA1E11'
also, make the index this:
CREATE NONCLUSTERED INDEX cx_vwi_Users__RRN ON vwi_Users(RRN) INCLUDE (UserId)
it is called a covering index, since all columns needed in the query are in the index.

Have you tried to add that function result to your table (not a view) as a persisted, computed column??
ALTER TABLE dbo.Users
ADD dbo.fn_Users_RRN(Data) PERSISTED
Doing so will extract that piece of information from the XML, store it in a computed, always up-to-date column, and the persisted flag makes it physically stored along side the other columns in your table.
If this works (the PERSISTED flag is a bit iffy in terms of all the limitations it has), then you should see nearly the same performance as querying any other string field on your table... and if the computed column is PERSISTED, you can even put an index on it if you feel the need for that.

Check the query execution plan and confirm whether or not the new query is even using the view. If the query doesn't use the view, that's the problem.
How does this query fair?
SELECT UserId FROM vwi_Users
WHERE RRN = '59021626919-61861855-S_FA1E11'
I see you're freely mixing nvarchar and varchar. Don't do that! It can cause full index conversions (eeeeevil).

Scalar functions tend to perform very poorly in SQL Server. I'm not sure why if you make it a persisted computed column and index it, it doesn't have identical performance to a normal indexed-column, but it may be due to the UDF being called even though you think it's no longer needed to be called once the data is computed.
I think you know this from another answer, but your final query is wrongly calling the scalar UDF on every row (defeating the point of persisting the computation):
SELECT UserId FROM Users
WHERE dbo.fn_Users_RRN(Data) = #RRN
It should be
SELECT UserId FROM vwi_Users
WHERE RNN = #RRN

Related

Dynamic SQL to execute large number of rows from a table

I have a table with a very large number of rows which I wish to execute via dynamic SQL. They are basically existence checks and insert statements and I want to migrate data from one production database to another - we are merging transactional data. I am trying to find the optimal way to execute the rows.
I've been finding the coalesce method for appending all the rows to one another to not be efficient for this particularly when the number of rows executed at a time is greater than ~100.
Assume the structure of the source table is something arbitrary like this:
CREATE TABLE [dbo].[MyTable]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[DataField1] [int] NOT NULL,
[FK_ID1] [int] NOT NULL,
[LotsMoreFields] [NVARCHAR] (MAX),
CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED ([ID] ASC)
)
CREATE TABLE [dbo].[FK1]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [int] NOT NULL, -- Unique constrained value
CONSTRAINT [PK_FK1] PRIMARY KEY CLUSTERED ([ID] ASC)
)
The other requirement is I am tracking the source table PK vs the target PK and whether an insert occurred or whether I have already migrated that row to the target. To do this, I'm tracking migrated rows in another table like so:
CREATE TABLE [dbo].[ChangeTracking]
(
[ReferenceID] BIGINT IDENTITY(1,1),
[Src_ID] BIGINT,
[Dest_ID] BIGINT,
[TableName] NVARCHAR(255),
CONSTRAINT [PK_ChangeTracking] PRIMARY KEY CLUSTERED ([ReferenceID] ASC)
)
My existing method is executing some dynamic sql generated by a stored procedure. The stored proc does PK lookups as the source system has different PK values for table [dbo].[FK1].
E.g.
IF NOT EXISTS (<ignore this existence check for now>)
BEGIN
INSERT INTO [Dest].[dbo].[MyTable] ([DataField1],[FK_ID1],[LotsMoreFields]) VALUES (333,(SELECT [ID] FROM [Dest].[dbo].[FK1] WHERE [Name]=N'ValueFoundInSource'),N'LotsMoreValues');
INSERT INTO [Dest].[dbo].[ChangeTracking] ([Src_ID],[Dest_ID],[TableName]) VALUES (666,SCOPE_IDENTITY(),N'MyTable'); --666 is the PK in [Src].[dbo].[MyTable] for this inserted row
END
So when you have a million of these, it isn't quick.
Is there a recommended performant way of doing this?
As mentioned, the MERGE statement works well when you're looking at a complex JOIN condition (if any of these fields are different, update the record to match). You can also look into creating a HASHBYTES hash of the entire record to quickly find differences between source and target tables, though that can also be time-consuming on very large data sets.
It sounds like you're making these updates like a front-end developer, by checking each row for a match and then doing the insert. It will be far more efficient to do the inserts with a single query. Below is an example that looks for names that are in the tblNewClient table, but not in the tblClient table:
INSERT INTO tblClient
( [Name] ,
TypeID ,
ParentID
)
SELECT nc.[Name] ,
nc.TypeID ,
nc.ParentID
FROM tblNewClient nc
LEFT JOIN tblClient cl
ON nc.[Name] = cl.[Name]
WHERE cl.ID IS NULL;
This is will way more efficient than doing it RBAR (row by agonizing row).
Taking the two answers from #RusselFox and putting them together, I reached this tentative solution (but looking a LOT more efficient):
MERGE INTO [Dest].[dbo].[MyTable] [MT_D]
USING (SELECT [MT_S].[ID] as [SrcID],[MT_S].[DataField1],[FK_1_D].[ID] as [FK_ID1],[MT_S].[LotsMoreFields]
FROM [Src].[dbo].[MyTable] [MT_S]
JOIN [Src].[dbo].[FK_1] ON [MT_S].[FK_ID1] = [FK_1].[ID]
JOIN [Dest].[dbo].[FK_1] [FK_1_D] ON [FK_1].[Name] = [FK_1_D].[Name]
) [SRC] ON 1 = 0
WHEN NOT MATCHED THEN
INSERT([DataField1],[FL_ID1],[LotsMoreFields])
VALUES ([DataField1],[FL_ID1],[LotsMoreFields])
OUTPUT [SRC].[SrcID],INSERTED.[ID],0,N'MyTable' INTO [Dest].[dbo].[ChangeTracking]([Src_ID],[Dest_ID],[AlreadyExists],[TableName]);

Index on nvarchar datatype with XML data in SQL Server

I have a table with this structure :
CREATE TABLE [dbo].[GasTests]
(
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[GasReceptionId] [bigint] NOT NULL,
[LineId] [bigint] NOT NULL,
[LabelCode] [nvarchar](max) NULL,
[StartTestDatetime] [datetime] NULL,
[EndTestDatetime] [datetime] NULL,
[ResultTest] [nvarchar](max) NULL,
CONSTRAINT [PK_dbo.GasTests]
PRIMARY KEY CLUSTERED ([Id] ASC)
)
In resulttest I store a xml datatype like this :
<?xml version="1.0" encoding="utf-16"?>
<GasResultTestParameterView xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ReceptionId>86</ReceptionId>
<ISIRI_9426_C1>on</ISIRI_9426_C1>
<ISIRI_9426_C2>on</ISIRI_9426_C2>
<ISIRI_9426_C3>on</ISIRI_9426_C3>
<ISIRI_9426_C4>on</ISIRI_9426_C4>
<ISIRI_9426_C5>on</ISIRI_9426_C5>
<ISIRI_9426_C6>on</ISIRI_9426_C6>
<ISIRI_9426_C7>on</ISIRI_9426_C7>
<ISIRI_9426_C8>on</ISIRI_9426_C8>
<ISIRI_9426_C9>on</ISIRI_9426_C9>
<ISIRI_9426_C10>on</ISIRI_9426_C10>
<ISIRI_9426_C11>on</ISIRI_9426_C11>
<ISIRI_9426_C12>on</ISIRI_9426_C12>
<ISIRI_9426_C13>on</ISIRI_9426_C13>
<ISIRI_9426_C14>on</ISIRI_9426_C14>
<ISIRI_9426_C15>on</ISIRI_9426_C15>
<ISIRI_9426_C16>on</ISIRI_9426_C16>
<ISIRI_9426_C17>on</ISIRI_9426_C17>
<ISIRI_9426_C18>on</ISIRI_9426_C18>
<ISIRI_9426_C19>on</ISIRI_9426_C19>
<ISIRI_9426_C20>on</ISIRI_9426_C20>
<ISIRI_9426_C21>on</ISIRI_9426_C21>
<ISIRI_9426_C22>on</ISIRI_9426_C22>
<ISIRI_9426_C23>on</ISIRI_9426_C23>
<ISIRI_9426_C24>on</ISIRI_9426_C24>
<ISIRI_9426_C25>on</ISIRI_9426_C25>
<ISIRI_9426_C26>on</ISIRI_9426_C26>
<ISIRI_9426_C27>on</ISIRI_9426_C27>
<ISIRI_9426_C28>on</ISIRI_9426_C28>
<ISIRI_9426_C29>on</ISIRI_9426_C29>
<ISIRI_9426_C30>on</ISIRI_9426_C30>
<ISIRI_9747_K1>on</ISIRI_9747_K1>
<ISIRI_9747_K2>on</ISIRI_9747_K2>
<ISIRI_9747_K3>on</ISIRI_9747_K3>
<ISIRI_9747_K4>on</ISIRI_9747_K4>
<ISIRI_9747_K5>on</ISIRI_9747_K5>
<ISIRI_9747_K6>on</ISIRI_9747_K6>
<ISIRI_9747_K7>on</ISIRI_9747_K7>
<ISIRI_9747_K8>on</ISIRI_9747_K8>
<ISIRI_9747_K9>on</ISIRI_9747_K9>
<ISIRI_9747_K10>on</ISIRI_9747_K10>
<ISIRI_9747_K11>on</ISIRI_9747_K11>
<ISIRI_9747_K12>on</ISIRI_9747_K12>
<ISIRI_9747_K13>on</ISIRI_9747_K13>
<ISIRI_9747_K14>on</ISIRI_9747_K14>
<ISIRI_9747_K15>on</ISIRI_9747_K15>
<ISIRI_9747_K16>on</ISIRI_9747_K16>
<ISIRI_9747_K17>on</ISIRI_9747_K17>
<ISIRI_9747_K18>on</ISIRI_9747_K18>
<ISIRI_9747_K19>on</ISIRI_9747_K19>
<ISIRI_9747_K20>on</ISIRI_9747_K20>
<ISIRI_9747_K21>on</ISIRI_9747_K21>
<ISIRI_9747_K22>on</ISIRI_9747_K22>
<ISIRI_9747_K23>on</ISIRI_9747_K23>
<ISIRI_9747_K24>on</ISIRI_9747_K24>
<ISIRI_9747_K25>on</ISIRI_9747_K25>
<ISIRI_9747_K26>on</ISIRI_9747_K26>
<ISIRI_9747_K27>on</ISIRI_9747_K27>
<ISIRI_9747_K28>on</ISIRI_9747_K28>
<ISIRI_9747_K29>on</ISIRI_9747_K29>
<ISIRI_9747_K30>on</ISIRI_9747_K30>
<ISIRI_9747_K31>on</ISIRI_9747_K31>
<ISIRI_9747_K32>on</ISIRI_9747_K32>
<ISIRI_9747_K33>on</ISIRI_9747_K33>
<ISIRI_9747_K34>on</ISIRI_9747_K34>
<ISIRI_9747_K35>on</ISIRI_9747_K35>
<ISIRI_9747_K36>on</ISIRI_9747_K36>
<ISIRI_9747_K37>on</ISIRI_9747_K37>
<ISIRI_9747_K38>on</ISIRI_9747_K38>
<ISIRI_9747_K39>on</ISIRI_9747_K39>
<ISIRI_9747_K40>on</ISIRI_9747_K40>
<ISIRI_9747_K41>on</ISIRI_9747_K41>
<ISIRI_9747_K42>on</ISIRI_9747_K42>
<ISIRI_9747_K43>on</ISIRI_9747_K43>
<ISIRI_9747_K44>on</ISIRI_9747_K44>
<ISIRI_9747_K45>on</ISIRI_9747_K45>
<ISIRI_9747_K46>on</ISIRI_9747_K46>
<TapCompany>LANDIRENZO</TapCompany>
<TapSerialNumber>039837</TapSerialNumber>
<RegulatorCompany>OMVL</RegulatorCompany>
<RegulatorSerialNumber>039837</RegulatorSerialNumber>
<CapsuleCompany>JINDUN</CapsuleCompany>
<CapsuleSerialNumber>1004986</CapsuleSerialNumber>
<CapsuleType>2</CapsuleType>
<CapsuleBuiltDate>1391</CapsuleBuiltDate>
<CapsuleExpireDate>1402</CapsuleExpireDate>
<GasSystemGeneration>2</GasSystemGeneration>
<Remark>تایید</Remark>
<PassedISIRI9747>on</PassedISIRI9747>
<PassedISIRI9426>on</PassedISIRI9426>
<PassedISIRI6792>noneed</PassedISIRI6792>
</GasResultTestParameterView>
I need a lot of reports based on my xml result. But when I want to search an item in my XML, it takes a lot of time. How can I make my table and my xml faster?
If you define your Resulttest column as XML, then you can fetch individual bits from your XML and store them into your table structure - as computed columns.
Storing as XML has two main benefits:
the storage is more efficient, than storing it as plain text - the XML will be "shredded" internally, and stored as tokens. This cuts back on the storage size, and it makes accessing the XML faster
using a XML column allows you to use the XQuery function on it, to handle the XML structures
First, define a scalar function like this (assuming you want to "surface" the TapCompany value):
CREATE FUNCTION dbo.GetTapCompany(#Input XML)
RETURNS VARCHAR(100)
AS
BEGIN
DECLARE #Result VARCHAR(100)
SELECT #Result = #Input.value('(/GasResultTestParameterView/TapCompany)[1]', 'VARCHAR(100)')
RETURN #Result
END
This function takes the XML value (from your table row) as input, goes into the XML and gets the TapCompany value, and returns it.
Then, add this as a column to your database table:
ALTER TABLE dbo.GasTests
ADD TapCompany AS dbo.GetTapCompany(Resulttest);
Now, you should be able to select from your table, and get the value of TapCompany along side your other table values:
SELECT
Id,
GasReceptionId,
LineId,
LabelCode,
TapCompany
FROM
dbo.GasTests
Unfortunately, you cannot make this column persisted - so each time you select from that table and include that column, the scalar function that provides the value will be called. Therefore, I'm unsure if you'll see any performance improvement - but I think that should be quite easy to check for you - with your real data - give it a try!

SQL Server - Order Identity Fields in Table

I have a table with this structure:
CREATE TABLE [dbo].[cl](
[ID] [int] IDENTITY(1,1) NOT NULL,
[NIF] [numeric](9, 0) NOT NULL,
[Name] [varchar](80) NOT NULL,
[Address] [varchar](100) NULL,
[City] [varchar](40) NULL,
[State] [varchar](30) NULL,
[Country] [varchar](25) NULL,
Primary Key([ID],[NIF])
);
Imagine that this table has 3 records. Record 1, 2, 3...
When ever I delete Record number 2 the IDENTITY Field generates a Gap. The table then has Record 1 and Record 3. Its not correct!
Even if I use:
DBCC CHECKIDENT('cl', RESEED, 0)
It does not solve my problem becuase it will set the ID of the next inserted record to 1. And that's not correct either because the table will then have a multiple ID.
Does anyone has a clue about this?
No database is going to reseed or recalculate an auto-incremented field/identity to use values in between ids as in your example. This is impractical on many levels, but some examples may be:
Integrity - since a re-used id could mean records in other systems are referring to an old value when the new value is saved
Performance - trying to find the lowest gap for each value inserted
In MySQL, this is not really happening either (at least in InnoDB or MyISAM - are you using something different?). In InnoDB, the behavior is identical to SQL Server where the counter is managed outside of the table, so deleted values or rolled back transactions leave gaps between last value and next insert. In MyISAM, the value is calculated at time of insertion instead of managed through an external counter. This calculation is what is giving the perception of being recalcated - it's just never calculated until actually needed (MAX(Id) + 1). Even this won't insert inside gaps (like the id = 2 in your example).
Many people will argue if you need to use these gaps, then there is something that could be improved in your data model. You shouldn't ever need to worry about these gaps.
If you insist on using those gaps, your fastest method would be to log deletes in a separate table, then use an INSTEAD OF INSERT trigger to perform the inserts with your intended keys by first looking for records in these deletions table to re-use (then deleting them to prevent re-use) and then using the MAX(Id) + 1 for any additional rows to insert.
I guess what you want is something like this:
create table dbo.cl
(
SurrogateKey int identity(1, 1)
primary key
not null,
ID int not null,
NIF numeric(9, 0) not null,
Name varchar(80) not null,
Address varchar(100) null,
City varchar(40) null,
State varchar(30) null,
Country varchar(25) null,
unique (ID, NIF)
)
go
I added a surrogate key so you'll have the best of both worlds. Now you just need a trigger on the table to "adjust" the ID whenever some prior ID gets deleted:
create trigger tr_on_cl_for_auto_increment on dbo.cl
after delete, update
as
begin
update dbo.cl
set ID = d.New_ID
from dbo.cl as c
inner join (
select c2.SurrogateKey,
row_number() over (order by c2.SurrogateKey asc) as New_ID
from dbo.cl as c2
) as d
on c.SurrogateKey = d.SurrogateKey
end
go
Of course this solution also implies that you'll have to ensure (whenever you insert a new record) that you check for yourself which ID to insert next.

Querying a varbinary column in SQL Server

I have some issues with querying varbinary columns using the contains predicate (it only works on nvarchar/varchar but on the msdn documentation it is specified that it works on image/varbinary also)
I have this table
[dbo].[Documents]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[title] [nvarchar](100) NOT NULL,
[doctype] [nchar](4) NOT NULL,
[docexcerpt] [nvarchar](1000) NOT NULL,
[doccontent] [varbinary](max) NOT NULL,
CONSTRAINT [PK_Documents]
PRIMARY KEY CLUSTERED ([id] ASC)
)
doctype - document type (format)
docexcerpt - small fragment of the document
doccontent - whole document stored in varbinary
Code:
INSERT INTO dbo.Documents (title, doctype, docexcerpt, doccontent)
SELECT
N'Columnstore Indices and Batch Processing',
N'docx',
N'You should use a columnstore index on your fact tables, putting all columns of a fact table in a columnstore index. In addition to fact tables, very large dimensions could benefit from columnstore indices as well. Do not use columnstore indices for small dimensions. ',
bulkcolumn
FROM
OPENROWSET(BULK 'myUrl', SINGLE_BLOB) AS doc;
Now this is how it looks like :
I have installed the Microsoft Office 2010 Filter Packs and registered them in SQL Server and checked if what I need (.docx) is installed using
SELECT document_type, path
FROM sys.fulltext_document_types;
Here's the output
My issue is that this query doesn't return anything :
As an observation, I have created a fulltext catalog and index on my table using the following code(s), making both docexcerpt and doccontent index-able columns
--fulltext index
CREATE FULLTEXT INDEX ON dbo.Documents
(
docexcerpt Language 1033,
doccontent TYPE COLUMN doctype Language 1033
STATISTICAL_SEMANTICS
)
KEY INDEX PK_Documents
ON DocumentsFtCatalog
WITH STOPLIST = SQLStopList,
SEARCH PROPERTY LIST = WordSearchPropertyList,
CHANGE_TRACKING AUTO;
I'm not sure what am I doing wrong/missing. I'd appreciate any help. Thanks
I've managed to 'solve' the mistery, well.... I forgot that I had to re-insert my documents into my tables (after editing them) in order for my queries to work properly. Can't believe I've been so numb.

SQL design for various data types

I need to store data in a SQL Server 2008 database from various data sources with different data types. Data types allowed are: Bit, Numeric (1, 2 or 4 bytes), Real and String. There is going to be a value, a timestamp, a FK to the item of which the value belongs and some other information for the data stored.
The most important points are the read performance and the size of the data. There might be a couple thousand items and each item may have millions of values.
I have 5 possible options:
Separate tables for each data type (ValueBit, ValueTinyInt, ValueSmallInt, etc... tables)
Separate tables with inheritance (Value table as base table, ValueBit table just for storing the Bit value, etc...)
Single value table for all data types, with separate fields for each data type (Value table, with ValueBit BIT, ValueTinyInt TINYINT etc...)
Single table and single value field using sql_variant
Single table and single value field using UDT
With case 2, a PK is a must, and,
1000 item * 10 000 000 data each > Int32.Max, and,
1000 item * 10 000 000 data each * 8 byte BigInt PK is huge
Other than that, I am considering 1 or 3 with no PK. Will they differ in size?
I do not have experience with 4 or 5 and I do not think that they will perform well in this scenario.
Which way shall I go?
Your question is hard to answer as you seem to use a relational database system for something it is not designed for. The data you want to keep in the database seems to be too unstructured for getting much benefit from a relational database system. Database designs with mostly fields like "parameter type" and "parameter value" that try to cover very generic situations are mostly considered to be bad designs. Maybe you should consider using a "non relational database" like BigTable. If you really want to use a relational database system, I'd strongly recommend to read Beginning Database Design by Clare Churcher. It's an easy read, but gets you on the right track with respect to RDBS.
What are usage scenarios? Start with samples of queries and calculate necessary indexes.
Consider data partitioning as mentioned before. Try to understand your data / relations more. I believe the decision should be based on business meaning/usages of the data.
I think it's a great question - This situation is fairly common, though it is awkward to make tables to support it.
In terms of performance, having a table like indicated in #3 potentially wastes a huge amount of storage and RAM because for each row you allocate space for a value of every type, but only use one. If you use the new sparse table feature of 2008 it could help, but there are other issues too: it's a little hard to constrain/normalize, because you want only only one of the multiple values to be populated for each row - having two values in two columns would be an error, but the design doesn't reflect that. I'd cross that off.
So, if it were me I'd be looking at option 1 or 2 or 4, and the decision would be driven by this: do I typically need to make one query returning rows that have a mix of values of different types in the same result set? Or will I almost always ask for the rows by item and by type. I ask because if the values are different types it implies to me some difference in the source or the use of that data (you are unlikely, for example, to compare a string and a real, or a string and a bit.) This is relevant because having different tables per type might actually be a significant performance/scalability advantage, if partitioning the data that way makes queries faster. Partitioning data into smaller sets of more closely related data can give a performance advantage.
It's like having all the data in one massive (albeit sorted) set or having it partitioned into smaller, related sets. The smaller sets favor some types of queries, and if those are the queries you will need, it's a win.
Details:
CREATE TABLE [dbo].[items](
[itemid] [int] IDENTITY(1,1) NOT NULL,
[item] [varchar](100) NOT NULL,
CONSTRAINT [PK_items] PRIMARY KEY CLUSTERED
(
[itemid] ASC
)
)
/* This table has the problem of allowing two values
in the same row, plus allocates but does not use a
lot of space in memory and on disk (bad): */
CREATE TABLE [dbo].[vals](
[itemid] [int] NOT NULL,
[datestamp] [datetime] NOT NULL,
[valueBit] [bit] NULL,
[valueNumericA] [numeric](2, 0) NULL,
[valueNumericB] [numeric](8, 2) NULL,
[valueReal] [real] NULL,
[valueString] [varchar](100) NULL,
CONSTRAINT [PK_vals] PRIMARY KEY CLUSTERED
(
[itemid] ASC,
[datestamp] ASC
)
)
ALTER TABLE [dbo].[vals] WITH CHECK
ADD CONSTRAINT [FK_vals_items] FOREIGN KEY([itemid])
REFERENCES [dbo].[items] ([itemid])
GO
ALTER TABLE [dbo].[vals] CHECK CONSTRAINT [FK_vals_items]
GO
/* This is probably better, though casting is required
all the time. If you search with the variant as criteria,
that could get dicey as you have to be careful with types,
casting and indexing. Also everything is "mixed" in one
giant set */
CREATE TABLE [dbo].[allvals](
[itemid] [int] NOT NULL,
[datestamp] [datetime] NOT NULL,
[value] [sql_variant] NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[allvals] WITH CHECK
ADD CONSTRAINT [FK_allvals_items] FOREIGN KEY([itemid])
REFERENCES [dbo].[items] ([itemid])
GO
ALTER TABLE [dbo].[allvals] CHECK CONSTRAINT [FK_allvals_items]
GO
/* This would be an alternative, but you trade multiple
queries and joins for the casting issue. OTOH the implied
partitioning might be an advantage */
CREATE TABLE [dbo].[valsBits](
[itemid] [int] NOT NULL,
[datestamp] [datetime] NOT NULL,
[val] [bit] NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[valsBits] WITH CHECK
ADD CONSTRAINT [FK_valsBits_items] FOREIGN KEY([itemid])
REFERENCES [dbo].[items] ([itemid])
GO
ALTER TABLE [dbo].[valsBits] CHECK CONSTRAINT [FK_valsBits_items]
GO
CREATE TABLE [dbo].[valsNumericA](
[itemid] [int] NOT NULL,
[datestamp] [datetime] NOT NULL,
[val] numeric( 2, 0 ) NOT NULL
) ON [PRIMARY]
GO
... FK constraint ...
CREATE TABLE [dbo].[valsNumericB](
[itemid] [int] NOT NULL,
[datestamp] [datetime] NOT NULL,
[val] numeric ( 8, 2 ) NOT NULL
) ON [PRIMARY]
GO
... FK constraint ...
etc...

Resources