I have two different tables as below and have specific requirement of a sql query.
Table1:
Name RuleNumber
Tom 1,2
Pete 1,3
Table2:
RuleNumber Description
1 Rule1
2 Rule2
3 Rule3
How can I get a sql query result something like below
Name Description
Tom Rule1, Rule2
Pete Rule1, Rule3
You will first need a custom split function to seperate the delimited list, and then use FOR XML PATH to combine the descriptions. Here is your final query
select t1.Name,
STUFF(( SELECT ',' + Description
FROM table2 AS t2
WHERE t2.ruleNumber in (select s from dbo.fn_split(t1.RuleNumber, ','))
ORDER BY ruleNumber
FOR XML PATH('')), 1, 1, '') as 'Description'
from table1 t1
Here is the code for the split function.
create function [dbo].[fn_Split]
(
#String varchar(8000) ,
#Delimiter varchar(10)
)
returns #tbl table (s varchar(1000))
as
begin
declare #i int ,
#j int
select #i = 1
while #i <= len(#String)
begin
select #j = charindex(#Delimiter, #String, #i)
if #j = 0
begin
select #j = len(#String) + 1
end
insert #tbl select substring(#String, #i, #j - #i)
select #i = #j + len(#Delimiter)
end
return
end
How the Table1's RuleNumber has more than one value? Is it a string? I will assume it is :
Name / RuleNumber
Tom / 1
Tom / 2
Then, the query would be:
select
Name,
(
select Description
from Table2 as t2
where t1.ruleNumber = t2.ruleNumber
) as Description
from
table1 as t1
Related
How do you return every other character from a string from a specific starting position?
Example: starting at position 1
1050231
Result:
1521
Starting at position 2
1050231
Result:
003
Using a numbers table is usually the best way to avoid loops in SQL.
If you don't already have a numbers table, you should go read Jeff Moden's The "Numbers" or "Tally" Table: What it is and how it replaces a loop.
To create a numbers table, you can use the following script:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Now that you have a numbers table, you can use it to select the specific chars from your string like this:
DECLARE #s varchar(20) = '1050231',
#Start int = 1
SELECT Substring(#s, Number, 1)
FROM Numbers
WHERE Number >= #Start
AND (Number - #Start) % 2 = 0
AND Number <= DATALENGTH(#s)
Late answer, but here's yet another option
Example
Declare #S varchar(max) = '1050231'
Declare #P int =1
Select NewValue = (Select substring(substring(#S,#P,len(#S)),N,1)
From (Select Top (len(#S)-#P+1) N=Row_Number() Over (Order By (Select NULL)) From master..spt_values n1) A
Where N%2=1
For XML Path('')
)
Returns
NewValue
1521
One method uses a recursive CTE:
with cte as (
select #pos as pos, #str as str
union all
select pos + 2, str
from cte
where pos + 2 <= len(#str)
)
select substring(str, pos, 1)
from cte;
Here is a rextester.
The ugly way--a while loop, since Gordon gave the recursive CTE approach.
declare #string varchar(64) = 1050231
declare #start int = 1
declare #result varchar(64) = ''
set #result = #result + substring(#string,#start,1)
while #start < len(#string)
begin
set #start = #start + 2
select #result = #result + substring(#string,#start,1)
end
select #result
You could use STUFF:
declare #i VARCHAR(20) = '1050231';
select #i = IIF(LEN(#i) >= sub.s, STUFF(#i,sub.s,1,''),#i)
FROM(SELECT 1 s UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) sub;
-- any tally table
SELECT #i;
-- 003
declare #i VARCHAR(20) = '1050231';
select #i = IIF(LEN(#i) > sub.s, STUFF(#i,sub.s+1,1,''),#i)
FROM(SELECT 1 s UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) sub;
SELECT #i;
-- 1521
DBFiddle Demo
I wanted to use UNION ALL on 3 different tables, merging them into one table by using SELECT INTO.
Table 1 ,2 and 3 has 15, 7 and 8 columns respectively.
So, was there a way for me to use UNION ALL and have table 2 and 3 default to a NULL for the missing columns, without individually classifying them as such?
For instance, I've been doing:
SELECT NULL as [Company_Code], NULL as [Doc_Code],
NULL as [Doc_Type], [H ID] as [Document_No] FROM [table_2]
INTO BIG_TABLE
UNION ALL
SELECT
[Document Company] as [Company_Code], [Document Company] as [Doc_Code],
[Doc Type] as [Doc_Type], NULL as [Document_No]
FROM [table_3]
In this way, the number of columns match up, and I can UNION them.
However, I was wondering if there was a way to avoid the tedious mechanism to avoid inserting NULL for each column that was missing, and have that done automatically in one go ?
Thanks.
In short, no. Unioning result sets together must have the same number / data type of columns. If you wanted to have the remaining sets populate null, the simplest way to do this would be to do something like so-
select col1
, col2
, col3
, col4
from tbl1
union all
select null as col1
, null as col2
, null as col3
, null as col4
from tbl2
Union should be inside another select
SELECT * FROM(
SELECT col1, col2 FROM test_table1
UNION ALL
SELECT col1, col2,col3 FROM test_table2
);
Result will be col1 and col2 the un matched columns are skipped
The best possible way to nullify the columns which were missing is to use null as column_name for which you want to see the null columns as like below :...
SELECT id as var1, mgr_id as var2,name as var3 into exp_test1 FROM
emp123 UNION ALL
SELECT null as employeeid, null as departmentid,null as lastname FROM employee
I still suggest to go with #iliketocode.
Dynamic Columns Generate as per your requirement
This is just another option, but purely dynamic as you want (only its need some time to execute rather than above answer). To achieve the result without define each column, you can create dynamic query which automatically add column as null via loop.
The same issue I faced and due to short time, I was done as above. But today I fill to give the proper solution (which I not done at that time) and I created a sample for you as you want, I hope this will help you.
--create table t1 (col1 int , col2 int, col3 int)
--create table t2 (col1 int , col2 int, col3 int, col4 int)
--insert into t1 values (1,11,111), (2,22,222)
--insert into t2 values (1,11,111,1111), (2,22,222,null)
--Step 1 - Declaration of variable
Declare #NoOfColumnForUnion int = 5
declare #T1TableColumnList nvarchar(max) ='' ,#T2TableColumnList nvarchar(max) ='' , #colName nvarchar(500) , #test cursor
--Step 2 - Get the column list of first table i.e. t1 and store into #T1TableColumnList variable
set #test = cursor for
select name from syscolumns
where id = object_id('t1')
open #test
fetch next from #test into #colName
while ##fetch_status = 0
begin
set #T1TableColumnList = #T1TableColumnList + #colName + ','
fetch next from #test into #colName
end
set #T1TableColumnList = left( #T1TableColumnList , len(#T1TableColumnList )-1)
close #test
deallocate #test
--Step 3 - Get the column list of Second table i.e. t2 and store into #T2TableColumnList variable
set #test = cursor for
select name from syscolumns
where id = object_id('t2')
open #test
fetch next from #test into #colName
while ##fetch_status = 0
begin
set #T2TableColumnList = #T2TableColumnList + #colName + ','
fetch next from #test into #colName
end
set #T2TableColumnList = left( #T2TableColumnList , len(#T2TableColumnList )-1)
close #test
deallocate #test
--Step 4 - Check the length of column list to add null columns or remove columns
--First table check
Declare #T1lengthofColumnList int
set #T1lengthofColumnList = (len(#T1TableColumnList) - len(replace(#T1TableColumnList, ',', '')) ) + 1
--add columns
if( #T1lengthofColumnList < #NoOfColumnForUnion)
Begin
While (#T1lengthofColumnList < #NoOfColumnForUnion)
Begin
set #T1lengthofColumnList = #T1lengthofColumnList + 1
Set #T1TableColumnList = #T1TableColumnList + ', null col' + cast( #T1lengthofColumnList as varchar(10))
End
End
--remove columns
Else if( #T1lengthofColumnList > #NoOfColumnForUnion)
Begin
While (#T1lengthofColumnList > #NoOfColumnForUnion)
Begin
set #T1lengthofColumnList = #T1lengthofColumnList - 1
Set #T1TableColumnList = LEFT(#T1TableColumnList, LEN(#T1TableColumnList) - CHARINDEX(',',REVERSE(#T1TableColumnList)))
End
End
--Second table check
Declare #T2lengthofColumnList int
set #T2lengthofColumnList = (len(#T2TableColumnList) - len(replace(#T2TableColumnList, ',', '')) ) + 1
--add columns
if( #T2lengthofColumnList < #NoOfColumnForUnion)
Begin
While (#T2lengthofColumnList < #NoOfColumnForUnion)
Begin
set #T2lengthofColumnList = #T2lengthofColumnList + 1
Set #T2TableColumnList = #T2TableColumnList + ', null col' + cast( #T2lengthofColumnList as varchar(10))
End
End
--remove columns
Else if( #T2lengthofColumnList > #NoOfColumnForUnion)
Begin
While (#T2lengthofColumnList > #NoOfColumnForUnion)
Begin
set #T2lengthofColumnList = #T2lengthofColumnList - 1
Set #T2TableColumnList = LEFT(#T2TableColumnList, LEN(#T2TableColumnList) - CHARINDEX(',',REVERSE(#T2TableColumnList)))
End
End
--Step 5 - create dynamic query and execute
DECLARE #template AS varchar(max)
SET #template = 'select ' + #T1TableColumnList + ' from t1 union all '
+ ' select ' + #T2TableColumnList + ' from t2 '
select #template
EXEC (#template)
--drop table t1
--drop table t2
I have the next table, how can I get substring before and after dot(.) special character?
MyTable
------------------------------
Id Description
------------------------------
1 [Hugo].[date].[Subtotal]
2 [Juan].[date].[Subtotal]
3 [7/23/2013].[SubTotal]
4 [7/25/2013].[Total]
I am looking for the following result
MyResultTable
------------------------
MyTableId Description depth
-----------------------
1 [Hugo] 1
1 [date] 2
1 [Subtotal] 3
2 [Juan] 1
2 [date] 2
2 [Subtotal] 3
3 [7/23/2013] 1
3 [SubTotal] 2
4 [7/25/2013] 1
4 [Total] 2
I want to separate the words after a dot(.) and list the words as the following table
How can I solve it?
You will want to split the data based on the .. You can use a recursive CTE to split the data and return the depth:
;with cte (id, DescriptionItem, Description, depth) as
(
select id,
cast(left(Description, charindex('.',Description+'.')-1) as varchar(50)) DescriptionItem,
stuff(Description, 1, charindex('.',Description+'.'), '') Description,
1 as depth
from MyTable
union all
select id,
cast(left(Description, charindex('.',Description+'.')-1) as varchar(50)) DescriptionItem,
stuff(Description, 1, charindex('.',Description+'.'), '') Description,
depth+1
from cte
where Description > ''
)
select id, DescriptionItem, depth
from cte
order by id, depth;
See SQL Fiddle with Demo
Or you can use a UDF function that splits the data:
create FUNCTION [dbo].[Split](#String varchar(MAX), #Delimiter char(1))
returns #temptable TABLE (items varchar(MAX), depth int)
as
begin
declare #idx int
declare #slice varchar(8000)
declare #depth int = 1
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items, depth) values(#slice, #depth)
set #String = right(#String,len(#String) - #idx)
set #depth = #depth +1
if len(#String) = 0 break
end
return
end;
Then when you call the function, you will use CROSS APPLY similar to this:
select t.id, c.items description,
c.depth
from mytable t
cross apply dbo.split(t.description, '.') c
order by t.id, c.depth;
See SQL Fiddle with Demo
USE tempdb;
GO
IF OBJECT_ID('dbo.csv_split','U') IS NOT NULL DROP TABLE dbo.csv_split;
CREATE TABLE dbo.csv_split
(
Id INT NOT NULL PRIMARY KEY
,Description VARCHAR(100)
)
INSERT INTO dbo.csv_split(Id,Description)
VALUES
(1,'[Hugo].[date].[Subtotal]')
,(2,'[Juan].[date].[Subtotal]')
,(3,'[7/23/2013].[SubTotal]')
,(4,'[7/25/2013].[Total]');
WITH cte_xml AS
(
Select csv.Id
,CONVERT(XML,'<desc>'
+ REPLACE(csv.Description,'.','</desc><desc>')
+ '</desc>') AS xml_desc
From dbo.csv_split csv
)
,cte_shred_xml AS
(
Select t.Id
,xml_desc_nodes.value('(.)','varchar(50)') AS Description
,ROW_NUMBER() OVER(PARTITION BY t.Id ORDER BY t.Id ) AS Depth
From cte_xml t
CROSS APPLY t.xml_desc.nodes('/desc') AS t2(xml_desc_nodes)
)
Select *
From cte_shred_xml
Here is a simple example. I created your table as #test and used both a cursor and a loop within the cursor.
DECLARE #test TABLE ( id INT, NAME VARCHAR(MAX) )
INSERT #test
VALUES ( 1, '[Hugo].[date].[Subtotal]' )
INSERT #test
VALUES ( 2, '[Juan].[date].[Subtotal]' )
INSERT #test
VALUES ( 3, '[7/23/2013].[SubTotal]' )
INSERT #test
VALUES ( 4, '[7/25/2013].[Total]' )
DECLARE #id INT ,
#name VARCHAR(MAX)
DECLARE #values TABLE
(
MyTableId INT ,
Description VARCHAR(MAX) ,
Depth INT
)
DECLARE #v VARCHAR(2000) ,
#i INT ,
#depth INT
DECLARE #MyTableList CURSOR
SET
#MyTableList = CURSOR FOR SELECT id, name FROM #test
OPEN #MyTableList
FETCH NEXT FROM #MyTableList INTO #id, #name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #depth = 1
SET #i = PATINDEX('%.%', #name)
WHILE #i > 0
BEGIN
INSERT #values
VALUES ( #id, SUBSTRING(#name, 1, #i - 1), #depth )
SET #name = SUBSTRING(#name, #i + 1, LEN(#name) - #i)
SET #i = PATINDEX('%.%', #name)
SET #depth = #depth + 1
END
INSERT #values
VALUES ( #id, #name, #depth )
FETCH NEXT FROM #MyTableList INTO #id, #name
END
SELECT MyTableId ,
Description ,
Depth
FROM #values
You output should look like this.
MyTableId Description Depth
1 [Hugo] 1
1 [date] 2
1 [Subtotal] 3
2 [Juan] 1
2 [date] 2
2 [Subtotal] 3
3 [7/23/2013] 1
3 [SubTotal] 2
4 [7/25/2013] 1
4 [Total] 2
Each row in each table of my database has a RowUpdateDateTime column, which is the latest time that particular row was updated or inserted. I've also got between 1 and 7 distinct sources of data coming into each table; some have 1, some have 7.
Basically, I'm trying to loop through these tables and find the most recent RowUpdateDateTime for each of these sources, where applicable, for each of these tables. Here's the really long query that I and a colleague wrote to do this. It's functional, but I suspect could be re-written.
IF OBJECT_ID('tempdb..#SourceID') IS NOT NULL
BEGIN
DROP TABLE #SourceID
END
IF OBJECT_ID('tempdb..#Tables') IS NOT NULL
BEGIN
DROP TABLE #Tables
END
IF OBJECT_ID('tempdb..#UpdateCount') IS NOT NULL
BEGIN
DROP TABLE #UpdateCount
END
GO
CREATE TABLE #SourceID
(
SourceID varchar(4),
CounterID int
)
INSERT INTO #SourceID (SourceID,CounterID)
SELECT 'ZEND',1
UNION ALL
SELECT 'ABC',2
UNION ALL
SELECT 'DEF',3
UNION ALL
SELECT 'GHI',4
UNION ALL
SELECT 'JKL',5
UNION ALL
SELECT 'MNO',6
UNION ALL
SELECT 'PQR',7
UNION ALL
SELECT 'STU',8
GO
CREATE TABLE #Tables
(
Name varchar(100),
CounterID int
)
INSERT INTO #Tables (Name,CounterID)
SELECT 'livendb..Table1',1
UNION ALL
SELECT 'livendb..Table2',2
UNION ALL
SELECT 'livendb..Table3',3
UNION ALL
SELECT 'livendb..Table4',4
UNION ALL
SELECT 'livendb..Table5',5
UNION ALL
SELECT 'livendb..Table6',6
UNION ALL
SELECT 'livendb..Table7',7
UNION ALL
SELECT 'livefdb..Table8',8
UNION ALL
SELECT 'livefdb..Table9',9
UNION ALL
SELECT 'livefdb..Table10',10
UNION ALL
SELECT 'livefdb..Table11',11
UNION ALL
SELECT 'livefdb..Table12',12
UNION ALL
SELECT 'livefdb..Table13',13
GO
Declare #counter varchar(10)
Declare #tablename varchar(100)
Declare #query varchar(1100)
Declare #sourceid varchar(4)
Declare #sourcecounter varchar(10)
CREATE TABLE #UpdateCount
(
SourceID varchar(3),
TableName Varchar(100),
MaxRowUpdateDateTime datetime,
--TotalRowCount int
)
SET #sourcecounter = (SELECT COUNT(*) FROM #SourceID)
SET #counter = (SELECT COUNT (*) FROM #Tables)
WHILE #sourcecounter >= 0
BEGIN
SET #sourceid = (SELECT SourceID FROM #SourceID WHERE CounterID = (#sourcecounter))
IF #sourceid <> 'ZEND'
BEGIN
WHILE #counter >=0
BEGIN
SET #tablename = (SELECT Name FROM #Tables WHERE CounterID = (#counter))
IF #counter <> 0
BEGIN
SET #query = 'INSERT INTO #UpdateCount (SourceID,TableName,MaxRowUpdateDateTime)
VALUES (
(SELECT SourceID FROM #SourceID WHERE CounterID = '+#sourcecounter+')
,(SELECT Name FROM #Tables WHERE CounterID = '+#counter+')
,(SELECT MAX(RowUpdateDateTime) FROM '+#tablename+' WHERE SourceID =
(SELECT SourceID FROM #SourceID WHERE CounterID = '+#sourcecounter+')))'
EXECUTE (#query)
END
SET #counter = (#counter-1)
END
END
SET #sourcecounter = (#sourcecounter-1)
SET #counter = (SELECT COUNT (*) FROM #Tables)
END
SELECT SourceID
,SUBSTRING(TableName,10,22) as TableName
,MaxRowUpdateDateTime
--,TotalRowCount
FROM #UpdateCount
Where MaxRowUpdateDateTime IS NOT NULL
ORDER BY TableName
DROP TABLE #Tables
DROP TABLE #UpdateCount
DROP TABLE #SourceID
You might be better off (as the code is simpler) following a pattern like this example. It does not answer your entire question (its quite hard to code dynamic SQL with no data - for me at least).
This should give you a good starting point to work from.
USE AdventureWorks2012
GO
DECLARE #Query VARCHAR(MAX) =''
;WITH Tables AS
(
SELECT DISTINCT TABLE_NAME = '[' + TABLE_SCHEMA + ']' + '.' + '[' + TABLE_NAME + ']'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'ModifiedDate'
)
SELECT #Query = #Query +
' SELECT SourceTable = '''+ TABLE_NAME + '''
,RecentMod = MAX(ModifiedDate)
FROM ' + TABLE_NAME + ' UNION ALL'
FROM Tables
SET #Query = LEFT(#Query, LEN(#Query) - LEN(' UNION ALL'))
EXEC (#Query)
Producing a result like this;
SourceTable | RecentMod
===========================================================================
[dbo].[AWBuildVersion] | 2012-03-14 00:00:00.000
[dbo].[OLE DB Destination] | 2008-07-31 00:00:00.000
[HumanResources].[Department] | 2002-06-01 00:00:00.000
[HumanResources].[Employee] | 2009-01-26 09:17:08.637
[HumanResources].[EmployeeDepartmentHistory] | 2007-12-15 00:00:00.000
[HumanResources].[EmployeePayHistory] | 2008-07-31 00:00:00.000
[HumanResources].[JobCandidate] | 2008-01-23 18:32:21.313
[HumanResources].[Shift] | 2002-06-01 00:00:00.000
[HumanResources].[vJobCandidate] | 2008-01-23 18:32:21.313
[Person].[Address] | 2008-07-31 00:00:00.000
[Person].[AddressType] | 2002-06-01 00:00:00.000
[Person].[BusinessEntity] | 2012-01-14 13:47:22.467
[Person].[BusinessEntityAddress] | 2008-10-13 11:15:06.967
...
I have data in a table which looks like this (worth noting its not CSV seperated)
It needs to be split in to single chars
Data
abcde
want to convert it to this
Data
a
b
d
c
e
I have looked on the internet but have not found the answer
CREATE FUNCTION dbo.SplitLetters
(
#s NVARCHAR(MAX)
)
RETURNS #t TABLE
(
[order] INT,
[letter] NCHAR(1)
)
AS
BEGIN
DECLARE #i INT;
SET #i = 1;
WHILE #i <= LEN(#s)
BEGIN
INSERT #t SELECT #i, SUBSTRING(#s, #i, 1);
SET #i = #i + 1;
END
RETURN;
END
GO
SELECT [letter]
FROM dbo.SplitLetters(N'abcdefgh12345 6 7')
ORDER BY [order];
Previous post that solves the problem: TSQL UDF To Split String Every 8 Characters
Pass a value of 1 to #length.
declare #T table
(
ID int identity,
Data varchar(10)
)
insert into #T
select 'ABCDE' union
select '12345'
;with cte as
(
select ID,
left(Data, 1) as Data,
stuff(Data, 1, 1, '') as Rest
from #T
where len(Data) > 0
union all
select ID,
left(Rest, 1) as Data,
stuff(Rest, 1, 1, '') as Rest
from cte
where len(Rest) > 0
)
select ID,
Data
from cte
order by ID
You could join the table to a list of numbers, and use substring to split data column into rows:
declare #YourTable table (data varchar(50))
insert #YourTable
select 'abcde'
union all select 'fghe'
; with nrs as
(
select max(len(data)) as i
from #YourTable
union all
select i - 1
from nrs
where i > 1
)
select substring(yt.data, i, 1)
from nrs
join #YourTable yt
on nrs.i < len(yt.data)
option (maxrecursion 0)
declare #input varchar(max);
set #input = 'abcde'
declare #table TABLE (char varchar(1));
while (LEN(#input)> 0)
begin
insert into #table select substring(#input,1,1)
select #input = RIGHT(#input,Len(#input)-1)
end
select * from #table