Create a table in SQL Server from a tables values - sql-server

Ok, Stay with me here and lets see if anyone knows if this can be done.
I initially query a table to bring back just 1 row, this row has the data that I will use.
What I need to do is, convert that 1 row to a table where each column value is aligned with what the column name was.
So I query a table to get 1 row, say a table called colours and in it there are the columns Name, Hue, Populatiry.
I need to get that 1 row to be a table where the values of lets say:
Red, 50, Super Popular
Become a table of:
Name, Red
Hue, 50
Populatiry, Super Popular
Im basically using a function to replace a string value (entered as a what a columns name is) with its actual value and I cannot use the exec command as functions wont allow it and it has to be a function cause Im selecting that returned string directly into a procedure:
This is the code I had until I found exec commands stop it working as a function
CREATE FUNCTION [dbo].[GetOutlookTrainingBodyText]
(
#ScheduleID bigint
)
RETURNS nvarchar(300)
AS
BEGIN
declare c cursor local fast_forward
for
SELECT Label, Name, QueryField
FROM IntranetPagesx ipx
INNER JOIN IntranetPageLayoutx ipl on ipl.PageID = ipx.PageID
WHERE ipx.PageID = 'Training Attendance'
declare #name nvarchar (100)
declare #name1 nvarchar (100)
declare #label nvarchar (100)
declare #queryfield nvarchar (200)
declare #sql nvarchar (1000)
declare #BodyText nvarchar(500) = (SELECT dbo.GetValidSystemPropertyValue('OutlookIntegrationTrainingBodyTextTemplate'))
open c
fetch next from c into #label, #name, #queryfield
while ##FETCH_STATUS = 0
begin
SET #sql = 'SELECT TOP 1 #name1= '+#queryfield+' FROM TRAINING_AttendanceConfirmed WHERE ScheduleID = ''246'''
EXEC sp_executesql #sql,N'#name1 nvarchar(200) out', #name1 out
set #BodyText = Replace(#BodyText, '#'+#label+'#', (ISNULL(#name1, '')))
fetch next from c into #label, #name, #queryfield
END
CLOSE c
DEALLOCATE c
PRINT #BodyText
return (#BodyText)
END
I need the working equivalent without the use of exec - can this be done????

xml can provide some flexibility to handle column names and values, something like this:
SELECT
CAST(CAST((SELECT TOP 1 * FROM colours FOR XML PATH('')) AS XML).query('fn:local-name(*[1]/.)') AS VARCHAR(100)) colname,
CAST(CAST((SELECT TOP 1 * FROM colours FOR XML PATH('')) AS XML).query('*[1]/text()') AS VARCHAR(100)) value
UNION
SELECT
CAST(CAST((SELECT TOP 1 * FROM colours FOR XML PATH('')) AS XML).query('fn:local-name(*[2]/.)') AS VARCHAR(100)) colname,
CAST(CAST((SELECT TOP 1 * FROM colours FOR XML PATH('')) AS XML).query('*[2]/text()') AS VARCHAR(100)) value
UNION
SELECT
CAST(CAST((SELECT TOP 1 * FROM colours FOR XML PATH('')) AS XML).query('fn:local-name(*[3]/.)') AS VARCHAR(100)) colname,
CAST(CAST((SELECT TOP 1 * FROM colours FOR XML PATH('')) AS XML).query('*[3]/text()') AS VARCHAR(100)) value

Related

How to get only the columns that have at least one non-null value in a table existing in SQL server

I have a table named Product with following data for instance:
p_id
p_name
p_cat
1
shirt
null
2
null
null
3
cap
null
Suppose I don't know numbre of rows and columns in the table as well as I don't know which columns are compltely null (no non-null value in all of its rows). How to write a query to retrieve just the columns that have atleast one non-null value in its rows. My approach is as follows but not getting a corret output:
select
column_name
into #TempColumns
from information_schema.columns
where
table_name = 'Product'
and table_schema = 'DDB'
declare #CurrentColumn nvarchar(max) = '', #IsNull bit, #NonNullCols nvarchar(max) = ''
declare Cur cursor for
select column_name from #TempColumns
open Cur
while 1=1
begin
fetch next from Cur into #CurrentColumn
select #IsNull = case when count(#CurrentColumn) > 0 then 0 else 1 end
from Product
if #IsNull = 1
begin
set #NonNullCols = #NonNullCols + ',' + #CurrentColumn
end
if ##fetch_status <> 0 break
end
close Cur
deallocate Cur
select #NonNullCols as NullColumns
drop table #TempColumns
If there is any other approach or correction in my above (T-SQL) query. Thanks in advance.
First I just create a temporary table to store all the column names avaialbe in the Product table. Then I looped in this temporary table and feteched each row and checked it on the product table whether the column is comptely null or not using the count() function. The condition sets the bit variable 1 if the column is completely null and then that particular column name is stored in anothe variable which is then retrieved as null columns.
Here is a conceptual example for you.
It is using SQL Server XML and XQuery powers without dynamic SQL and cursors/loops.
The algorithm is very simple.
When we are converting each row into XML, columns that hold NULL value are missing from the XML.
SQL
USE tempdb;
GO
DROP TABLE IF EXISTS #tmpTable;
CREATE TABLE #tmpTable (
client_id int,
client_name varchar(500),
client_surname varchar(500),
city varchar(500),
state varchar(500));
INSERT #tmpTable VALUES
(1,'Miriam',NULL,'Las Vegas',NULL),
(2,'Astrid',NULL,'Chicago',NULL),
(3,'David',NULL,'Phoenix',NULL),
(4,'Hiroki',NULL,'Orlando',NULL);
SELECT DISTINCT x.value('local-name(.)', 'SYSNAME') AS NotNULLColumns
FROM #tmpTable AS t
CROSS APPLY (SELECT t.* FOR XML PATH(''), TYPE, ROOT('root')) AS t1(c)
CROSS APPLY c.nodes('/root/*') AS t2(x);
SQL #2
To handle edge cases.
SELECT DISTINCT x.value('local-name(.)', 'SYSNAME') AS NotNULLColumns
FROM #tmpTable AS t
CROSS APPLY (SELECT t.* FOR XML RAW, ELEMENTS, BINARY BASE64, TYPE, ROOT('root')) AS t1(c)
CROSS APPLY c.nodes('/root/row/*') AS t2(x);
Output
NotNULLColumns
city
client_id
client_name

Local Variable not showing all the values

I am new to programming so I do not know how this question to ask.
what I am doing is firstly finding #userid value on different query, then after when I do
select a,b
from tableA
where userid = #userid
and active=1
and payments=1
then it executes to show me number of rows(lets say:10 rows)
but when I do like below, I only get 1 row (I want to get all 10 rows):
declare #A varchar(10)
declare #B bigint
select #A=a,#B=b
from tableA
where userid=#userid
and active=1
and payments=1
Select #A,#B
so, I am asking for help how do I do this . I have to do like step 2 because i have to run other query taking,#A and #B
Yes because those are scalar variable which can hold only 1 item and in your case it will hold the values for last row. You might want to consider using a table variable rather. Like
DECLARE #tab1 table(
A varchar(10),
B bigint );
Then fill it like
insert into #tab1(A,B)
select a, b
from tableA
where userid=#userid
and active=1
and payments=1
Now select from it
select * from #tab1;
It seems like you are trying to do something with all a and b values of tableA.
You can use cursor for that if number of records are small else use the WHILE loop to read each record.
Declare #a varchar(10)
Declare #b varchar(10)
DECLARE MyCursor CURSOR FOR
select a, b
from tableA
where userid=#userid
and active=1
and payments=1
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO #a, #b
WHILE ##FETCH_STATUS = 0
BEGIN
Select #a, #b
FETCH NEXT FROM MyCursor INTO #a, #b
END
CLOSE MyCursor
DEALLOCATE MyCursor

SQL Server : convert XML data onto table

I need help in fine-tuning this code that I write. I am new to SQL Server and I believe there are better ways to do this or perhaps some of the following codes can be simplified or fine-tuned for performances or saving memory resources.
Basically, I have this XML data :
<table_result id="001" subj_cd="cdaaa" grade="b" name="Phua Chu Kang"/>
and I want to create a table which looks like this from that XML data
Please note on the following points :
SplitThis is not a built in function (check the code below).
The data can have space, but delimited by ". Do note as well that the XML data can have varying number of fields-data pairs for that particular given table - referred as #dummy in the following codes. i.e. example XML data above have 4 fields (id, subj_cd, grade, name) and the next XML data could have 5 fields (i.e. id, name, occupation, phone_no, address). In the following code, #table_result is created to match the example XML data for easier demonstration. In other words, the table structures are known..so I can ignore the field names from the XML data and focus on extracting the data itself.
The code ran well on SQL Server 2012 (you can copy and paste run code directly) and I am able to get as above. I just need to fine tune this, if possible. I have include line like this : - - test blabla. You can uncomment that and try. I could use enhancements such as in term of avoiding the number of temp tables used or any ways to replace the use of row_number() in the code.
/* remove all temp tables */
declare #sql varchar(5000)
SELECT #sql = isnull(#sql+';', '') + 'drop table ' + SUBSTRING(t.name, 1, CHARINDEX('___', t.name)-1)
FROM tempdb..sysobjects AS t
WHERE t.name LIKE '#%[_][_][_]%'
AND t.id =OBJECT_ID('tempdb..' + SUBSTRING(t.name, 1, CHARINDEX('___', t.name)-1));
exec (#sql)
/* end */
/* function */
drop function splitthis
go
create function splitthis(#separator char(1), #list varchar(max))
returns #returntable table(item nvarchar(max))
as
begin
declare #index int
declare #newtext varchar(max)
if #list = null
return
set #index = charindex(#separator, #list)
while not(#index = 0)
begin
set #newtext = rtrim(ltrim(left(#list, #index - 1)))
set #list = right(#list, len(#list) - #index)
insert into #returntable(item) values(#newtext)
set #index = charindex(#separator, #list)
end
insert into #returntable(item) values(rtrim(ltrim(#list)))
update #returntable set item='' where item is null
return
end
go
/* end of function */
/* create dummy tables */
create table #table_result
(id nvarchar(max), subj_cd nvarchar(max), grade nvarchar(max), name nvarchar(max))
create table #dummy (name nvarchar(max), data nvarchar(max))
insert into #dummy
values ('a', '<table_result id="001" subj_cd="cdaaa" grade="b" name="phua chu kang"/>');
--test : select * from #dummy
/* remove the fist non-data opening tag */
declare #record nvarchar(max)
select #record = data from #dummy where name = 'a'
select *, null as temp into #tempb from splitthis(' ',#record)
select *, row_number() over (order by temp) count into #tempc from #tempb
select item into #tempd from #tempc where #tempc.count>1
-- test : select * from #tempd
/* get the actual field & data into a single column table */
declare #temp varchar(max)
set #temp=''select #temp=#temp+' ' + item from #tempd
select *, null as temp into #tempe from splitthis('"',#temp)
select *, row_number() over (order by temp) count into #tempf from #tempe
select item, count into #tempg from #tempf
--test : select * from #tempg
/* prepare the data table */
select
case when #tempg.count % 2 = 0
then item
else null
end as data
into #temph
from #tempg
select data, null as temp into #tempi from #temph
select data, row_number() over (order by temp) count into #data from #tempi
where data is not null
--test : select * from #data
/* prepare the field table. */
select name, null as temp into #tempj
from tempdb.sys.columns where object_id=object_id('tempdb..#table_result');
select *, row_number() over (order by temp) count into #field from #tempj
--test : select * from #field
/* get the final table */
select a.name as field, b.data from #field a
left join #data b on a.count=b.count
This is - using XML methods - much easier!
Try this:
DECLARE #xml XML='<table_result id="001" subj_cd="cdaaa" grade="b" name="Phua Chu Kang"/>';
SELECT One.Attr.value('fn:local-name(.)','varchar(max)') AS field
,One.Attr.value('.','varchar(max)') AS data
FROM #xml.nodes('table_result/#*') AS One(Attr)
The result
field data
id 001
subj_cd cdaaa
grade b
name Phua Chu Kang
Now I try to imitate your table structure (I'd recommend to store the data as XML from the beginning! In this case you could omit the first CROSS APPLY with the CAST ... AS XML):
DECLARE #tbl TABLE(name VARCHAR(10),data VARCHAR(MAX));
INSERT INTO #tbl VALUES
('a','<table_result id="001" subj_cd="cdaaa" grade="b" name="Phua Chu Kang"/>')
,('b','<Another test="test data" test2="test2 data"/>')
,('c','<OneMore x="x data" y="y data" z="z data"/>');
SELECT tbl.name
,One.Attr.value('fn:local-name(..)','varchar(max)') AS element
,One.Attr.value('fn:local-name(.)','varchar(max)') AS field
,One.Attr.value('.','varchar(max)') AS data
FROM #tbl AS tbl
CROSS APPLY(SELECT CAST(tbl.data AS XML)) AS MyData(AsXml)
CROSS APPLY MyData.AsXml.nodes('*/#*') AS One(Attr)
The result
name element field data
a table_result id 001
a table_result subj_cd cdaaa
a table_result grade b
a table_result name Phua Chu Kang
b Another test test data
b Another test2 test2 data
c OneMore x x data
c OneMore y y data
c OneMore z z data
Now, I'm not at all very good with T-SQL XML, but can't you just do it like this:
create table #dummy (name nvarchar(max), data xml);
insert into #dummy
values ('a', '<table_result id="001" subj_cd="cdaaa" grade="b" name="phua chu kang"/>');
select 'id' "field",
elem.value('#id', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem)
union all
select 'subj_cd' "field",
elem.value('#subj_cd', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem)
union all
select 'grade' "field",
elem.value('#grade', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem)
union all
select 'name' "field",
elem.value('#name', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem);
Notice that I changed the data type for #dummy.data to be xml. That's required to be able to use the XML functions.

dynamic filters and sp_executesql

I have a stored procedure which has a parameter #SomeFilterIds, which takes in comma separated integer ids. If this parameter is not NULL it is translated into something like this:
AND [X] IN(1, 2, 4)
and assigned to #SomeFilter
I then used something along those lines:
SET #Sql = N' ...WHERE
c.SomeDate >= #SomeDate
' + #SomeFilter
and:
SET #ParameterDefinition = N'#SomeDate DateTime';
EXEC sp_executesql
#Sql
,#ParameterDefinition
,#SomeDate = #SomeDate
I would think that this is not best practice and opens potential security holes. Is this correct? Can this be improved? Thanks.
I think instead of #SomeFilterIds as varchar parameter you can use XML type variable and then use Inner join your main table with this XML variable.
This will avoid dynamic query execution and will be be safer.
Example:
--Instead of comma separated ID use below XML
declare #xml xml = '<row><ID>1</ID></row><row><ID>3</ID></row>'
--Assume this is your other table
declare #YourTable table (ItemId int, ColA varchar(20))
insert #YourTable
select 1, 'Hello World'
--Joining both the tables
select col.value('data(ID[1])', 'int') as ID
from #xml.nodes('/row') tbl(col)
inner join #YourTable t2
on t2. ItemId=tbl.col.value('data(ID[1])', 'int')
--WHERE c.SomeDate >= #SomeDate

How to split string and save into an array in T-SQL

I am writing a cursor to populate data in new table from main table which contains data in below manner
Item
Colors
Shirt
Red,Blue,Green,Yellow
I want to populate new Table data by fetching the Item and then adding it in row, according to each color it contains
Item
Color
Shirt
Red
Shirt
Blue
Shirt
Green
Shirt
Yellow
I am stuck in how to
Delimit/Split "Colors" string
To save it in an array
To use it in cursor
as I am going to use Nested cursor for this purpose.
Using Sql Server 2005+ and the XML datatype, you can have a look at the following
DECLARE #Table TABLE(
Item VARCHAR(250),
Colors VARCHAR(250)
)
INSERT INTO #Table SELECT 'Shirt','Red,Blue,Green,Yellow'
INSERT INTO #Table SELECT 'Pants','Black,White'
;WITH Vals AS (
SELECT Item,
CAST('<d>' + REPLACE(Colors, ',', '</d><d>') + '</d>' AS XML) XmlColumn
FROM #Table
)
SELECT Vals.Item,
C.value('.','varchar(max)') ColumnValue
FROM Vals
CROSS APPLY Vals.XmlColumn.nodes('/d') AS T(C)
The article Faking Arrays in Transact SQL details SEVERAL techniques to solve this problem, ranging from using the PARSENAME() function (limit to 5 items) to writing CLR functions.
The XML answer is one of the detailed techniques that can be chosen to a specific scenario.
Combining some of the tips, I solved my string split problem like this:
SET NOCOUNT ON;
DECLARE #p NVARCHAR(1000), #len INT;
SET #p = N'value 1,value 2,value 3,value 4,etc';
SET #p = ',' + #p + ',';
SET #len = LEN(#p);
-- Remove this table variable creation if you have a permanent enumeration table
DECLARE #nums TABLE (n int);
INSERT INTO #nums (n)
SELECT A.n FROM
(SELECT TOP 1000 ROW_NUMBER() OVER (ORDER BY TableKey) as n FROM dbo.Table) A
WHERE A.n BETWEEN 1 AND #len;
SELECT SUBSTRING(#p , n + 1, CHARINDEX( ',', #p, n + 1 ) - n - 1 ) AS "value"
FROM #nums
WHERE SUBSTRING( #p, n, 1 ) = ',' AND n < #len;
Note that, considering 1000 your string length limit, you must have a table with 1000 or more rows (dbo.Table on the sample tsql) to create the table variable #nums of this sample. On the article, they have a permanent enumeration table.
For those who like to keep it simple:
-- Here is the String Array you want to convert to a Table
declare #StringArray varchar(max)
set #StringArray = 'First item,Second item,Third item';
-- Here is the table which is going to contain the rows of each item in the String array
declare ##mytable table (EachItem varchar(50))
-- Just create a select statement appending UNION ALL to each one of the item in the array
set #StringArray = 'select ''' + replace(#StringArray, ',', ''' union all select ''') + ''''
-- Push the data into your table
insert into ##mytable exec (#StringArray)
-- You now have the data in an an array inside a table that you can join to other objects
select * from ##mytable
I just accomplished something like this to create staging tables to replicate the source tables using the INFORMATION_SCHEMA views on a linked server. But this is a modified version to create the results you are look for. Just remember to remove the last two characters from the Colors column when displaying it.
SELECT
t.Item
, (
SELECT
x.Color + ', ' AS [data()]
FROM
Items x
WHERE
x.Item = t.Item
FOR XML PATH(''), TYPE
).value('.', 'varchar(max)') AS Colors
FROM
Items t
GROUP BY
t.Item

Resources