Related
I am trying to write a query that needs to call a stored procedure. But it always throws an error:
Unknown object type 'TABLEIXICHistoricalData' used in a CREATE, DROP, or ALTER statement.
This is query:
USE ETLCourse
DECLARE #LOOP TABLE
(
ID INT IDENTITY(1,1),
TableName NVARCHAR(100)
)
INSERT INTO #LOOP (TableName)
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE '%_Stocks%'
DECLARE #b INT = 1, #m INT, #t NVARCHAR(100)
SELECT #m = MAX(ID) FROM #LOOP
WHILE #b <= #m
BEGIN
SELECT #t = TableName
FROM #LOOP
WHERE ID = #b
EXECUTE [dbo].[stp_BuildNormalizedTable] #t
SET #b = #b + 1
END
and here is the procedure:
ALTER PROCEDURE [dbo].[stp_BuildNormalizedTable]
#table NVARCHAR(100)
AS
BEGIN
DECLARE #cleanTable NVARCHAR(100),
#s NVARCHAR(MAX)
SET #cleanTable = REPLACE(#table, '_Stocks', 'HistoricalData')
SET #s = 'CREATE TABLE' + #cleanTable + '(ID INT IDENTITY(1,1), Price DECIMAL(13, 4), PriceDate DATE)
INSERT INTO' + #cleanTable + '(Price,PriceDate) SELECT [Adj Close],[Date] FROM'
+ #table + ' ORDER BY Date ASC'
--PRINT #s
EXECUTE sp_executesql #s
END
It should copy two specific column and create a new table by using #Loop table and procedure
You need to add 'space' after 'create table' and 'insert into' and 'from'
declare #s nvarchar(max)
declare #cleantable nvarchar(100)
declare #table nvarchar(100)
set #cleantable = 'aaa'
set #table = 'bbb'
SET #s = 'CREATE TABLE' + #cleanTable + '(ID INT IDENTITY(1,1),Price Decimal(13,4),PriceDate DATE)
Insert into' + #cleanTable
+ '(Price,PriceDate) SELECT [Adj Close],[Date] FROM'
+ #table + ' ORDER BY Date ASC'
print #s
Output:
CREATE TABLEaaa(ID INT IDENTITY(1,1),Price Decimal(13,4),PriceDate DATE)
Insert intoaaa(Price,PriceDate) SELECT [Adj Close],[Date] FROMbbb ORDER BY Date ASC
Use 'print' to check your query.
If I have a table with a column named "Ids" and another column named "clients" and I have the following data:
IDs Clients
----------------
1 A
2 B
3 C
4 D
I'm trying to get the IDs for multiple clients without writing a query for each one.
So I am using:
select ids
from table1
where clients in (A,B,E,C)
This returns
1,2,3
But I need to know that E is missing. So I really need
1,2,NULL,3
How can I accomplish this?
You can inline the values
declare #t table (id int, client char(1))
insert into #t values
(1, 'A'),
(2, 'B'),
(3, 'C'),
(4, 'D');
select *
from #t;
SELECT q.client, t.id
FROM ( values ('A'), ('B'), ('E') ) q(client)
left join #t t
on t.client = q.client;
Don't use plural for columns. A row is singular.
Insert your input to a table and proceed your query as below
declare #tbl table (ids int, clients varchar(10))
insert into #tbl values
(1,'A')
,(2,'B')
,(3,'C')
,(4,'D')
SELECT * FROM #tbl
declare #value table (Val varchar(10))
insert into #value values
('A')
,('B')
,('C')
,('E')
select ids,t1.Val
from #tbl t
RIGHT JOIN #value t1 on t1.Val = t.clients
Alertnate option.
declare #tbl table (ids int, clients varchar(10))
insert into #tbl values
(1,'A')
,(2,'B')
,(3,'C')
,(4,'D')
For SQL Server 2016 and above
Declare #var varchar(50) = 'A,B,C,E'
SELECT ids, t1.value
FROM #tbl t
RIGHT JOIN STRING_SPLIT(#var, ',') t1 on t1.value = t.clients
For lower than SQL Server 2016 use below query. You have to create strparse function. script given below
SELECT ids, t1.Keys
FROM #tbl t
RIGHT JOIN dbo.StrParse (',', #var) t1 on t1.Keys = t.clients
go
Create the function for parsing string to rows
CREATE FUNCTION [dbo].[StrParse]
(#delimiter CHAR(1),
#csv NTEXT)
RETURNS #tbl TABLE(Keys NVARCHAR(255))
AS
BEGIN
DECLARE #len INT
SET #len = Datalength(#csv)
IF NOT #len > 0
RETURN
DECLARE #l INT
DECLARE #m INT
SET #l = 0
SET #m = 0
DECLARE #s VARCHAR(255)
DECLARE #slen INT
WHILE #l <= #len
BEGIN
SET #l = #m + 1--current position
SET #m = Charindex(#delimiter,Substring(#csv,#l + 1,255))
IF #m <> 0
SET #m = #m + #l
--insert #tbl(keys) values(#m)
SELECT #slen = CASE
WHEN #m = 0 THEN 255
ELSE #m - #l
END
IF #slen > 0
BEGIN
SET #s = Substring(#csv,#l,#slen)
INSERT INTO #tbl
(Keys)
SELECT #s
END
SELECT #l = CASE
WHEN #m = 0 THEN #len + 1
ELSE #m + 1
END
END
RETURN
END
I have a temp table that has all the Names of Column for now there are 4 of them and I am looping through the temp table and do a Insert into another table, Now the issue is that the Column name that I want to insert depends on what it gets from the loop:
Here is the code:
Declare #OutputTable table
(
RowID int IDENTITY(1, 1),
ClientID int,
ClientName VarChar(100),
ScoreModule1 VarChar(100),
ScoreModule2 VarChar(100),
ScoreModule3 VarChar(100),
ScoreModule4 VarChar(100)
)
Declare #TempModuleNumber table
(
RowIDNumber int IDENTITY(1, 1),
ModuleNumber varchar (300)
)
INSERT INTO #TempModuleNumber(ModuleNumber)
VALUES ('ScoreModule1'), ('ScoreModule2'),
('ScoreModule3'), ('ScoreModule4')
Declare #ModuleRowCountNumber int
Declare #ModuleCounterNumber int
Declare #ModuleNumber varchar(300)
Select #ModuleRowCount = COUNT(#ModuleNumber)
from #TempModuleNumber
set #ModuleCounterNumber = 1
while #ModuleCounterNumber <= #ModuleRowCount
begin
Select #ModuleNumber = ModuleNumber
from #TempModuleNumber
where RowIDNumber = #ModuleCounterNumber
Insert into #OutputTable (ClientID, ClientName, #ModuleNumber) --This is where the problem is the #ModuleNumber is. I get a "syntax error". Is there another way of doing this to it depends on what it gets from the Loop?
Set #ModuleCounterNumber = #ModuleCounterNumber + 1
END
I only made change to the necessary part:
while #ModuleCounterNumber <= #ModuleRowCount
begin
Select #ModuleNumber = ModuleNumber
from #TempModuleNumber
where RowIDNumber = #ModuleCounterNumber
DECLARE #SQL VARCHAR(MAX)
SET #SQL =
'
Insert into #OutputTable (ClientID, ClientName, ' + #ModuleNumber + ')
SELECT A,B,C //<----------- THIS IS THE MISSING PART I WAS TALKING ABOUT
'
EXEC(#SQL)
Set #ModuleCounterNumber = #ModuleCounterNumber + 1
END
I have an SP which accepts the Inputtable as parameter, My Inputtable is as shown in the code
`create table inputTable ( id int,ItemQty varchar(100))
insert into inputTable(id, ItemQty) values(1,'a,b,c')
insert into inputTable(id, ItemQty) values(2,'x,y')
insert into inputTable(id, ItemQty) values(3,'l,m,n,o,p')
insert into inputTable(id, ItemQty) values(4,'a,b')
insert into inputTable(id, ItemQty) values(5,'m')`
and SP i have written is like below
`ALTER PROCEDURE [dbo].[Column_Dynamics] (#tablename varchar(50))
AS
BEGIN
-----
declare #maxcount as int
set #maxcount='select MAX(len(ITEMQTY) - len(replace(ITEMQTY, '','', '''')) +1) from '+#tablename
exec('select MAX(len(ITEMQTY) - len(replace(ITEMQTY, '','', '''')) +1) from '+#tablename)
print #maxcount
exec #maxcount
print #maxcount
declare #var varchar(100)
IF EXISTS(SELECT * FROM sys.columns WHERE object_id = Object_id(#tablename))
set #var='alter table '+ #tablename +' ADD column QTY1'
exec(#var)
select * from #tablename
select max(len(ItemQty))-max(len(replace(ItemQty, ',', ''))-1) from inputtable
END`
My table is :
step 1 ) I want to add the columns dynamically to inputtable like QTY1,QTY2,QTY3,QTY4,QTY5 because maximum count of ItemQty column is 5, by considering comma as delimiter as shown in figure1
**step 2) ** Parse values in the respective columns(by considering the delimiter comma (,).as shown in figure2
Later SP: I got till here, But Not getting the second step, that is update Parse values in the respective columns.
ALTER PROCEDURE dynamic_tbl (#tablename varchar(50))
AS
BEGIN
DECLARE #ColumnCount int
DECLARE #rowcount TABLE (Value int);
INSERT INTO #rowcount
EXEC('select MAX(len(ITEMQTY) - len(replace(ITEMQTY, '','', '''')) +1) from '+#tablename);
SELECT #ColumnCount = Value FROM #rowcount;
Declare #ColumnName nvarchar(10)='qty_'
Declare #count int =0
IF(#ColumnCount>0)
BEGIN
IF (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'dyn_tbl'))
BEGIN
DROP TABLE dyn_tbl
END
select * into dyn_tbl from inputtable
SET #count=#count +1;
WHile(#ColumnCount>=#count)
BEGIN
SET #ColumnName='qty_'+CONVERT(varchar(2),#count)
EXEC ('ALTER TABLE dyn_tbl ADD ['+#ColumnName +'] varchar(20)')
declare #myvar as varchar(max)
set #myvar='update '+#tablename+' set '+#ColumnName +' =itemQty'
--exec dynamic_tbl 'dyn_tbl'
--select * from dyn_tbl
--CAST('<A>'+REPLACE(ITEMQTY, ',', '</A><A>')+'</A>' AS XML)
print #myvar
exec(#myvar)
SET #count=#count +1;
END
END
----
END
Procedure to alter given table dynamically based on column length as you have asked
Alter PROCEDURE [dbo].[Column_Dynamics] (#tablename varchar(50))
AS
BEGIN
drop table ##temp
declare #query1 varchar(max)
exec ( '
create table ##temp (id int identity,columnsl varchar(100))
declare #maxcount as int
set #maxcount = (select MAX(len(ITEMQTY) - len(replace(ITEMQTY, '','', '''')) +1) from '+#tablename+')
declare #count int = 1
while (#count <= #maxcount)
begin
declare #colvar nvarchar(100)= ''QTY''
set #colvar = concat(#colvar,#count)
set #count = #count + 1
insert into ##temp select #colvar
end
')
declare #tempstart int = 1
declare #templast int = (select count(*) from ##temp)
declare #updatecol varchar(100) = ''
while (#tempstart <= #templast)
Begin
set #updatecol = (select columnsl from ##temp where id = #tempstart)
exec ('alter table '+#tablename+' Add '+#updatecol+' varchar(100) ')
set #tempstart = #tempstart + 1
end
End
output for inputTable:
id ItemQty QTY1 QTY2 QTY3 QTY4 QTY5
1 a,b,c NULL NULL NULL NULL NULL
2 x,y NULL NULL NULL NULL NULL
3 l,m,n,o,p NULL NULL NULL NULL NULL
4 a,b NULL NULL NULL NULL NULL
5 m NULL NULL NULL NULL NULL
may not be the best way but works.
edit
Altered above procedure to perform both actions, Please use below procedure
Alter PROCEDURE [dbo].[Column_Dynamics] (#tablename varchar(50))
AS
BEGIN
-- declare #tablename varchar(100) = 'inputTable'
drop table #temp if object_id('temp..#temp') is not null drop table #temp
declare #query1 varchar(max)
create table #temp (id int identity,columnsl varchar(100))
exec ( '
declare #maxcount as int
set #maxcount = (select MAX(len(ITEMQTY) - len(replace(ITEMQTY, '','', '''')) +1) from '+#tablename+')
declare #count int = 1
while (#count <= #maxcount)
begin
declare #colvar nvarchar(100)= ''QTY''
set #colvar = concat(#colvar,#count)
set #count = #count + 1
insert into #temp
select #colvar
end
')
declare #tempstart int = 1
declare #templast int = (select count(*) from #temp)
declare #updatecol varchar(100) = ''
declare #itemqty varchar(100)
while (#tempstart <= #templast)
Begin
set #updatecol = (select columnsl from #temp where id = #tempstart)
exec ('alter table '+#tablename+' Add '+#updatecol+' varchar(100) ')
set #tempstart = #tempstart + 1
end
declare #sysvar table (id int identity,cols varchar(100))
insert into #sysvar select sys.columns.name AS ColumnName FROM sys.columns JOIN sys.tables ON sys.columns.object_id = sys.tables.object_id WHERE sys.tables.name = 'inputTable'
declare #finvar table (id int identity,cols varchar(100))
insert into #finvar select cols from #sysvar where id not in (1,2)
declare #cat int = 1 declare #dog int = (select max(id) from inputTable)
while (#cat <= #dog)
begin
drop table #tab2
if object_id('temp..#tab2') is not null drop table #tab2
create table #tab2 (id int identity,fnvalues varchar(100))
set #itemqty = (select itemqty from inputTable where id = #cat)
insert into #tab2 select item from [dbo].[fnSplit](#itemQty,',')
declare #cn int = 1
declare #max int = (select max(id) from #tab2)
declare #sql nvarchar (1000);
while (#cn <= #max)
begin
declare #upcol varchar(100) = (select fnvalues from #tab2 where id = #cn)
declare #plscol varchar(100) = (select cols from #finvar where id = #cn)
set #sql = N'update '+#tablename+' set ' + #plscol + '= '''+#upcol+''' where id = '''+cast(#cat as varchar(10))+''' ';
select #sql
exec sp_executesql #sql;
set #cn = #cn + 1
end
set #cat = #cat + 1
End
End
output:
id ItemQty QTY1 QTY2 QTY3 QTY4 QTY5
1 a,b,c a b c NULL NULL
2 x,y x y NULL NULL NULL
3 l,m,n,o,p l m n o p
4 a,b a b NULL NULL NULL
5 m m NULL NULL NULL NULL
did not optimize the query but works fine.
However, if you have maximum Qty's are known which are in comma separated format then you could use xml node method to separate them into columns.QTY1...QTY5
SELECT DISTINCT ID, ITEMQTY,
a.value('/A[1]', 'VARCHAR(MAX)') as QTY1,
a.value('/A[2]', 'VARCHAR(MAX)') as QTY2,
a.value('/A[3]', 'VARCHAR(MAX)') as QTY3,
a.value('/A[4]', 'VARCHAR(MAX)') as QTY4,
a.value('/A[5]', 'VARCHAR(MAX)') as QTY5
FROM
(
SELECT ID, ITEMQTY,
CAST('<A>'+REPLACE(ITEMQTY, ',', '</A><A>')+'</A>' AS XML) AS ITEMQTY1
FROM inputTable
) A
CROSS APPLY ITEMQTY1.nodes('/A') AS split(a);
Result :
ID ITEMQTY QTY1 QTY2 QTY3 QTY4 QTY5
1 a,b,c a b c NULL NULL
2 x,y x y NULL NULL NULL
3 l,m,n,o,p l m n o p
4 a,b a b NULL NULL NULL
5 m m NULL NULL NULL NULL
Later, you could replace null by using coalesce() or isnull() function with ''
Use This
First Create a function
CREATE FUNCTION [dbo].[fn_split](
#str VARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #returnTable TABLE (idx INT PRIMARY KEY IDENTITY, item VARCHAR(8000))
AS
BEGIN
DECLARE #pos INT
SELECT #str = #str + #delimiter
WHILE LEN(#str) > 0
BEGIN
SELECT #pos = CHARINDEX(#delimiter,#str)
IF #pos = 1
INSERT #returnTable (item)
VALUES (NULL)
ELSE
INSERT #returnTable (item)
VALUES (SUBSTRING(#str, 1, #pos-1))
SELECT #str = SUBSTRING(#str, #pos+1, LEN(#str)-#pos)
END
RETURN
END
GO
and use function like this
Declare #test TABLE (
ID VARCHAR(200),
Data VARCHAR(200)
)
insert into #test
(ID, Data)
Values
(1,'a,b,c')
insert into #test
(ID, Data )
values(2,'x,y')
insert into #test
(ID, Data )
values(3,'l,m,n,o,p')
insert into #test
(ID, Data )
values(4,'a,b')
insert into #test
(ID, Data )
values(5,'m')
select ID,data AS ItemQty,
ISNULL((select item from fn_split(Data,',') where idx in (1)),'') as QTY1 ,
ISNULL((select item from fn_split(Data,',') where idx in (2)),'') as QTY2,
ISNULL((select item from fn_split(Data,',') where idx in (3)),'') as QTY3,
ISNULL((select item from fn_split(Data,',') where idx in (4)),'') as QTY5 ,
ISNULL((select item from fn_split(Data,',') where idx in (5)),'') as QTY5
from #test
Output Same as your Image
instead of using
insert into #test
(ID, Data)
Values
(1,'a,b,c')
you can also assgin it like this
insert into #test
(ID, Data)
Values
(Select Column1, Column2 From YourTable)
I want to split a column of strings say "99 crystal springs road" and get only 2 words (99 and crystal) respectively and update 99 to one column and crystal to another column of another table. How can I do it using charindex and substring?
Here is some sample code on how to do it ...
First, create this function:
CREATE FUNCTION [dbo].[GetStringPart]
(#fullString varchar(200), #pos tinyint)
RETURNS VARCHAR(200) -- return_data_type.
AS
BEGIN
IF #pos IS NULL OR #pos <= 0
SET #pos = 1
declare #secondPart varchar(200),#firstPart varchar(200),#output varchar(200)
declare #firstSpace int, #secondSpace int
set #firstSpace = CHARINDEX(' ', #fullString)
IF #firstSpace <= 0
RETURN ''
ELSE IF #pos = 1
BEGIN
SET #output = LTRIM(RTRIM(SUBSTRING(#fullString, 1, #firstSpace)))
END
ELSE
BEGIN
SET #secondSpace = CHARINDEX(' ', #fullString, CHARINDEX(' ', #fullString)+1) - CHARINDEX(' ', #fullString)+1
IF #secondSpace <= 0
SET #secondSpace = LEN(#fullString) - #firstSpace + 1
SET #output = LTRIM(RTRIM(SUBSTRING(#fullString, #firstSpace, #secondSpace)))
END
RETURN #Output
END
GO
Then you can use it like this:
declare #origTable table(name varchar(100))
insert into #origTable (name) values ('99 crystal springs road')
declare #newTable table(col1 varchar(100), col2 varchar(100))
INSERT INTO #newTable (col1, col2)
SELECT dbo.GetStringPart(name, 1), dbo.GetStringPart(name, 2) FROM #origTable
select * from #newTable
Assuming that you are selecting "99" and "crystal" just because they're the first two words...
You can do this in a single step but for ease of reading the solution I've separated it out
declare #sourceAddresses table
(
address varchar(100)
)
declare #split table
(
address varchar(100),
firstDelimiter int,
secondDelimiter int
)
declare #table table
(
part1 varchar(20),
part2 varchar(20)
)
insert into #sourceAddresses (address) values ('99 crystal springs road')
insert into #sourceAddresses (address) values ('100 elsewhere road')
insert into #split (address, firstDelimiter)
select address, charindex(' ', address)
from #sourceAddresses
update #split
set secondDelimiter = charindex(' ', address, (firstDelimiter+1))
where firstDelimiter > -1
insert into #table (part1, part2)
select substring(address, 0, firstDelimiter), substring(address, (firstDelimiter+1), (secondDelimiter-firstDelimiter))
from #split
where firstDelimiter > -1
and secondDelimiter > -1
select * from #table
Maybe something like this:
First create a function that gets part of the strings:
CREATE FUNCTION dbo.GetStringPart (#sep char(1), #s varchar(512),#pos int)
RETURNS VARCHAR(200)
AS
BEGIN
DECLARE #output VARCHAR(200)
;WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT
#output=SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END)
FROM Pieces
WHERE pn=#pos
RETURN #Output
END
GO
Then you can easy do this:
DECLARE #origalTable TABLE(name VARCHAR(100))
INSERT INTO #origalTable
VALUES('99 crystal springs road')
DECLARE #newTable TABLE(col1 VARCHAR(100), col2 VARCHAR(100))
INSERT INTO #newTable (col1, col2)
SELECT dbo.GetStringPart(' ',name, 1), dbo.GetStringPart(' ',name, 2) FROM #origalTable
SELECT * FROM #newTable
DROP FUNCTION dbo.GetStringPart