How to generate XML path within CASE - sql-server

Edit: Trying to query a XML Path list that has been narrowed down by a case statement. Column 'displayname' contains over 700 unique values throughout the database. However, based on other criteria including the AccountID and if RenderedValue is = '', the remaining results will most likely be less than 5. The variables in my query is I cannot explicitly declare an Account Id or DisplayName.
I have a successful CASE statement on it's own. But trying to also have the XML PATH statement pulls all the data from the table and comma separates it instead of just the results from the previous CASE statement. Can't figure out how to nest them together. Besides the GUID in column 1, values are nvarchar.
Query w/o CASE
select tb1.AccountID,
tb3.DisplayName,
tb4.RenderedValue
from Accounts tb1
join Display tb2 on tb2.AccountID = tb1.AccountID
inner join ExtractDetail tb3 on tb3.ExtractID = tb2.ExtractID
left join ExtractDetailData tb4 on tb4.ExtractDetailID = tb3.ExtractDetailID
result:
+-----------+---------------+-----------------------+
| AccountID | DisplayName | RenderedValue |
+-----------+---------------+-----------------------+
| E8175 | FirstName | John |
| E8175 | LastName | Smith |
| E8175 | StreetAddress | 123 Washington Street |
| E8175 | City | |
| E8175 | State | NY |
| E8175 | ZipCode | |
| E8175 | PhoneNumber | 555-555-5555 |
| E8175 | Email | JohnSmith#aol.com |
+-----------+---------------+-----------------------+
Query w/ CASE
select tb1.AccountID,
CASE When tb4.RenderedValue = ''
Then tb3.DisplayName
Else ''
End As MissingField
from Accounts tb1
join Display tb2 on tb2.AccountID = tb1.AccountID
inner join ExtractDetail tb3 on tb3.ExtractID = tb2.ExtractID
left join ExtractDetailData tb4 on tb4.ExtractDetailID = tb3.ExtractDetailID
Where tb4.RenderedValue =''
Result:
+-----------+--------------+
| AccountID | MissingField |
+-----------+--------------+
| E8175 | City |
| E8175 | ZipCode |
+-----------+--------------+
Expected Output:
+-----------+--------------+
| AccountID | MissingField |
+-----------+--------------+
| E8175 | City,ZipCode |
+-----------+--------------+

i think this code will help you
create table #temp (AccountID varchar(20),DisplayName varchar(20),RenderedValue varchar(255))
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','FirstName','John')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','LastName','Smith')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','StreetAddress','123 Washington Street')
insert into #temp (AccountID,DisplayName,RenderedValue) values ('E8175','City','')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','State','NY')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','ZipCode','')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','PhoneNumber','555-555-5555 ')
insert into #temp (AccountID,DisplayName,RenderedValue) values
('E8175','Email','JohnSmith#aol.com')
SELECT distinct
P.AccountID,
STUFF
(
(
SELECT ',' + case when RenderedValue = '' Then DisplayName Else '' End
FROM #temp M
FOR XML PATH(''), type
).value('.', 'varchar(max)'), 1, 1, ''
) AS Temp
FROM
#temp P
Drop table #temp

Related

Convert Column Data into Row in SQL Server

I have following Store Procedure.
Declare #myData TABLE(
Id int,
Note1 nvarchar(Max),
Note2 nvarchar(Max),
Note3 nvarchar(Max),
)
Insert into #myData (Id,Note1,Note2,Note3)
Select
--First Column
E.Id as [ID],
E.Notes as [Note1],
E.NotesDate as [Date1]
F.ItemDescription as [Note2],
F.DescriptionDate as [Date2]
G.Description as [Note3],
G.DescDate as [Date3]
From
Employee E
LEFT Join Tab1 as F
on I.EmpId = E.Id
LEFT join Tab2 AS G
on G.EmpId = E.Id
where E.Id = 1019
select * from #myData
It gives Following result table.
+------+----------+------------+-------+-------------+---------+--------------+
| ID | Note1 | Date1 | Note2 | Date2 | Note3 | Date3 |
+------+----------+------------+-------+-------------+----------+-------------+
| 1 | admils | 2020-07-02 | sadd | 2020-06-08 | 1230 | 2020-05-06 |
+------+----------+--------+---------+---------------+----------+-------------+
Now i need resulting table some thing like below...
+------+----------+-------------+--------+
| ID | Note1 | Date | Col |
+------+----------+-------------+--------+
| 1 | admils | 2020-07-02 | 1 | <- Because This Data is from Note1 Column
| 1 | sadd | 2020-06-08 | 2 | <- Because This Data is from Note2 Column
| 1 | 1230 | 2020-05-06 | 3 | <- Because This Data is from Note3 Column
+------+----------+-------------+--------+
Just unpivot your data, as shown in the linked duplicate candidate. I prefer using VALUES over UNPIVOT as it's less restrictive:
SELECT E.ID,
V.[Description],
V.[Date],
V.Col
FROM dbo.Employee E
LEFT JOIN dbo.Tab1 T1 ON E.ID = T1.EmpID
LEFT JOIN dbo.Tab2 T2 ON E.ID = T2.EmpID
CROSS APPLY (VALUES(1,E.NotesDate,E.Notes),
(2,T1.DescriptionDate,T1.ItemDescription),
(3,T2.DescDate,T2.Description))V(Col,[Date],[Description]);

How could I update multiple columns in Oracle with same id?

I am new in Oracle SQL and I am trying to make an update of a table with the next context:
I have a table A:
+---------+---------+---------+----------+
| ColumnA | name | ColumnC | Column H |
+---------+---------+---------+----------+
| 1 | Harry | null | null |
| 2 | Harry | null | null |
| 3 | Harry | null | null |
+---------+---------+---------+----------+
And a table B:
+---------+---------+---------+
| name | ColumnE | ColumnF |
+---------+---------+---------+
| Harry | a | d |
| Ron | b | e |
| Hermione| c | f |
+---------+---------+---------+
And I want to update the table A so that the result will be the next:
+---------+---------+---------+----------+
| ColumnA | name | ColumnC | Column H |
+---------+---------+---------+----------+
| 1 | Harry | a | d |
| 2 | Harry | a | d |
| 3 | Harry | a | d |
+---------+---------+---------+----------+
How could I do it?
merge into tableA a
using tableB b
on (a.name=b.name)
when matched then update set
columnC = b.columnE,
columnH = b.columnF
create table tableA (columnC varchar2(20), columnH varchar2(20), name varchar2(20), columnA number);
create table tableB (columnE varchar2(20), columnF varchar2(20), name varchar2(20));
insert into tableA values (null, null,'Harry',1);
insert into tableA values (null, null,'Harry',3);
insert into tableA values (null, null,'Harry',3);
insert into tableB values ('a', 'd','Harry');
insert into tableB values ('b', 'e','Ron');
insert into tableB values ('c', 'f','Hermione');
select * from tableA;
merge into tableA a
using tableB b
on (a.name=b.name)
when matched then update set
columnC = b.columnE,
columnH = b.columnF;
select * from tableA;
I got no error
UPDATE tableA t1
SET (ColumnC, ColumnH) = (SELECT t2.ColumnE, t2.ColumnF
FROM table2 t2
WHERE t1.name = t2.name)
WHERE EXISTS (
SELECT 1
FROM table2 t2
WHERE t1.name = t2.name)
This should work. You can refer to this answer for more info:
Oracle SQL: Update a table with data from another table
I think you can use below query and update your table A.
Update all rows with 'a' and 'd';
update table A
set (columnC , columnh ) = (SELECT COLUMNE,COLUMNF
FROM TABLE B
where b.name =a.name);
Alternatively you can also use:
UPDATE (SELECT T2.COLUMNE COLE,
T2.COLUMNF COLF,
T1.COLUMNC COLC,
T1.COLUMNH COLH
FROM tableB T2,
tableA T1
WHERE T1.NAME = T2.NAME)
SET COLC = COLE,
COLH = COLF ;
and Output is :
+---------+---------+---------+----------+
| ColumnA | name | ColumnC | Column H |
+---------+---------+---------+----------+
| 1 | Harry | a | d |
| 2 | Harry | a | d |
| 3 | Harry | a | d |
+---------+---------+---------+----------+

SQL Server split SELECT XML column as arbitrary individual columns

In my application, I have few pre-defined fields for an object and user can define custom fields. I am using XML data type to store the custom fields in a name value format.
e.g. I have Employees table that has FN, LN, Email as pre-defined columns and CustomFields as XML column to hold the user defined fields.
And different rows can contain different custom fields.
e.g. Row 1 -> John, Smith, jsmith#example.com,
<root>
<phone>123-123-1234</phone>
<country>USA</country>
</root>
and then Row 2 -> Smith, John, sjohn#example.com,
<root>
<age>50</age>
<sex>Male</sex>
</root>
And there can be any number of such custom fields defined for different employee records. The format will always be the same
<root><field>value</field></root>
How can I return Phone and Country as columns while selecting Row1 and return Age and Sex as columns while selecting Row2?
Take this temp table for all examples
CREATE TABLE #tbl (ID INT IDENTITY, FirstName VARCHAR(100),LastName VARCHAR(100),eMail VARCHAR(100),CustomFields XML);
INSERT INTO #tbl VALUES
('John','Smith','john.smith#test.com'
,'<root>
<phone>123-123-1234</phone>
<country>USA</country>
</root>')
, ('Jane','Miller','jane.miller#test.com'
,'<root>
<age>50</age>
<sex>Male</sex>
</root>');
Option 1
Assuming that there is a fix known set of custom fields.
This allows typesafe reading (age as INT)
all possible columns are returned, unused are NULL
Try this code
SELECT tbl.ID
,tbl.FirstName
,tbl.LastName
,tbl.eMail
,tbl.CustomFields.value('(/root/phone)[1]','nvarchar(max)') AS phone
,tbl.CustomFields.value('(/root/country)[1]','nvarchar(max)') AS country
,tbl.CustomFields.value('(/root/age)[1]','int') AS age
,tbl.CustomFields.value('(/root/sex)[1]','nvarchar(max)') AS sex
FROM #tbl AS tbl
This is the result
+----+-----------+----------+----------------------+--------------+---------+------+------+
| ID | FirstName | LastName | eMail | phone | country | age | sex |
+----+-----------+----------+----------------------+--------------+---------+------+------+
| 1 | John | Smith | john.smith#test.com | 123-123-1234 | USA | NULL | NULL |
+----+-----------+----------+----------------------+--------------+---------+------+------+
| 2 | Jane | Miller | jane.miller#test.com | NULL | NULL | 50 | Male |
+----+-----------+----------+----------------------+--------------+---------+------+------+
*/
Option 2
assuming you do not know the field names in advance you cannot name the output columns directly
But you can use generic names, read the data row-wise and do PIVOT
Try this:
SELECT p.*
FROM
(
SELECT tbl.FirstName
,tbl.LastName
,tbl.eMail
,N'Col_' + CAST(ROW_NUMBER() OVER(PARTITION BY tbl.ID ORDER BY (SELECT NULL)) AS NVARCHAR(max)) AS ColumnName
,A.cf.value('local-name(.)','nvarchar(max)') + ':' + A.cf.value('.','nvarchar(max)') AS cf
FROM #tbl AS tbl
CROSS APPLY tbl.CustomFields.nodes('/root/*') AS A(cf)
) AS x
PIVOT
(
MAX(cf) FOR ColumnName IN(Col_1,Col_2,Col_3,Col_4 /*add as many as you need*/)
) AS p
This is the result
+-----------+----------+----------------------+--------------------+-------------+-------+-------+
| FirstName | LastName | eMail | Col_1 | Col_2 | Col_3 | Col_4 |
+-----------+----------+----------------------+--------------------+-------------+-------+-------+
| Jane | Miller | jane.miller#test.com | age:50 | sex:Male | NULL | NULL |
+-----------+----------+----------------------+--------------------+-------------+-------+-------+
| John | Smith | john.smith#test.com | phone:123-123-1234 | country:USA | NULL | NULL |
+-----------+----------+----------------------+--------------------+-------------+-------+-------+
Option 3
assuming you do not know the columns, but you need the columns correctly named
attention: be aware of the fact, that such an approach will never be allowed in ad-hoc-SQL such as VIEW or inline TVF which might be a great back draw...
This needs dynamic creation of a statement. I will create the statement of Option 1 but replace the fix list with a dynamically created list:
DECLARE #DynamicColumns NVARCHAR(MAX)=
(
SELECT ',tbl.CustomFields.value(''(/root/' + A.cf.value('local-name(.)','nvarchar(max)') + ')[1]'',''nvarchar(max)'') AS ' + A.cf.value('local-name(.)','nvarchar(max)')
FROM #tbl AS tbl
CROSS APPLY tbl.CustomFields.nodes('/root/*') AS A(cf)
FOR XML PATH('')
);
DECLARE #DynamicSQL NVARCHAR(MAX)=
' SELECT tbl.ID
,tbl.FirstName
,tbl.LastName
,tbl.eMail'
+ #DynamicColumns +
' FROM #tbl AS tbl;'
EXEC(#DynamicSQL);
The result would be the same as in Option 1, but with a completely dynamic approach.
Cleanup
DROP TABLE #tbl;

SQL Server table combination in a single query

In SQL Server, is it possible to, in a single query regardless of how complex, combine two table in the manner below?
Using a 'full join' I'll have to choose the .name from both tables resulting in duplicate columns, and using a 'union all' I'll get duplicates rows.
Table 1:
+---------+--------+
| name | value1 |
+---------+--------+
| Abel | a |
| Baker | b |
+---------+--------+
Table 2:
+---------+--------+
| name | value2 |
+---------+--------+
| Baker | x |
| Charlie | y |
+---------+--------+
Query output:
+---------+--------+--------+
| name | value1 | value2 |
+---------+--------+--------+
| Abel | a | NULL |
| Baker | b | x |
| Charlie | NULL | y |
+---------+--------+--------+
Using FULL OUTER JOIN you can achieve your expectation.
CREATE TABLE #table1 (name varchar (200), value1 VARCHAR(200))
INSERT INTO #table1
SELECT 'Abel', 'a' UNION
SELECT 'Baker', 'b'
GO
CREATE TABLE #table2 (name varchar (200), value2 VARCHAR(200))
INSERT INTO #table2
SELECT 'Baker', 'x' UNION
SELECT 'Charlie', 'y'
GO
SELECT ISNULL(t1.name, t2.name) AS name, t1.value1, t2.value2
FROM #table1 t1
FULL OUTER JOIN #table2 t2 ON t2.name = t1.name
DROP TABLE #table1
DROP TABLE #table2

Creating a column in runtime

I have a Query
select Distinct EmailAddress as MailId from tblUsers
it shows the emails, but i want to create a column through the query which will show total number of emails.
Please help me
SELECT *, COUNT(1) OVER ()
FROM (
SELECT DISTINCT EmailAddress
FROM tblUsers
) t
Just for R&D -
CREATE FUNCTION dbo.udf_GetCount ()
RETURNS INT
WITH SCHEMABINDING
AS BEGIN
RETURN (SELECT COUNT(DISTINCT EmailAddress) FROM tblUsers)
END
GO
ALTER TABLE dbo.tblUsers
ADD TotalCnt AS dbo.udf_GetCount() PERSISTED
GO
or
SELECT DISTINCT EmailAddress, t.TotalCnt
FROM tblUsers
CROSS JOIN (
SELECT TotalCnt = COUNT(DISTINCT EmailAddress)
FROM tblUsers
) t
Query
CREATE TABLE #email
(
email VARCHAR(MAX)
);
INSERT INTO #email VALUES
('a.c#s'),
('a.c#s'),
('b.c#s'),
('b.c#s'),
('c.c#s'),
('c.c#s'),
('d.c#s');
If you want count of each email, then
SELECT email, COUNT(email) AS [Count]
FROM #email
GROUP BY email;
Result
+-------+-------+
| email | Count |
+-------+-------+
| a.c#s | 2 |
| b.c#s | 2 |
| c.c#s | 2 |
| d.c#s | 1 |
+-------+-------+
else count of total distinct email
SELECT DISTINCT email, (SELECT COUNT(DISTINCT email) FROM #email) AS [Count]
FROM #email;
Result
+-------+-------+
| email | Count |
+-------+-------+
| a.c#s | 4 |
| b.c#s | 4 |
| c.c#s | 4 |
| d.c#s | 4 |
+-------+-------+
SQL Fiddle demo

Resources