Store more character than the assign column in SQL Server - sql

Can I do something like this, column is of type nchar(8), but the string I wanted to store in the table is longer than that.
The reason I am doing this is because I want to convert from one table to another table. Table A is nchar(8) and Table B is nvarchar(100). I want all characters in Table B transfer to Table A without missing any single character.

If the nvarchar(100) contains only latin characters with a length up to 16 chars, then you can squeeze the nvarchar(100) into the nchar(8):
declare #t table
(
col100 nvarchar(100),
col8 nchar(8)
);
insert into #t(col100) values('1234567890123456');
update #t
set col8 = cast(cast(col100 as varchar(100)) as varbinary(100))
select *, cast(cast(cast(col8 as varbinary(100)) as varchar(100)) as nvarchar(100)) as from8to100_16charsmax
from #t;

If you cannot modify A, then you cannot use it to store the data. Create another table for the overflow . . . something like:
create table a_overflow (
a_pk int primary key references a(pk),
column nvarchar(max) -- why stop at 100?
);
Then, you can construct a view to bring in the data from this table when viewing a:
create view vw_a as
select . . . , -- all the other columns
coalesce(ao.column, a.column) as column
from a left join
a_overflow ao
on ao.a_pk = a.pk;
And, if you really want to "hide" the view, you can create an insert trigger on vw_a, which inserts the appropriate values into the two tables.
This is a lot of work. Simply modifying the table is much simpler. That said, this approach is sometimes needed when you need to modify a large table and altering a column would incur too much locking overhead.

Related

How to update xml column node value with another column new value at same update query?

I want to change the value of 2 columns in one table. One column is varchar and the other is XML. First of all, I want to replace the value of the RECIPIENT column with the new value and replace the node value named as RecipientNo in the XML column with the new value of RecipientNo. How can I do these two operations in the same update function? The query below works. Secondly, DATARECORD table includes too many records. Does modify function take too much time to update the records? If so, how can I increase the performance of modify function or can you suggest another alternative solution? By the way, I cannot add index to DATARECORD table. Thanks.
Here is the sample row;
ID RECIPIENT RECORDDETAILS
1 1 <?xml version="1.0"?>
<MetaTag xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/XMLSchema">
<Code>123</Code>
<RecipientNo>123</RecipientNo>
<Name>xyz</Name>
</MetaTag>'
CREATE TABLE #TEMPTABLE(
ID bigint,
RECIPIENT nvarchar(max),
RECORDDETAILS xml
)
INSERT INTO #TEMPTABLE
SELECT ID,RECIPIENT,RECORDDETAILS
FROM DATARECORD WITH (NOLOCK)
WHERE cast(RECORDDETAILS as varchar(max)) LIKE '%<Code>123</Code>%' and cast(RECORDDETAILS as varchar(max)) LIKE '%MetaTag%'
UPDATE #TEMPTABLE SET RECIPIENT = CONCAT('["queryType|1","recipientNoIDENTIFICATION|',RECIPIENT,']')
UPDATE #TEMPTABLE SET RECORDDETAILS.modify('replace value of (MetaTag/RecipientNo/text())[1] with sql:column("RECIPIENT")')
UPDATE d
SET d.RECORDDETAILS =Concat('<?xml version="1.0"?>', CAST(t.RECORDDETAILS AS VARCHAR(max))),
d.RECIPIENT = t.RECIPIENT
FROM dbo.DATARECORD as d
Join #TEMPTABLE as t
ON t.ID = d.ID
It's certainly possible to update an SQL column and an XML node in the same update statement, e.g.:
create table DataRecord (
ID bigint not null primary key,
Recipient nvarchar(max) not null,
RecordDetails xml not null
);
insert DataRecord values
(1, N'1', N'<?xml version="1.0"?>
<MetaTag xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/XMLSchema">
<Code>123</Code>
<RecipientNo>123</RecipientNo>
<Name>xyz</Name>
</MetaTag>');
create table #TempTable (
ID bigint not null primary key,
Recipient nvarchar(max) not null,
RecordDetails xml not null
);
insert #TempTable
select ID, Recipient, RecordDetails
from DataRecord with (nolock)
where cast(RecordDetails as varchar(max)) like '%<Code>123</Code>%' and cast(RecordDetails as varchar(max)) like '%MetaTag%'
-- Change an SQL value and an XML node in the one update statement...
update tt set
Recipient = NewRecipient,
RecordDetails.modify('replace value of (/MetaTag/RecipientNo/text())[1] with sql:column("NewRecipient")')
from #TempTable tt
outer apply (
select NewRecipient = concat('["queryType|1","recipientNoIDENTIFICATION|', Recipient, '"]')
) Calc
select * from #TempTable
Which yields:
ID Recipient RecordDetails
1 ["queryType|1","recipientNoIDENTIFICATION|1"] <MetaTag
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/XMLSchema">
<Code>123</Code>
<RecipientNo>["queryType|1","recipientNoIDENTIFICATION|1"]</RecipientNo>
<Name>xyz</Name>
</MetaTag>
There are a couple of things contributing to your performance problem:
Converting XML, which SQL Server essentially stores in UTF-16 encoding, to varchar (twice) is expensive. It will also trash any Unicode characters outside your database's collation.
Performing like matches on the XML (converted to varchar) will be causing TABLE SCAN operations, converting and testing every row in your table.
Some things to consider:
Add XML Index(es) to the RecordDetails column and use something like WHERE RecordDetails.exists('/MetaTag/Code[.="123"]) to short list the rows to be updated.
Alternatively, pre-shred your RecordDetails, persist the value of /MetaTag/Code/text() in a table column (e.g.: MetaTagCode), and use something like WHERE MetaTagCode='123' in your query. Adding an index to that column will allow SQL to do a much cheaper INDEX SCAN when searching for the desired value instead of a TABLE SCAN.
Since you say you cannot add indexes you're basically going to have to tolerate TABLE SCANs and just wait it out.

Table Variable in SQL Server Function from Input Columns

I would like to create a function that returns a column based on input from three other columns. As temporary tables are not allowed within functions, is it possible to create a table variable from three input columns?
CREATE FUNCTION dbo.convert_value(
#CustomerID VARCHAR(MAX),
#CustomerValue VARCHAR(MAX),
#CustomerDescription VARCHAR(MAX)
)
RETURNS FLOAT
AS BEGIN
DECLARE #CustomerTable TABLE (
UniquePatientUID VARCHAR(MAX),
ResultValue VARCHAR(MAX),
PracticeDescription VARCHAR(MAX)
);
-- How can I insert #UniquePatientUID, #ResultValue and #PracticeDescription into #CustomerTable
END
The context of this question is that I have a SQL script that uses temporary tables and many UPDATE and ALTER TABLE statements, that I need to convert into a function. That script begins with the three columns mentioned, and adds a fourth column, Converted_Value, which is calculated with several hundred lines of code and manipulating temporary tables. Is there any hope here?
A table variable insert is really not different than a regular insert. Don't use temp tables. You can alter the table as well, or just declare it initially with that fourth column and allow it to be NULL.
INSERT INTO #CustomerTable (UniquePatientUID, ResultValue, PracticeDescription)
VALUES(#CustomerID, #CustomerValue, #CustomerDescription);
Don't forget to return the FLOAT.
Table Variable is a table so, you can just use INSERT INTO ... VALUES....
INSERT INTO #CustomerTable (UniquePatientUID,ResultValue,PracticeDescription )
VALUES
(#UniquePatientUID, #ResultValue , #PracticeDescription)
Unless you need a table variable for some specific reason, why not just work with the variables as a derived table expression? i.e.
;with inputs (UniquePatientUID, ResultValue, PracticeDescription) as
(
select #UniquePatientUID, #ResultValue, #PracticeDescription
)
select *
from inputs
Table variables fall out of scope after the function call, and you can't pass table types in or out of functions either. So really all a table variable does here is serve as a means of place keeping that's more familiar to SQL developers. But they're not free, which is the only reason I'm curious what your use case is.
If you don't need to return them as a set or something similar, you can just interact with the variables directly too.

is it possible to "clone" a table variable?

I have a table variable with about 20 columns. I'd like to essentially reuse a single table variable structure for 2 different result sets. The 2 result sets should be represented in different table variables so I can't reuse a single table variable. Therefore, I was wondering if there was a way to clone a single table variable for reuse. For example, something like this:
DECLARE #MyTableVar1 TABLE(
Col1 INT,
Col2 INT
}
DECLARE #MyTableVar2 TABLE = #MyTableVar1
I'd like to avoid creating duplicate SQL if I can reuse existing SQL.
That is not possible, use temp table instead
if object_id('tempdb..#MyTempTable1') is not null drop table #MyTempTable1
Create TABLE #MyTempTable1 (
Col1 INT,
Col2 INT
)
if object_id('tempdb..#MyTempTable2') is not null drop table #MyTempTable2
select * into #MyTempTable2 from #MyTempTable1
update :
As suggested by Eric in comment, if you are looking for just table schema and not the data inside the first table then
select * into #MyTempTable2 from #MyTempTable1 where 1 = 0
You can create a user-defined table type which is typically meant for using table valued parameters for stored procedures. Once the type is created, you can use it as a type to declare any number of table variables just like built-in types. This comes closest to you requirement.
Ex:
CREATE TYPE MyTableType AS TABLE
( COL1 int
, COL2 int )
DECLARE #MyTableVar1 AS MyTableType
DECLARE #MyTableVar2 AS MyTableType
A few things to note with this solution
MyTableType becomes a database level type. It is not local to a specific stored procedure.
If you have to ever change the definition of the table, then you have to drop the code/sprocs using the TVP type, then recreate the table type with new definition and related sprocs. Typically this is a non-issue as the code and the type are created/recreated together.
You could use a temp table and select into... they perform better since their statistics are better.
create table #myTable(
Col1 INT null,
Col2 INT null
}
...
select *
into #myTableTwo
from #myTable
You can create one table variable and add type column in the table and use the type column in your queries to filter the data.
By this you are using one table to hold more than one type of data.
Hope this helps.
declare #myTable table(
Col1 INT null,
Col2 INT null,
....
Type INT NULL
}
insert into #myTable(...,type)
select ......,1
insert into #myTable(...,type)
select ......,2
select * from #myTable where type =1
select * from #myTable where type =2

How to column schema of arbitrary query in SQL server

I want to get just schema of arbitrary sql query in SQL server.
For example-
Create Table Tabl1(Ta1bID int ,col1 varchar(10))
Create Table Tabl2(Tab1ID int ,col2 varchar(20))
SQL Query -
SELECT col1, col2
FROM Tab1
INNER JOIN Tab2 ON Tab1ID = Tab2ID
Here result will have this schema-
Col1 varchar(10), Col2 varchar(20)
I want to know what will be schema of result.
PS : I have just read access on the server where I am executing this query.
Any way to do this?
The schema, you mean to say datatype right? For resulted datatype will you always know when you operate on table(s). In your case both column is varchar datatype, so it will give character datatype, that you may convert into any character datatype like varchar, nvarchar, char, ntext etc.
Basic thing is, table designing was/is done by, so that time we know why we define datatype, now when you execute query below , you always know what will come with datatype of each column.
SELECT col1, col2
FROM Tab1
INNER JOIN Tab2 ON Tab1ID = Tab2ID
This though will issue when you use dynamic query, where you run time add column as per your requirement and you then execute which may or may not give error, due to mismatch of wrong datatype.
like
declare #sqlstring nvarchar(max)
set #sqlstring = 'declare #t table (name varchar(50))
insert into #t values(''Minh''),(''Tinh''),(''Justin'')
Select * from #t where Name like ''%in%'''
EXEC sp_executesql #sqlstring
I had similar problem before, but I had to create tables (70+) of each query one by one.
If there is a pattern, write a tool to generate create table
statement.
If not and it's one time job, just create them manually.
If it's not one time job, you might be thinking, why would store temp
date in table instead of temp table.

SQL How to Split One Column into Multiple Variable Columns

I am working on MSSQL, trying to split one string column into multiple columns. The string column has numbers separated by semicolons, like:
190230943204;190234443204;
However, some rows have more numbers than others, so in the database you can have
190230943204;190234443204;
121340944534;340212343204;134530943204
I've seen some solutions for splitting one column into a specific number of columns, but not variable columns. The columns that have less data (2 series of strings separated by commas instead of 3) will have nulls in the third place.
Ideas? Let me know if I must clarify anything.
Splitting this data into separate columns is a very good start (coma-separated values are an heresy). However, a "variable number of properties" should typically be modeled as a one-to-many relationship.
CREATE TABLE main_entity (
id INT PRIMARY KEY,
other_fields INT
);
CREATE TABLE entity_properties (
main_entity_id INT PRIMARY KEY,
property_value INT,
FOREIGN KEY (main_entity_id) REFERENCES main_entity(id)
);
entity_properties.main_entity_id is a foreign key to main_entity.id.
Congratulations, you are on the right path, this is called normalisation. You are about to reach the First Normal Form.
Beweare, however, these properties should have a sensibly similar nature (ie. all phone numbers, or addresses, etc.). Do not to fall into the dark side (a.k.a. the Entity-Attribute-Value anti-pattern), and be tempted to throw all properties into the same table. If you can identify several types of attributes, store each type in a separate table.
If these are all fixed length strings (as in the question), then you can do the work fairly simply (at least relative to other solutions):
select substring(col, 1+13*(n-1), 12) as val
from t join
(select 1 as n union all select union all select 3
) n
on len(t.col) <= 13*n.n
This is a useful hack if all the entries are the same size (not so easy if they are of different sizes). Do, however, think about the data structure because semi-colon (or comma) separated list is not a very good data structure.
IF I were you, I would create a simple function that is dividing values separated with ';' like this:
IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N'fn_Split_List') AND xtype IN (N'FN', N'IF', N'TF'))
BEGIN
DROP FUNCTION [dbo].[fn_Split_List]
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[fn_Split_List](#List NVARCHAR(512))
RETURNS #ResultRowset TABLE ( [Value] NVARCHAR(128) PRIMARY KEY)
AS
BEGIN
DECLARE #XML xml = N'<r><![CDATA[' + REPLACE(#List, ';', ']]></r><r><![CDATA[') + ']]></r>'
INSERT INTO #ResultRowset ([Value])
SELECT DISTINCT RTRIM(LTRIM(Tbl.Col.value('.', 'NVARCHAR(128)')))
FROM #xml.nodes('//r') Tbl(Col)
RETURN
END
GO
Than simply called in this way:
SET NOCOUNT ON
GO
DECLARE #RawData TABLE( [Value] NVARCHAR(256))
INSERT INTO #RawData ([Value] )
VALUES ('1111111;22222222')
,('3333333;113113131')
,('776767676')
,('89332131;313131312;54545353')
SELECT SL.[Value]
FROM #RawData AS RD
CROSS APPLY [fn_Split_List] ([Value]) as SL
SET NOCOUNT OFF
GO
The result is as the follow:
Value
1111111
22222222
113113131
3333333
776767676
313131312
54545353
89332131
Anyway, the logic in the function is not complicated, so you can easily put it anywhere you need.
Note: There is not limitations of how many values you will have separated with ';', but there are length limitation in the function that you can set to NVARCHAR(MAX) if you need.
EDIT:
As I can see, there are some rows in your example that will caused the function to return empty strings. For example:
number;number;
will return:
number
number
'' (empty string)
To clear them, just add the following where clause to the statement above like this:
SELECT SL.[Value]
FROM #RawData AS RD
CROSS APPLY [fn_Split_List] ([Value]) as SL
WHERE LEN(SL.[Value]) > 0