Copy Table and Merge field - sql

Could you please help me with present SQL?
I am copy 1 table to another table and the same time merge the fields.
I get an error on the +
INSERT INTO [dSCHEMA].[TABLE_COPY_TO]
(
[FIELD_A],
[FIELD_B],
[FIELD_A] + '-' + [FIELD_B]
)
SELECT [FIELD_A]
,[FIELD_B]
FROM [dSCHEMA].[TABLE_COPY_FROM]

The appended text actually needs to be in the SELECT statement so it would look more like
Insert Into [dSCHEMA].[TABLE_COPY_TO]
(FieldA, FieldB, FieldC)
Select FieldA, FieldB, FieldA + '-' + FieldB
From [dSCHEMA].[TABLE_COPY_FROM]

I guess you are trying to create a computed column in [dSCHEMA].[TABLE_COPY_TO].
In such case, you have to define the DDL properly. Below is an example
declare #tblCopyFrom table
(
fieldA varchar(10)
,fieldB varchar(10)
)
declare #tblCopyTo table
(
fieldA varchar(10)
,fieldB varchar(10)
,fieldC AS (fieldA + '-' + fieldB) -- Computed Column
)
insert into #tblCopyFrom
select 'valA1','valB1' union all
select 'valA2','valB2' union all
select 'valA3','valB3' union all
select 'valA4','valB4' union all
select 'valA5','valB5'
insert into #tblCopyTo (fieldA,fieldB)
select * from #tblCopyFrom
select * from #tblCopyTo
Else, before insertion you can add the computed column to your table and then insert
like
Alter table TABLE_COPY_TO Add (fieldC AS (fieldA + '-' + fieldB))
insert into.......

Related

Concatenate results in select

I am trying to insert values into a table that come from an other (lookup) table.
The first 3 results from the table are selected and need to be concatenated before they are inserted into an other table.
How can I alter the following insert to first concatenates them with no separation characters between the 3 names (example: JohnMaxLouise)?
INSERT INTO Table 2 VALUES ((SELECT TOP 3 names FROM Table1 ORDER BY NEWID()))
I am using SQL Server 2016 so string_agg is not available.
Personally, I think this is simplest with conditional aggregation:
INSERT INTO Table2
SELECT (MAX(CASE WHEN seqnum = 1 THEN name ELSE '' END) +
MAX(CASE WHEN seqnum = 2 THEN name ELSE '' END) +
MAX(CASE WHEN seqnum = 3 THEN name ELSE '' END)
)
FROM (SELECT name, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as seqnum
FROM (SELECT TOP 3 name
FROM Table1
ORDER BY NEWID()
) t
) t;
An alternative is an XML approach, but if you know you want three, then conditional aggregation (or pivot) works fine.
try the following:
declare #tab table (names varchar(max))
declare #tab1 table ([name] varchar(100))
insert into #tab1
select 'John' union select 'Max' union select 'Louise' union select 'xxx'
insert into #tab select (select top 3 [name] + '' from #tab1 for xml path(''))
select * from #tab
Thanks.
This will return '1,2,3,4'
DECLARE #x TABLE (i INTEGER)
DECLARE #r VARCHAR(255)
INSERT INTO #x VALUES (1),(3),(2),(4)
SELECT #r= STUFF(( SELECT ',' + CAST(i AS VARCHAR(max))
FROM #x
ORDER BY i
FOR XML PATH(''), type
).value('.','varchar(255)'), 1, 1, '')
SELECT #r
Solution Overview
You can use FOR XML PATH('') to achieve this, just use the following command:
SELECT '' + NAME
FROM (SELECT Top 3 NAME FROM TBL_1 ORDER BY NEWID()) AS T
FOR XML PATH('')
Or simple concatenation
SELECT #x = #x + NAME
FROM (SELECT Top 3 NAME FROM TBL_1 ORDER BY NEWID()) AS T1
Detailed Solution
SQLFiddle Demo
First i created the test environment using the following query
CREATE TABLE TBL_1 (NAME Varchar(50))
CREATE TABLE TBL_2 (NAME Varchar(50))
INSERT INTO TBL_1 (Name) VALUES ('John'),('Max'),('Louise'),('Mark'),('Peter')
Then i Used the following command
DECLARE #x varchar(255)
SELECT #x = (SELECT '' + NAME
FROM (SELECT Top 3 NAME FROM TBL_1 ORDER BY NEWID()) AS T1
FOR XML PATH('') )
INSERT INTO TBL_2(NAME) SELECT #x;
SELECT * FROM TBL_2
And the Result is JohnLouiseMax
Or you can use simple concatenation to achieve this
SQLFiddle Demo
DECLARE #x varchar(255)
SET #x = ''
SELECT #x = #x + NAME
FROM (SELECT Top 3 NAME FROM TBL_1 ORDER BY NEWID()) AS T1
INSERT INTO TBL_2(NAME) SELECT #x;
SELECT * FROM TBL_2

SQL Server multiple REPLACE with #temp table

I am trying to REPLACE multiple characters in SQL Server query and want to achieve this via #temp table instead of nested REPLACE. I got SQL code below and want to achieve result like
ABC DEF GHI
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL DROP TABLE #temp2
CREATE TABLE #temp
(
STRING_TO_REPLACE NVARCHAR(5)
)
INSERT INTO #temp (STRING_TO_REPLACE)
VALUES (' ')
,('/')
,('_')
CREATE TABLE #temp2
(
STRING_NAME NVARCHAR(5)
)
INSERT INTO #temp2 (STRING_NAME)
VALUES ('A BC')
,('D/EF')
,('G_HI')
SELECT REPLACE(t2.STRING_NAME,(SELECT t1.STRING_TO_REPLACE
FROM #temp t1),'')
FROM #temp2 t2
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL DROP TABLE #temp2
I can achieve result with nested replace
SELECT REPLACE(REPLACE(REPLACE(t2.STRING_NAME,'_',''),'/',''),' ','') FROM #temp2 t2
but would really like to do this via #temp table. Please can someone help me on this.
When I try to run my first code I get the following error
Msg 512, Level 16, State 1, Line 23 Subquery returned more than 1
value. This is not permitted when the subquery follows =, !=, <, <= ,
, >= or when the subquery is used as an expression.
Here is one way using CROSS APPLY
SELECT result
FROM #temp2 t2
CROSS apply (SELECT Replace(string_name, t1.string_to_replace, '') AS
result
FROM #temp t1) cs
WHERE result <> string_name
Result :
result
-----
ABC
DEF
GHI
Note : This will work only if the each string_name has only one string_to_replace
Update : To handle more than one string_to_replace in a single string_name here is one way using Dynamic sql
I have made one small change to the #temp table by adding a identity property to loop
IF Object_id('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
IF Object_id('tempdb..#temp2') IS NOT NULL
DROP TABLE #temp2
CREATE TABLE #temp
(
id INT IDENTITY(1, 1),
string_to_replace NVARCHAR(5)
)
INSERT INTO #temp
(string_to_replace)
VALUES (' '),
('/'),
('_')
CREATE TABLE #temp2
(
string_name NVARCHAR(5)
)
INSERT INTO #temp2
(string_name)
VALUES ('A BC'),
('D/EF'),
('G_HI'),
('A BD_')
DECLARE #col_list VARCHAR(8000)= '',
#sql VARCHAR(max),
#cntr INT,
#inr INT =1,
#STRING_TO_REPLACE NVARCHAR(5)
SELECT #cntr = Max(id)
FROM #temp
SET #sql = 'select '
WHILE #inr < = #cntr
BEGIN
SELECT #STRING_TO_REPLACE = string_to_replace
FROM #temp
WHERE id = #inr
IF #inr = 1
SET #col_list = 'replace (STRING_NAME,'''
+ #STRING_TO_REPLACE + ''','''')'
ELSE
SET #col_list = 'replace (' + #col_list + ','''
+ #STRING_TO_REPLACE + ''','''')'
SET #inr+=1
END
SET #sql += ' from #temp2'
--print #col_list
SET #sql = 'select ' + #col_list + ' as Result from #temp2'
--print #sql
EXEC (#sql)
Result :
Result
------
ABC
DEF
GHI
ABD
The multiple replace can be achieved via a recursive CTE as per following example:
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL DROP TABLE #temp2
CREATE TABLE #temp
(
STRING_TO_REPLACE NVARCHAR(10)
,Pattern NVARCHAR(10)
)
INSERT INTO #temp (STRING_TO_REPLACE, Pattern)
VALUES (' ', '% %')
,('/', '%/%')
,('_', '%[_]%') ;
CREATE TABLE #temp2
(
STRING_NAME NVARCHAR(10)
);
INSERT INTO #temp2 (STRING_NAME)
VALUES ('A BC')
,('D/EF_F E')
,('G_HI')
,('XYZ');
WITH CTE_Replace AS
(
SELECT STRING_NAME AS OriginalString
,CAST(STRING_NAME AS NVARCHAR(10)) AS ReplacedString
,CAST('' AS NVARCHAR(10)) AS StringToReplace
,1 AS ReplaceCount
FROM #temp2 ancor
UNION ALL
SELECT CTE_Replace.OriginalString
,CAST(REPLACE(CTE_Replace.ReplacedString, rep.STRING_TO_REPLACE, '') AS NVARCHAR(10)) AS ReplacedString
,CAST(rep.STRING_TO_REPLACE AS NVARCHAR(10)) AS StringToReplace
,CTE_Replace.ReplaceCount + 1 AS ReplaceCount
FROM #temp rep
INNER JOIN CTE_Replace ON CTE_Replace.ReplacedString LIKE rep.Pattern
)
,CTE_FinalReplacedString AS
(
SELECT OriginalString
,ReplacedString
,ReplaceCount
,ROW_NUMBER() OVER (PARTITION BY OriginalString ORDER BY ReplaceCount DESC) AS [Rank]
FROM CTE_Replace
)
SELECT *
FROM CTE_FinalReplacedString
WHERE [Rank] = 1
Note that #temp table was updated to include an extra column called Pattern, this column contains the search pattern to use in order to find the specific strings that has to be replaced. This was also done to simplify the join statement in the recursive CTE. Also note that in order to find the _ character the search pattern had to be updated as '%[_]%'. The reason for this is because SQL Server will interpret the _ character as a wild character instead of a specific character we are trying to find.
replace in the table is probably easier here
update t2
set t2.string_name = Replace(t2.string_name, t1.string_to_replace, '')
from #temp2 t2
cross join #temp1 t1
A simple way to deal with this is to download a copy of PatExclude8K, a T-SQL function designed for exactly this type of task. Here's a couple examples:
-- remove all non-aplphabetical characters
SELECT NewString FROM #temp2 CROSS APPLY dbo.PatExclude8K(STRING_NAME,'[^A-Z]');
-- remove all spaces, forward slashes and underscores
SELECT NewString FROM #temp2 CROSS APPLY dbo.PatExclude8K(STRING_NAME,'[ /_]');
Both queries produce this result set:
NewString
------------
ABC
DEF
GHI
I've found below code on stackoverflow which seems more near to what I'm trying to achieve but am struggling that how can I use this with my code
declare #String varchar(max) = '(N_100-(6858)*(6858)*N_100/0_2)%N_35'
--table containing values to be replaced
create table #Replace
(
StringToReplace varchar(100) not null primary key clustered
,ReplacementString varchar(100) not null
)
insert into #Replace (StringToReplace, ReplacementString)
values ('+', '~')
,('-', '~')
,('*', '~')
,('/', '~')
,('%', '~')
,('(', '~')
,(')', '~')
select #String = replace(#String, StringToReplace, ReplacementString)
from #Replace a
select #String
drop table #Replace
EDIT by gofr1
CREATE FUNCTION replacement
(
#String nvarchar(max)
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #Replace TABLE (
StringToReplace nvarchar(100),
ReplacementString nvarchar(100)
)
INSERT INTO #Replace (StringToReplace, ReplacementString)
VALUES ('+', '~')
,('-', '~')
,('*', '~')
,('/', '~')
,('%', '~')
,('(', '~')
,(')', '~')
SELECT #String = replace(#String, StringToReplace, ReplacementString)
FROM #Replace
RETURN #String
END
GO
Then call it:
SELECT dbo.replacement ('A B-C/d')
Output:
A B~C~d
Another way with recursive CTE (full batch below):
--Create a sample table, you should use YourTable
CREATE TABLE #temp2 (
STRING_NAME NVARCHAR(max)
)
INSERT INTO #temp2 (STRING_NAME)
VALUES ('A BC'),('D/EF'),('G_HI'),('J_K/L_'),('MNO')
--I add some more objects here
The main query:
;WITH replacement AS (
SELECT *
FROM (VALUES (' '),('/'),('_')
) as t(STRING_TO_REPLACE)
), cte AS (
SELECT STRING_NAME,
STRING_NAME as OriginalString,
ROW_NUMBER() OVER (ORDER BY STRING_NAME) as rn,
1 as [Level]
FROM #temp2 t2
UNION ALL
SELECT REPLACE(c.STRING_NAME,t.STRING_TO_REPLACE,'~'),
c.OriginalString,
c.rn,
[Level]+1
FROM cte c
INNER JOIN replacement t
ON CHARINDEX(t.STRING_TO_REPLACE,c.STRING_NAME,0) > 0
)
SELECT TOP 1 WITH TIES OriginalString,
STRING_NAME
FROM cte
ORDER BY ROW_NUMBER() OVER (PARTITION BY rn ORDER BY [Level] DESC)
OPTION (MAXRECURSION 0)
Output:
OriginalString STRING_NAME
A BC A~BC
D/EF D~EF
J_K/L_ J~K~L~
G_HI G~HI
MNO MNO

How to retrieve data from multiple daily tables in SQL Server

I am new to Microsoft SQL Server 2008. I need some help in retrieving data from multiple tables which have same columns with same datatype.
Currently I have multiple tables in my database, each table contains data for a single day. For example, I have the following tables:
Table_20160820
Table_20160821
Table_20160822
Table_20160823
Table_20160824
Table_20160825
All the tables have same columns with same datatype say:
column_1
column_2
column_3
column_4
Now, is it possible to retrieve the columns from all the tables using a single query.
What I am exactly looking for is to get the count of column_3 which will be grouped by column_1 for all the tables. I want to avoid writing multiple queries as I need to fetch the data for an entire month i.e. from 30 tables.
As the comments have said, it'd be best to organize this into a single table, and then add a date column so you can see when each row was added. You can even set the default value of the date_created column to be getdate().
To get all of the data together use the UNION Operator in conjunction with an insert statement to your new better table.
CREATE TABLE Better_Table (Column_1 <type>, Column_2 <type>, ..., Date_Created DateTime2(7) Default GETDATE())
GO
INSERT INTO Better_Table (Column_1, Column_2, ...)
SELECT *, '2016/08/22' FROM Table_20160822
UNION
SELECT *, '2016/08/23' FROM Table_20160823
...
Once you've done this your query will be far simpler:
SELECT Column_1, COUNT(Column_3)
FROM Better_Table
WHERE date_created >= '2016/08/01'
AND date_created < '2016/09/01'
GROUP BY Column_1
Although you can still do this in the db current state by using the union operator in the group by query. It would look something like this:
SELECT a.column_1, COUNT(a.column_3)
FROM (
SELECT column_1, Column_3
FROM Table_20160822
UNION ALL
SELECT column_1, Column_3
FROM Table_20160823
) a
GROUP BY a.Column_1
Just expand this to the tables you need to grab from.
You can use CTE to get all dates you need, then create a query and execute it:
DECLARE #dateStart datetime = '2016-08-01',
#dateFinish datetime = '2016-09-01',
#query nvarchar(max)
;WITH cte as (
SELECT #dateStart as d
UNION ALL
SELECT DATEADD(day,1,d)
FROM cte
WHERE DATEADD(day,1,d) < #dateFinish
)
SELECT #query = (
SELECT 'SELECT column_1, COUNT(column_3) FROM ( '
+ STUFF((
SELECT ' UNION ALL SELECT * FROM [Table_'+CONVERT(nvarchar(8),d,112)+'] '
FROM cte
FOR XML PATH('')
),1,10,'') + ') as t GROUP BY column_1 '
FOR XML PATH('')
)
PRINT #query
EXEC sp_executesql #query
Output of #query variable:
SELECT column_1, COUNT(column_3)
FROM (
SELECT * FROM [Table_20160801] UNION ALL
SELECT * FROM [Table_20160802] UNION ALL
SELECT * FROM [Table_20160803] UNION ALL
SELECT * FROM [Table_20160804] UNION ALL
....
SELECT * FROM [Table_20160831]
) as t
GROUP BY column_1
As mentioned above this is a bad design and it would be good if the design is fixed at first place. This is a technical debt which has to be paid back sooner or later. Anyway here's how you can do it:
DECLARE #dtStartDate DATE,
#dtEndDate DATE,
#nvchTablePrefix NVARCHAR(50),
#nvchQuery NVARCHAR(max)
SELECT #dtStartDate = '20160801',
#dtEndDate = '20160831',
#nvchTablePrefix = 'Table_'
CREATE TABLE #temp
(name NVARCHAR(100))
WHILE(#dtStartDate <= #dtEndDate)
BEGIN
INSERT INTO #temp
SELECT ' SELECT column_1, column_3 FROM ' + #nvchTablePrefix + CONVERT(NVARCHAR,#dtStartDate,112)
SELECT #dtStartDate = DATEADD(d,1,#dtStartDate)
IF (#dtStartDate <= #dtEndDate)
INSERT INTO #temp
SELECT ' UNION '
END
SELECT #nvchQuery = 'SELECT column_1, COUNT(column_3) as column3count FROM ( '
SELECT #nvchQuery = #nvchQuery + name FROM #temp
SELECT #nvchQuery = #nvchQuery + ') t GROUP BY column1'
PRINT #nvchQuery
EXEC sp_executesql #nvchQuery
DROP TABLE #temp
OUTPUT of print statement will be like -
SELECT column_1, COUNT(column_3) as column3count
FROM
( SELECT column_1, column_3 FROM Table_20160801
UNION
SELECT column_1, column_3 FROM Table_20160802
UNION
....
UNION
SELECT column_1, column_3 FROM Table_20160831
) t GROUP BY column1

Computed column does not include one of the values

I'm trying to create a computed column in order to have a unique index on a nullable column that ignores NULL rows1. I've composed this test case:
SELECT TEST_ID, CODE, UNIQUE_CODE, CAST(UNIQUE_CODE AS VARBINARY(4000)) AS HEX
FROM (
SELECT TEST_ID, CODE,
ISNULL(CODE, CONVERT(VARCHAR, SPACE(10)) + CONVERT(VARCHAR, TEST_ID)) AS UNIQUE_CODE
FROM (
SELECT 1 AS TEST_ID, 'ABCDEFGHIJ' AS CODE
UNION ALL
SELECT 2, 'XYZ'
UNION ALL
SELECT 3, NULL
) TEST
) X;
It works as expected when CODE is not null but I only get a string of whitespaces when CODE is null (i.e., the trailing TEST_ID is missing):
TEST_ID | CODE | UNIQUE_CODE | HEX
--------+------------+-------------+-----------------------
1 | ABCDEFGHIJ | ABCDEFGHIJ | 0x4142434445464748494A
2 | XYZ | XYZ | 0x58595A
3 | NULL | | 0x20202020202020202020
The funny thing is that I already use this technique successfully in another table and I can't spot the difference:
CREATE TABLE SOME_OTHER_TABLE (
SOME_OTHER_TABLE_ID INT IDENTITY(1, 1) NOT NULL,
NOMBRE VARCHAR(50),
-- This works just fine:
NOMBRE_UNICO AS ISNULL(NOMBRE, CONVERT(VARCHAR, SPACE(50)) + CONVERT(VARCHAR, SOME_OTHER_TABLE_ID)),
CONSTRAINT SOME_OTHER_TABLE_PK PRIMARY KEY (SOME_OTHER_TABLE_ID)
);
What am I missing?
(1) This was a workaround for SQL Server 2005 that's no longer necessary in later versions thanks to filtered indexes.
There you go with " 3"
SELECT TEST_ID, CODE, UNIQUE_CODE, CAST(UNIQUE_CODE AS VARBINARY(4000)) AS HEX
FROM (
SELECT TEST_ID, CODE,
ISNULL(CODE, CONVERT(VARCHAR(20), SPACE(10)) + CONVERT(VARCHAR(20), TEST_ID)) AS UNIQUE_CODE
FROM (
SELECT 1 AS TEST_ID, cast('ABCDEFGHIJ' as varchar(20)) AS CODE
UNION ALL
SELECT 2, 'XYZ'
UNION ALL
SELECT 3, NULL
) TEST
) X;
'ABCDEFGHIJ' (first value in the union list) is exactly 10 characters and this column is a first argument of IsNull. So it takes 10 characters as size for IsNull result. Which is enough only for spaces. Replacing this constant with 'ABCDEFGHIJKLMNOPQR' would do the trick also.
It looks like SQL is trying to help out to define the column length in your inner query. By casting/converting to a specific size this fixes the problem. Once your UNIQUE_CODE field exceeds this value, the returned value is limited to the size of the column.
SELECT TEST_ID, CODE, UNIQUE_CODE, CAST(UNIQUE_CODE AS VARBINARY(4000)) AS HEX
FROM (
SELECT TEST_ID, CODE,
ISNULL(CODE, CONVERT(VARCHAR, SPACE(10)) + CONVERT(VARCHAR, TEST_ID)) AS UNIQUE_CODE
FROM (
SELECT 1 AS TEST_ID, CONVERT(VARCHAR(50), 'ABCDEFGHIJ') AS CODE
UNION ALL
SELECT 2, 'XYZ'
UNION ALL
SELECT 3, NULL
) TEST
) X;
You can run below piece of code to find out why ?
this fails :
declare #a char(20)
set #a=null
declare #b char(10)
set #b='aaaaaa'
select isnull(#a,convert(char(10),space(10)+#b))
This works:
declare #a char(20)
set #a=null
declare #b char(10)
set #b='aaaaaa'
select isnull(#a,convert(char(30),space(10)+#b))

Write a T-SQL query that filters records containing NCHAR(2028)

My ultimate goal is to write a sql script that selects data from a particular table where a nvarchar(max) column contains the character NCHAR(2028).
But the obvious:
select *
from tablename
where columnname like '%' + NCHAR(2028) + '%'
returns all rows.
Use a binary collation for your like comparison.
select *
from tablename
where columnname COLLATE Latin1_General_Bin like '%' + NCHAR(2028) + '%'
This works
CREATE TABLE #temp (columnname NVARCHAR(128))
INSERT #temp VALUES ('a')
INSERT #temp VALUES ( NCHAR(2028))
INSERT #temp VALUES ('b')
INSERT #temp VALUES ('c' + NCHAR(2028) + 'c')
INSERT #temp VALUES ('a' + NCHAR(2028) + 'b')
SELECT *
FROM #temp
WHERE ColumnName COLLATE Latin1_General_Bin Like N'%' + NCHAR(2028) + '%'
drop table #temp
I think you're hitting limitations on characters that are outside of your collation. I had some weird behavior. Notice the result of the two SELECTs here:
CREATE TABLE dbo.foo
(
id INT IDENTITY(1,1),
bar NVARCHAR(128)
);
INSERT dbo.foo(bar) SELECT N'foobar'
UNION
SELECT N'foo' + NCHAR(2028) + N'bar'
SELECT *
FROM dbo.foo
WHERE bar LIKE N'%' + NCHAR(2028) + '%';
TRUNCATE TABLE dbo.foo;
INSERT dbo.foo(bar)
SELECT N'foo' + NCHAR(2028) + N'bar'
SELECT *
FROM dbo.foo
WHERE bar LIKE N'%' + NCHAR(2028) + '%';
DROP TABLE dbo.foo;
Notice that whether we've inserted one row or two, we always get the first row back, even though the query is the same and the data has changed.
Unfortunately pasting the actual value of NCHAR(2028) into SSMS doesn't work because it is not in the set of supported characters (I get a glyph like a question mark box in Super Mario Brothers). Otherwise I would just suggest:
WHERE columnname LIKE N'%߬%';
If you can do that from your code (and not worry about SSMS), it may be a workable alternative.