TSQL Select Clause with Case Statement - sql-server

I have a basic select statement that is getting me a list of types that are stored in the database:
SELECT teType
FROM BS_TrainingEvent_Types
WHERE source = #source
FOR XML PATH ('options'), TYPE, ELEMENTS, ROOT ('types')
My table contains a type column and a source column.
There is a record in that table where I need to include it for two separate sources but I can't create a separate record for it.
**Table Data**
type | source
test users
test2 members
test3 admins
I need a case statement to be able to say IF source = admins also give me the type test2.
Does this make sense and is it possible to do with a basic select?
Update
I came up with this temp solution but I still think there is a better way to handle this.:
DECLARE #tmp AS TABLE (
QID VARCHAR (10));
INSERT INTO #tmp (QID)
SELECT DISTINCT qid
FROM tfs_adhocpermissions;
SELECT t.QID,
emp.FirstName,
emp.LastName,
emp.NTID,
(SELECT accessKey
FROM TFS_AdhocPermissions AS p
WHERE p.QID = t.QID
FOR XML PATH ('key'), TYPE, ELEMENTS, ROOT ('keys'))
FROM #tmp AS t
LEFT OUTER JOIN
dbo.EmployeeTable AS emp
ON t.QID = emp.QID
FOR XML PATH ('data'), TYPE, ELEMENTS, ROOT ('root');

try this
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--create temp table for testing
IF OBJECT_ID('Tempdb..#BS_TrainingEvent_Types') IS NOT NULL
DROP TABLE #BS_TrainingEvent_Types
SELECT [type] ,
[source]
INTO #BS_TrainingEvent_Types
FROM ( VALUES ( 'test', 'users'), ( 'test2', 'members'),
( 'test3', 'admins') ) t ( [type], [source] )
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--final query
DECLARE #Source VARCHAR(10) = 'users'
IF #Source = 'admins'
BEGIN
SELECT [Type]
FROM #BS_TrainingEvent_Types
WHERE source = #source
OR [type] = 'test2'
FOR XML PATH('options') ,
TYPE ,
ELEMENTS ,
ROOT('types')
END
ELSE
BEGIN
SELECT [Type]
FROM #BS_TrainingEvent_Types
WHERE source = #source
FOR XML PATH('options') ,
TYPE ,
ELEMENTS ,
ROOT('types')
END

select sq.teType
from (
SELECT t.teType
FROM BS_TrainingEvent_Types t
WHERE t.source = #source
union all
SELECT t.teType
FROM BS_TrainingEvent_Types t
WHERE #source = 'admins' and t.source = 'members'
) sq
FOR XML PATH ('options'), TYPE, ELEMENTS, ROOT ('types');
Though normally it would be better to introduce an additional table that would store these relationships, so that the whole idea would be more expandable.

Related

SQL Server extract data from XML column without tag names

I have an XML string:
<XML>
<xml_line>
<col1>1</col1>
<col2>foo 1</col2>
</xml_line>
<xml_line>
<col1>2</col1>
<col2>foo 2</col2>
</xml_line>
</XML>
I am extracting data from that string (stored in #data_xml) by storing it in SQL Server table and parsing it:
-- create temp table, insert XML string
CREATE TABLE table1 (data_xml XML)
INSERT table1
SELECT #data_xml
-- parse XML string into temp table
SELECT
N.C.value('col1[1]', 'int') col1_name,
N.C.value('col2[1]', 'varchar(31)') col2_name,
FROM
table1
CROSS APPLY
data_xml.nodes('//xml_line') N(C)
I would like to know if there is a generic way to accomplish the same without specifying column names (i.e. col1[1], col2[1])
You can use something like:
SELECT
N.C.value('let $i := . return count(//xml_line[. << $i]) + 1', 'int') as LineNumber,
Item.Node.value('local-name(.)', 'varchar(max)') name,
Item.Node.value('.', 'varchar(max)') value
FROM
table1
CROSS APPLY
data_xml.nodes('//xml_line') N(C)
CROSS APPLY
N.C.nodes('*') Item(Node)
To get:
LineNumber
name
value
1
col1
1
1
col2
foo 1
2
col1
2
2
col2
foo 2
See this db<>fiddle.
However, to spread columns horizontally, you will need to generate dynamic SQL after querying for distinct element names.
ADDENDUM: Here is an updated db<>fiddle that also shows a dynamic SQL example.
The above maps all values as VARCHAR(MAX). If you have NVARCHAR data you can make the appropriate changes. If you have a need to map specific columns to specific types, you will need to explicitly define and populate a name-to-type mapping table and incorporate that into the dynamic SQL logic. The same may be necessary if you prefer that the result columns be in a specific order.
ADDENDUM 2: This updated db<>fiddle now includes column type and ordering logic.
--------------------------------------------------
-- Extract column names
--------------------------------------------------
DECLARE #Names TABLE (name VARCHAR(100))
INSERT #Names
SELECT DISTINCT Item.Node.value('local-name(.)', 'varchar(max)')
FROM table1
CROSS APPLY data_xml.nodes('//xml_line/*') Item(Node)
--SELECT * FROM #Names
--------------------------------------------------
-- Define column-to-type mapping
--------------------------------------------------
DECLARE #ColumnTypeMap TABLE ( ColumnName SYSNAME, ColumnType SYSNAME, ColumnOrder INT)
INSERT #ColumnTypeMap
VALUES
('col1', 'int', 1),
('col2', 'varchar(10)', 2)
DECLARE #ColumnTypeDefault SYSNAME = 'varchar(max)'
--------------------------------------------------
-- Define SQL Templates
--------------------------------------------------
DECLARE #SelectItemTemplate VARCHAR(MAX) =
' , N.C.value(<colpath>, <coltype>) <colname>
'
DECLARE #SqlTemplate VARCHAR(MAX) =
'SELECT
N.C.value(''let $i := . return count(//xml_line[. << $i]) + 1'', ''int'') as LineNumber
<SelectItems>
FROM
table1
CROSS APPLY
data_xml.nodes(''//xml_line'') N(C)
'
--------------------------------------------------
-- Expand SQL templates into SQL
--------------------------------------------------
DECLARE #SelectItems VARCHAR(MAX) = (
SELECT STRING_AGG(SI.SelectItem, '')
WITHIN GROUP(ORDER BY ISNULL(T.ColumnOrder, 999), N.Name)
FROM #Names N
LEFT JOIN #ColumnTypeMap T ON T.ColumnName = N.name
CROSS APPLY (
SELECT SelectItem = REPLACE(REPLACE(REPLACE(
#SelectItemTemplate
, '<colpath>', QUOTENAME(N.name + '[1]', ''''))
, '<colname>', QUOTENAME(N.name))
, '<coltype>', QUOTENAME(ISNULL(T.ColumnType, #ColumnTypeDefault), ''''))
) SI(SelectItem)
)
DECLARE #Sql VARCHAR(MAX) = REPLACE(#SqlTemplate, '<SelectItems>', #SelectItems)
--------------------------------------------------
-- Execute
--------------------------------------------------
SELECT DynamicSql = #Sql
EXEC (#Sql)
Result (with some additional data):
LineNumber
col1
col2
bar
foo
1
1
foo 1
null
More
2
2
foo 2
Stuff
null

TSQL Where clause based on temp table data

I have a straight forward SQL query that I am working with and trying to figure out the best way to approach the where clause.
Essentially, there are two temp tables created and if there is data in the XML string passed to the stored procedure, those tables are populated.
My where clause needs to check these temp tables for data, and if there is no data, it ignores them like they are not there and fetches all data.
-- Create temp tables to hold our XML filter criteria
DECLARE #users AS TABLE (QID VARCHAR(10))
DECLARE #dls AS TABLE (dlName VARCHAR(50))
-- Insert our XML filters
IF #xml.exist('/root/data/users') > 0
BEGIN
INSERT INTO #users( QID )
SELECT ParamValues.x1.value('QID[1]', 'varchar(10)')
FROM #xml.nodes('/root/data/users/user') AS ParamValues(x1)
END
IF #xml.exist('/root/data/dls') > 0
BEGIN
INSERT INTO #dls( dlName )
SELECT ParamValues.x1.value('dlName[1]', 'varchar(50)')
FROM #xml.nodes('/root/data/dld/dl') AS ParamValues(x1)
END
-- Fetch our document details based on the XML provided
SELECT d.documentID ,
d.sopID ,
d.documentName ,
d.folderLocation ,
d.userGroup ,
d.notes
FROM dbo.Documents AS d
LEFT JOIN dbo.DocumentContacts AS dc
ON dc.documentID = d.documentID
LEFT JOIN dbo.DocumentContactsDLs AS dl
ON dl.documentID = d.documentID
-- How can I make these two logic checks work only if there is data, otherwise, include everything.
WHERE dc.QID IN (SELECT QID FROM #users)
AND dl.DL IN (SELECT dlName FROM #dls)
FOR XML PATH ('data'), ELEMENTS, TYPE, ROOT('root');
In the query above, I am trying to used the data in the temp tables only if there is data in them, otherwise, it needs to act like that where statement isn't there for that specific value and include records regardless.
Example: If only #users had data, it would ignore AND dl.DL IN (SELECT dlName FROM #dls) and get everything, regardless of what was in the DL column on those joined records.
Use NOT EXISTS to check the existence of any record in variable table. Here is one way
WHERE ( dc.QID IN (SELECT QID FROM #users)
OR NOT EXISTS (SELECT 1 FROM #users) )
AND ( dl.DL IN (SELECT dlName FROM #dls)
OR NOT EXISTS (SELECT 1 FROM #dls) )
Try this. But please note that I did not get a chance to test it properly and I believe that you want to check the values in #users first and if there is no record existing in that table, then you want to check with the entries in #dls. Also if there are no entries in both of these tables, then you want to skip both the tables.
DECLARE #fl bit = 0
SELECT #fl = CASE WHEN EXISTS (SELECT 1 FROM #users) THEN
1
WHEN EXISTS (SELECT 1 FROM #dls) THEN
2
ELSE
0
END
WHERE ( (dc.QID IN (SELECT QID FROM #users) AND #fl = 1)
OR
(dl.DL IN (SELECT dlName FROM #dls) AND #fl = 2)
OR (1=1 AND #fl = 0)
)

Remove Root XML nodes from default output of Sql Server statement

I want #MyActualXMLOut to look like #MyDesiredXMLOut... How? Thanks in advance!
#MyDesiredXMLOut =
<MyRequiredRoot>
<Property1>Value1</Property1>
<Property2>Value2</Property2>
</MyRequiredRoot>
#MyActualXMLOut
<_x0040_MyTableVar>
<MyXML>
<MyRequiredRoot>
<Property1>Value1</Property1>
<Property2>Value2</Property2>
</MyRequiredRoot>
</MyXML>
</_x0040_MyTableVar>
The code below can be run as is...
DECLARE #MyDesiredXMLOut XML;
DECLARE #MyActualXMLOut XML;
SELECT #MyDesiredXMLOut =
CONVERT( XML,
'<MyRequiredRoot><Property1>Value1</Property1>
<Property2>Value2</Property2>
</MyRequiredRoot>' );
DECLARE #MyTableVar table( ID int NOT NULL, MyXML XML NOT NULL );
INSERT INTO #MyTableVar VALUES( 1, #MyDesiredXMLOut )
SELECT #MyActualXMLOut =
( SELECT MyXML
FROM #MyTableVar
WHERE ID = 1
FOR XML AUTO )
SELECT #MyDesiredXMLOut;
SELECT #MyActualXMLOut;
FOR XML AUTO is trying to add information about your table name (which likely contains characters that aren't valid XML element names) and the column name it came from.
Change
SELECT #MyActualXMLOut =
( SELECT MyXML
FROM #MyTableVar
WHERE ID = 1
FOR XML AUTO )
to
SELECT #MyActualXMLOut =
( SELECT MyXML as '*'
FROM #MyTableVar
WHERE ID = 1
FOR XML PATH('') )
Explanation: as '*' tells SQL Server that you just want the column value directly, don't use the column name as a tag name; FOR XML PATH('') says you don't want to add any additional root node around the output, just use as is.
You can use query('/')
SELECT #MyActualXMLOut =
( SELECT MyXML.query ('/')
FROM #MyTableVar
WHERE ID = 1
FOR XML PATH('')
)
I don't know if this is real life example, but:
The value in the table is the XML already...
Leave away the FOR XML AUTO (Anyway as pointed out one should prefer PATH):
SELECT #MyActualXMLOut =
( SELECT MyXML
FROM #MyTableVar
WHERE ID = 1);
Or even simpler
SELECT #MyActualXMLOut = MyXML
FROM #MyTableVar
WHERE ID = 1;

SQL Server query with list parameter

I am using SQL Server and in a stored procedure I want to execute a query with a list parameter something like this:
select * from table where type in #list_types
Is it possible to make this? Or must I use temporary tables?
You could use table-valued parameters. For example:
-- A table valued parameter must have a type.
-- This command creates the type.
create type YourType as table (type varchar(50))
go
create procedure dbo.YourStoredProcedure(
#types YourType readonly)
as
select *
from YourTable
where type in (select type from #types)
go
You can invoke the stored procedure like this:
declare #types YourType
insert #types (type) values ('Type1'), ('Type2')
exec dbo.YourStoredProcedure #types
ADO.NET supports passing a DataTable as a table-valued parameter.
Try this one -
DECLARE #temp TABLE
(
[type] INT
, name NVARCHAR(50)
)
INSERT INTO #temp ([type], name)
VALUES
(1, '1'),
(2, '2')
DECLARE #list_types VARCHAR(30)
SELECT #list_types = '1,3,4,5'
;WITH cte AS
(
SELECT [type] = p.value('(./s)[1]', 'INT')
FROM (
SELECT field = CAST('<r><s>' + REPLACE(#list_types, ',', '</s></r><r><s>') + '</s></r>' AS XML)
) d
CROSS APPLY field.nodes('/r') t(p)
)
SELECT *
FROM #temp
WHERE [type] IN (SELECT [type] FROM cte)

Insert from single table into multiple tables, invalid column name error

I am trying to do the following but getting an "Invalid Column Name {column}" error. Can someone please help me see the error of my ways? We recently split a transaction table into 2 tables, one containing the often updated report column names and the other containing the unchanging transactions. This leave me trying to change what was a simple insert into 1 table to a complex insert into 2 tables with unique columns. I attempted to do that like so:
INSERT INTO dbo.ReportColumns
(
FullName
,Type
,Classification
)
OUTPUT INSERTED.Date, INSERTED.Amount, INSERTED.Id INTO dbo.Transactions
SELECT
[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}
The "INSERTED.Date, INSERTED.Amount" are the source of the errors, with or without the "INSERTED." in front.
-----------------UPDATE------------------
Aaron was correct and it was impossible to manage with an insert but I was able to vastly improve the functionality of the insert and add some other business rules with the Merge functionality. My final solution resembles the following:
DECLARE #TransactionsTemp TABLE
(
[Date] DATE NOT NULL,
Amount MONEY NOT NULL,
ReportColumnsId INT NOT NULL
)
MERGE INTO dbo.ReportColumns AS Trgt
USING ( SELECT
{FK}
,[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}) AS Src
ON Src.{FK} = Trgt.{FK}
WHEN MATCHED THEN
UPDATE SET
Trgt.FullName = Src.FullName,
Trgt.Type= Src.Type,
Trgt.Classification = Src.Classification
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
FullName,
Type,
Classification
)
VALUES
(
Src.FullName,
Src.Type,
Src.Classification
)
OUTPUT Src.[Date], Src.Amount, INSERTED.Id INTO #TransactionsTemp;
MERGE INTO dbo.FinancialReport AS Trgt
USING (SELECT
[Date] ,
Amount ,
ReportColumnsId
FROM #TransactionsTemp) AS Src
ON Src.[Date] = Trgt.[Date] AND Src.ReportColumnsId = Trgt.ReportColumnsId
WHEN NOT MATCHED BY TARGET And Src.Amount <> 0 THEN
INSERT
(
[Date],
Amount,
ReportColumnsId
)
VALUES
(
Src.[Date],
Src.Amount,
Src.ReportColumnsId
)
WHEN MATCHED And Src.Amount <> 0 THEN
UPDATE SET Trgt.Amount = Src.Amount
WHEN MATCHED And Src.Amount = 0 THEN
DELETE;
Hope that helps someone else in the future. :)
Output clause will return values you are inserting into a table, you need multiple inserts, you can try something like following
declare #staging table (datecolumn date, amount decimal(18,2),
fullname varchar(50), type varchar(10),
Classification varchar(255));
INSERT INTO #staging
SELECT
[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}
Declare #temp table (id int, fullname varchar(50), type varchar(10));
INSERT INTO dbo.ReportColumns
(
FullName
,Type
,Classification
)
OUTPUT INSERTED.id, INSERTED.fullname, INSERTED.type INTO #temp
SELECT
FullName
,Type
,Classification
FROM #stage
INSERT into dbo.transacrions (id, date, amount)
select t.id, s.datecolumn, s.amount from #temp t
inner join #stage s on t.fullname = s.fullname and t.type = s.type
I am fairly certain you will need to have two inserts (or create a view and use an instead of insert trigger). You can only use the OUTPUT clause to send variables or actual inserted values ti another table. You can't use it to split up a select into two destination tables during an insert.
If you provide more information (like how the table has been split up and how the rows are related) we can probably provide a more specific answer.

Resources