Convert elements into attributes within one short expression when creating XML - sql

I found a common method to create XML from tables where elements' names changed into attributes' names. It looks like
SELECT ElementName_1 as "#AttName_1", ElementName_2 as "#AttName_2", ElementName_3 as "#AttName_3", etc..
from table
for XML PATH
Is there a method to shorten this kind of converting entry? For example i have dozens of fields\columns and need co convert all of them except one. So, formally it would looks like
(SELECT table.* --> all #attributes --> except 'OneFieldName'
If it possible, how it would be noted correctly?

It will be better to specify the columns. What if someone add new columns and you are using SELECT *?
If you do not want to write the definition itself, you can build it using the sys.columns system view. It will be something like this:
SELECT CONCAT(QUOTENAME([name]), ' AS "AttName_', [column_id], '"')
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY [column_id]) AS [column_id]
,[name]
FROM sys.columns
WHERE [object_id] = OBJECT_ID('dbo.table')
AND [name] NOT IN ('OneFieldName')
) DS
and you can even use STRING_AGG to build the whole SELECT:
SELECT STRING_AGG(CAST(CONCAT(QUOTENAME([name]), ' AS "AttName_', [column_id], '"') AS NVARCHAR(MAX)), ', ') WITHIN GROUP (ORDER BY [column_id])
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY [column_id]) AS [column_id]
,[name]
FROM sys.columns
WHERE [object_id] = OBJECT_ID('dbo.table')
AND [name] NOT IN ('OneFieldName')
) DS

Related

SQL Search for Data in Multiple Columns

Dears,
I have a table as shown below as a sample, and I want to run one query by which i can find all the yellow highlighted ones by using %AAA%.
Instead of running the Where command on each column one by one, I can do one general find option and it will list all the rows.
Thank you in advance!!
You can include all the conditions in one where clause using or:
where col1 like '%aaa%' or
col2 like '%aaa%' or
. . . -- and so on for all the columns
Unpivot the columns and do a WHERE based on that:
select *
from Table
where exists (select 1
from (values (col1), (col2), (col3) ) AS v (allCols) -- etc
where v.allCols like '%aaa%'
);
If you can't be bothered to type them out, try this little query:
select STRING_AGG('(' + c.name + ')', ', ')
from sys.columns c
where c.object_id = OBJECT_ID('Name_Of_Table_Here');
If you are using sql server then you can write dynamic query to do so. Please try below query:
declare #sql as varchar(max);
select #sql = 'select * from [TableName] where '
+ stuff((
select ' or [' + [column_name] + '] like ''%AAA%'''
from information_schema.columns
where table_name = 'TableName'
for xml path('')
)
, 1, 5, ''
);
exec(#sql);
This query will return every row in which at least one column contains AAA.
If you are using PostgreSQL, you can use its JSON functionality:
select t.*
from the_table t
where exists (select *
from jsonb_each(to_jsonb(t)) as x(col,val)
where val like '%AAA%');
If you are using Postgres 12 or later you can use a SQL/JSON path expression:
select t.*
from the_table t
where to_jsonb(t) ## '$.* like_regex "AAA" flag "i"'

Dynamic delete based on update table

I want to write a dynamic script that removes duplicates. I want to try and avoid a CURSOR so I've been looking into writing strings instead that will have table in one column and corresponding table attributes in another. I have also tried dynamic SQL using WITH. But this is what I have so far. This I intend to use as parameters in dynamic SQL later on
STUFF example. However this results in repeating the same column names for every row:
select name as table_name,
stuff(( select ', ' +char(10)+ ac.[name] FROM DW.sys.columns ac
inner join DW.sys.tables t on ac.object_id=t.object_id
where ac.name not in ('ModifiedOn','ValidFrom','ValidTo')
FOR XML PATH('')
), 1, 1, '')
from sys.tables
What I want is this output:
TableName || ColumnName
table1 || aa,ab,ac
table2 || ba,bb,bc
table3 || ca,cb,cc
My idea is to use this to this effect or similair:
'WITH DELETEDUPLICATE AS (
SELECT '+#ColumnName+',
ROW_NUMBER() OVER(PARTITION BY '+#ColumnName+' ORDER BY '+#ColumnName+') AS Duplicate_Row_Count
FROM '+#TableName+'
)
DELETE
FROM DELETEDUPLICATE
WHERE Duplicate_Row_Count > 1
Any ideas appreciated!
UPDATE:
With satishcse's suggestion i get the table I wanted. I had problem with getting multiple rows in the dynamic WITH step so I just removed that part as a varaible (removed away 'SET #WITH =' ). But how to execute every row? what i get now is:
WITH DELETEDUPLICATE AS(....
For every table per row
In OpenQuery you have to run the query using execute() function. The answer can solve your problem, but I do not suggest you use OpenQuery.
declare #query as nvarchar(max)
set
#query =
'WITH DELETEDUPLICATE AS (
SELECT '+#ColumnName+',
ROW_NUMBER() OVER(PARTITION BY '+#ColumnName+' ORDER BY '+#ColumnName+') AS Duplicate_Row_Count
FROM '+#TableName+'
)
DELETE
FROM DELETEDUPLICATE
WHERE Duplicate_Row_Count > 1'
execute(#query)
try the following for the first part:
select name as table_name,
stuff(( select ', ' +char(10)+ ac.[name] FROM DW.sys.columns ac
inner join DW.sys.tables t on ac.object_id=t.object_id
where ac.name not in ('ModifiedOn','ValidFrom','ValidTo')
and st.name = t.name
order by 1
FOR XML PATH('')
), 1, 1, '')
from sys.tables st

SQL Server : add 'Type' to every column in table

I have a table DemoTable in SQL Server. And it has these columns:
Column1, Column2, Column3
I want to query the table
select * from DemoTable
but in query results I want to concatenate Type_ to all the column names available in DemoTable.
So the result of this query should be showing columns
Type_Column1, Type_Column2, Type_Column3
Is there any function or any way to achieve this?
Note: there are N number of columns not only 3 just to rename only these manually.
If the problem is as you say:
After joining all the tables , there are many duplicate column names
then the typical solution is to NOT use *. So instead of this:
SELECT *
FROM A
JOIN B ON ...
JOIN C ON ...
... you should consider using a custom column set, which is the normal and recommended way to do this, as in the following example:
SELECT A.Column1, A.Column2, B.Column3, C.Column4, C.Column5
FROM A
JOIN B ON ...
JOIN C ON ...
Here's one way to automate your task using dynamic SQL:
use MY_DATABASE;
go
--here you specify all your parameters, names should be self-explanatory
declare #sql varchar(1000) = 'select ',
#tableName varchar(100) = 'DemoTable',
#prefix varchar(10) = 'Type_';
select #sql = #sql + name + ' as ' + #prefix + name + ',' from sys.columns
where object_name(object_id) = #tableName;
set #sql = left(#sql, len(#sql) - 1) + ' from ' + #tableName;
exec(#sql);
Some general remarks:
Naming your result set's columns dynamically will demand for dynamic SQL in any case. No way around...
Naming columns to carry extra information is - in most cases - a very bad idea.
the only way I know to deal with the asterisk in a SELECT * FROM ... and still get full control over the columns names and types is XML.
Try this:
SELECT TOP 10 *
FROM sys.objects
FOR XML RAW, ROOT('TableDef'),ELEMENTS, XMLSCHEMA,TYPE
This will return the 10 first rows of sys.objects. The result is an XML, where the rows follow an XML schema definition.
It is possible (but sure not the best in performance) to create a fully inlined query dynamically. The result will be an EAV list carrying everything you need.
WITH PrepareForXml(QueryAsXml) AS
(
SELECT
(
SELECT TOP 10 *
FROM sys.objects
FOR XML RAW, ROOT('TableDef'),ELEMENTS, XMLSCHEMA,TYPE
)
)
,AllRows AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) RowIndex
,rw.query('.') theRowXml
FROM PrepareForXml
CROSS APPLY QueryAsXml.nodes('TableDef/*:row') A(rw)
)
SELECT RowIndex
,B.ColumnName
,B.ColumnValue
,COALESCE(
(SELECT QueryAsXml.value('declare namespace xsd="http://www.w3.org/2001/XMLSchema";
(TableDef
/xsd:schema
/xsd:element
/xsd:complexType
/xsd:sequence
/xsd:element[#name=sql:column("ColumnName")]
/#type )[1]','nvarchar(max)')
FROM PrepareForXml)
,(SELECT QueryAsXml.value('declare namespace xsd="http://www.w3.org/2001/XMLSchema";
(TableDef
/xsd:schema
/xsd:element
/xsd:complexType
/xsd:sequence
/xsd:element[#name=sql:column("ColumnName")]
/xsd:simpleType
/xsd:restriction
/#base)[1]','nvarchar(max)')
FROM PrepareForXml)
) AS ColumnType
FROM AllRows
CROSS APPLY theRowXml.nodes('*:row/*') A(col)
CROSS APPLY (SELECT col.value('local-name(.)','nvarchar(max)') ColumnName
,col.value('(./text())[1]','nvarchar(max)') ColumnValue ) B;
This is the beginning of the result-set:
RowIndex ColumnName ColumnValue ColumnType
1 name sysrscols sqltypes:nvarchar
1 object_id 3 sqltypes:int
1 schema_id 4 sqltypes:int
[...many more...]
I don't know what you need actually, but it might be enough to export the XML as is. It's everything in there...
UPDATE: I did not read carefully enough...
You want to trick out the fact, that a result set's column names must be unique in order to continue with this...
The approach above will not solve this issue. Sorry.
I won't delete this immediately... Might be there are some hints you can get out of this...
You can use the following query to add 'Type' to every column in table:
SELECT Column1 AS Type_Column1, Column2 AS Type_Column2, Column3 AS Type_Column3
FROM DemoTable

SQL - Sort a CSV field

I have a CSV field in my SQL server, which contains X-number of days.
Here's an example with numbers (to make it easier to read):
1,2,3,4
4,5,1,9
3,6,8,4
I would like to sort each line. Is there an easy way to do it?
I want to have following result:
1,2,3,4
1,4,5,9
3,4,6,8
Thanks!
That functionality is not standard in the SQL language, nor is it readily available in SQL Server.
What you could do is write a User-Defined Function (UDF) in any .NET language, then call that function in your query. The function itself would simply take a string as input, expecting a CSV value, and return the sorted version of that string. You can then query your table with that function, like so:
SELECT SortedCsv(NumbersOfDays) FROM MyTable
You can find out more about writing UDF's in .NET (C#) in this article: http://www.dotnetspider.com/resources/19679-Creating-User-Defined-Function-Using-Managed-Code.aspx and this one: http://www.diaryofaninja.com/blog/2010/09/06/hidden-gems-microsoft-sql-net-managed-code-support
Good luck!
I'd recommend that you use a programming language like python to read each line, sort it and optionally put it back into SQL.
Try this
DECLARE #CSV TABLE(RID INT,ID INT,COL_NAME VARCHAR(MAX))
INSERT INTO #CSV(RID,ID,COL_NAME)
SELECT
ROW_NUMBER() OVER(PARTITION BY ID ORDER BY COL_NAME) AS RID
,ID,COL_NAME FROM
(
SELECT ID , Split.a.value('.', 'VARCHAR(100)') AS COL_NAME
FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY COL_NAME) AS ID,
CAST ('<M>' + REPLACE(COL_NAME, ',', '</M><M>') + '</M>' AS XML) AS COL_NAME
FROM TABLE_NAME
) AS A CROSS APPLY COL_NAME.nodes ('/M') AS Split(a)
) v
SELECT COL_NAME FROM
(
SELECT DISTINCT C1.ID,
STUFF((SELECT ',' + C2.COL_NAME AS [text()] FROM #CSV C2
WHERE C2.ID = C1.ID FOR XML PATH('')),1,1,'' ) AS COL_NAME
FROM #CSV C1
) RESULT
ORDER BY COL_NAME
Replace 'COL_NAME' and 'TABLE_NAME' with your column amd table names
SQLFiddle Demo

SQL Dynamic Pivot - how to order columns

I'm working on a dynamic pivot query on a table that contains:
OID - OrderID
Size - size of the product
BucketNum - the order that the sizes
should go
quantity - how many ordered
The size column contains different sizes depending upon the OID.
So, using the code found here, I put this together:
DECLARE #listCol VARCHAR(2000)
DECLARE #query VARCHAR(4000)
SELECT #listCol = STUFF(( SELECT distinct '], [' + [size]
FROM #t
FOR
XML PATH('')
), 1, 2, '') + ']'
SET #query = 'SELECT * FROM
(SELECT OID, [size], [quantity]
FROM #t
) src
PIVOT (SUM(quantity) FOR Size
IN (' + #listCol + ')) AS pvt'
EXECUTE ( #query )
This works great except that the column headers (the sizes labels) are not in the order based upon the bucketnum column. The are in the order based upon the sizes.
I've tried the optional Order By after the pivot, but that is not working.
How do I control the order in which the columns appear?
Thank you
You need to fix this:
SELECT #listCol = STUFF(( SELECT distinct '], [' + [size]
FROM #t
FOR
XML PATH('')
), 1, 2, '') + ']'
To return the columns in the right order. You might have to do something like this instead of using DISTINCT:
SELECT [size]
FROM #t
GROUP BY [size]
ORDER BY MIN(BucketNum)
SELECT #listCol = STUFF(
(SELECT DISTINCT ',' + QUOTENAME(size) AS [size]
FROM #t
ORDER BY [size]
FOR XML PATH('')
I saw this link just today, which uses a CTE to build the column list (which, presumably, you could order) on the fly without the need for dynamic sql:
http://blog.stevienova.com/2009/07/13/using-ctes-to-create-dynamic-pivot-tables-in-sql-20052008/
I had the same problem and tried the solution suggested above but, probably due to my level of understanding, couldn't get it to work. I found a simple hack was to create a Temp table with the column headers ordered correctly using Order by statements and then pull in that list to the variable that sets the dynamic pivot query column names.
e.g.
SELECT WeekNum INTO #T3
FROM #T2
GROUP BY WeekNum
ORDER BY MIN(WeekNum)
SELECT #ColumnName1 = ISNULL(#ColumnName1 + ',','') + QuoteName(WeekNum)
FROM (SELECT WeekNum From #T3) AS WeekNum
Worked a treat.
Hope that helps someone.