I seem struggle with XML. I am looking to get appropriate ID attached to each row
Declare #User table (id int,First_Name varchar(50),Last_Name varchar(50),EMail varchar(50))
Insert into #User values
(1,'John','Smith','john.smith#gmail.com'),
(2,'Jane','Doe' ,'jane.doe#gmail.com')
Declare #XML xml
Set #XML = (Select * from #User for XML RAW)
Select ID = 1 -- < dummy need actual id
,Item = cast(x.v.query('local-name(.)') as varchar(100))
,Value = x.v.value('.','varchar(150)')
From #xml.nodes('//#*') x(v)
My current result is.
ID Item Value
1 id 1
1 First_Name John
1 Last_Name Smith
1 EMail john.smith#gmail.com
1 id 2
1 First_Name Jane
1 Last_Name Doe
1 EMail jane.doe#gmail.com
My Desired result would be.
ID Item Value
1 id 1
1 First_Name John
1 Last_Name Smith
1 EMail john.smith#gmail.com
2 id 2
2 First_Name Jane
2 Last_Name Doe
2 EMail jane.doe#gmail.com
Try it like this:
Btw: You were pretty close!
Declare #User table (id int,First_Name varchar(50),Last_Name varchar(50),EMail varchar(50))
Insert into #User values
(1,'John','Smith','john.smith#gmail.com'),
(2,'Jane','Doe' ,'jane.doe#gmail.com')
Declare #XML xml
Set #XML = (Select * from #User for XML RAW)
SELECT #XML;
/*
<row id="1" First_Name="John" Last_Name="Smith" EMail="john.smith#gmail.com" />
<row id="2" First_Name="Jane" Last_Name="Doe" EMail="jane.doe#gmail.com" />
*/
The first .nodes() will return with all row elements in single rows
The CROSS APPLY .nodes(./#*) will do a row based search for all attributes and deliver them as single rows.
Select r.value('#id','int') AS ID
,Attr.value('local-name(.)','varchar(max)') AS Item
,Attr.value('.','varchar(max)') AS Value
FROM #XML.nodes('/row') AS A(r)
CROSS APPLY A.r.nodes('./#*') AS B(Attr)
Just came across this question. Though answer to this question was already given, thought to answer it with different approach without using xquery.
You can achieve the same result using CROSS APPLY and Table Value Constructor like this -
Declare #User table (id int,First_Name varchar(50),Last_Name varchar(50),EMail varchar(50))
Insert into #User values
(1,'John','Smith','john.smith#gmail.com'),
(2,'Jane','Doe' ,'jane.doe#gmail.com')
SELECT r.ID, t.* FROM #User r
CROSS APPLY (
VALUES ('ID', cast(id as varchar)),
('First_Name', First_Name),
('Last_Name', Last_Name),
('EMail', EMail)
) t(Item, Value)
Result
ID Item Value
---------------------
1 ID 1
1 First_Name John
1 Last_Name Smith
1 EMail john.smith#gmail.com
2 ID 2
2 First_Name Jane
2 Last_Name Doe
2 EMail jane.doe#gmail.com
Related
I have some XML content in a single field; I want to split each xml field in multiple rows.
The XML is something like that:
<env>
<id>id1<\id>
<DailyProperties>
<date>01/01/2022<\date>
<value>1<\value>
<\DailyProperties>
<DailyProperties>
<date>05/05/2022<\date>
<value>2<\value>
<\DailyProperties>
<\env>
I want to put everything in a table as:
ID DATE VALUE
id1 01/01/2022 1
id1 05/05/2022 2
For now I managed to parse the xml value, and I have found something online to get a string into multiple rows (like this), but my string should have some kind of delimiter. I did this:
SELECT
ID,
XMLDATA.X.query('/env/DailyProperties/date').value('.', 'varchar(100)') as r_date,
XMLDATA.X.query('/env/DailyProperties/value').value('.', 'varchar(100)') as r_value
from tableX
outer apply xmlData.nodes('.') as XMLDATA(X)
WHERE ID = 'id1'
but I get all values without a delimiter, as such:
01/10/202202/10/202203/10/202204/10/202205/10/202206/10/202207/10/202208/10/202209/10/202210/10/2022
Or, as in my example:
ID R_DATE R_VALUE
id01 01/01/202205/05/2022 12
I have found out that XQuery has a last() function that return the last value parsed; in my xml example it will return only 05/05/2022, so it should exists something for address the adding of a delimiter. The number of rows could vary, as it could vary the number of days of which I have a value.
Please try the following solution.
I had to fix your XML to make it well-formed.
SQL
DECLARE #tbl TABLE (id INT IDENTITY PRIMARY KEY, xmldata XML);
INSERT INTO #tbl (xmldata) VALUES
(N'<env>
<id>id1</id>
<DailyProperties>
<date>01/01/2022</date>
<value>1</value>
</DailyProperties>
<DailyProperties>
<date>05/05/2022</date>
<value>2</value>
</DailyProperties>
</env>');
SELECT p.value('(id/text())[1]','VARCHAR(20)') AS id
, c.value('(date/text())[1]','VARCHAR(10)') AS [date]
, c.value('(value/text())[1]','INT') AS [value]
FROM #tbl
CROSS APPLY xmldata.nodes('/env') AS t1(p)
OUTER APPLY t1.p.nodes('DailyProperties') AS t2(c);
Output
id
date
value
id1
01/01/2022
1
id1
05/05/2022
2
Yitzhak beat me to it by 2 min. Nonetheless, here's what I have:
--==== XML Data:
DECLARE #xml XML =
'<env>
<id>id1</id>
<DailyProperties>
<date>01/01/2022</date>
<value>1</value>
</DailyProperties>
<DailyProperties>
<date>05/05/2022</date>
<value>2</value>
</DailyProperties>
</env>';
--==== Solution:
SELECT
ID = ff2.xx.value('(text())[1]','varchar(20)'),
[Date] = ff.xx.value('(date/text())[1]', 'date'),
[Value] = ff.xx.value('(value/text())[1]', 'int')
FROM (VALUES(#xml)) AS f(X)
CROSS APPLY f.X.nodes('env/DailyProperties') AS ff(xx)
CROSS APPLY f.X.nodes('env/id') AS ff2(xx);
Returns:
ID Date Value
-------------------- ---------- -----------
id1 2022-01-01 1
id1 2022-05-05 2
I need to rename the column base on the value I give in it.
SELECT TOP 1
[Pratice_Id] = [OID]// id
, [Province] = '' //province
, [Country] = 'US'// country
FROM [tbl_Office]
if the country equals to US I want to change columnName [Province] to State
I would strongly suggest a column STATE_PROVINCE, however, if you MUST and you are not opposed to a temp table, perhaps something like this
Example
Select Top 1 *
Into #Temp
From YourTable
Where ...
Order By ...
If ( Select Top 1 Country From #Temp ) = 'US'
Select [Pratice_Id]
,[State] = [Province]
,[Country]
From #Temp
Else
Select * from #Temp
Returns
Pratice_Id State Country
1 RI US
Personally, I would provide both
Declare #YourTable table ([Pratice_Id] int,[Province] varchar(50),[Country] varchar(50))
Insert Into #YourTable values
(1,'RI','US')
,(2,'Alberta','Canada')
Select [Pratice_Id]
,[State] = case when [Country]='US' then Province else '' end
,[Province] = case when [Country]<>'US' then Province else '' end
,[Country]
From #YourTable
Returns
Pratice_Id State Province Country
1 RI US
2 Alberta Canada
I'm trying to group a SELECT like you'd normally do - AND at the same time make a new "shared/aggregate group" adding that to the original result-set without a secondary SELECT and UNION.
The secondary SELECT and UNION is out of the question since the real use of this is with some very big tables, with a lot of joins, so it would be waay to slow. So the UNION way is definitely out of the question.
I've tried my best to illustrate this with the following simplified example:
BEGIN TRAN
CREATE TABLE #MyTable
(
id INT,
name VARCHAR(255)
)
INSERT INTO #MyTable VALUES (1,'cola');
INSERT INTO #MyTable VALUES (2,'cola');
INSERT INTO #MyTable VALUES (3,'cola');
INSERT INTO #MyTable VALUES (4,'fanta');
INSERT INTO #MyTable VALUES (5,'fanta');
INSERT INTO #MyTable VALUES (6,'fanta');
INSERT INTO #MyTable VALUES (7,'water');
INSERT INTO #MyTable VALUES (8,'water');
INSERT INTO #MyTable VALUES (9,'water');
INSERT INTO #MyTable VALUES (10,'cola');
INSERT INTO #MyTable VALUES (11,'cola');
SELECT
CASE
WHEN name = 'cola' OR name = 'fanta'
THEN 'soda'
ELSE
name
END as name,
COUNT(distinct id) as count
FROM #MyTable
GROUP BY name
ROLLBACK TRAN
Actual output:
soda 5
soda 3
water 3
Desired output:
cola 5
fanta 3
soda 8 <- this is the "shared/aggregate group"
water 3
As Panagiotis Kanavos correctly pointed out in the comment above, this can be done using ROLLUP:
BEGIN TRAN
CREATE TABLE #BeverageType
(
name VARCHAR(255)
)
INSERT INTO #BeverageType VALUES ('Soda');
INSERT INTO #BeverageType VALUES ('Other');
CREATE TABLE #UserBeverage
(
id INT,
name VARCHAR(255)
)
INSERT INTO #UserBeverage VALUES (1,'cola');
INSERT INTO #UserBeverage VALUES (2,'cola');
INSERT INTO #UserBeverage VALUES (3,'cola');
INSERT INTO #UserBeverage VALUES (1,'fanta'); -- <- NOTE: user 1 drinks both cola and fanta so the as intended the user is only counted 1 time in the ROLLUP 'Soda' group (7)
INSERT INTO #UserBeverage VALUES (5,'fanta');
INSERT INTO #UserBeverage VALUES (6,'fanta');
INSERT INTO #UserBeverage VALUES (7,'water');
INSERT INTO #UserBeverage VALUES (8,'water');
INSERT INTO #UserBeverage VALUES (9,'water');
INSERT INTO #UserBeverage VALUES (10,'cola');
INSERT INTO #UserBeverage VALUES (11,'cola');
SELECT ub.name, bt.name AS groupName, COUNT(distinct id) as uniqueUserCount
FROM #UserBeverage as ub
JOIN #BeverageType as bt
ON CASE
WHEN (ub.name = 'water')
THEN 'Other'
ELSE
'Soda'
END = bt.name
GROUP BY ROLLUP(bt.name, ub.name)
ROLLBACK TRAN
Outputs:
cola Soda 5
fanta Soda 3
water Other 3
NULL Other 3
NULL Soda 7
NULL NULL 10
You should repeat CASE statement everywhere.
SELECT
CASE WHEN name = 'cola' OR name = 'fanta'
THEN 'soda' ELSE name END as name,
COUNT((CASE WHEN name = 'cola' OR name = 'fanta'
THEN 'soda' ELSE name END)) as count
FROM #MyTable
GROUP BY CASE WHEN name = 'cola' OR name = 'fanta'
THEN 'soda' ELSE name END
+-------+-------+
| name | count |
+-------+-------+
| soda | 8 |
+-------+-------+
| water | 3 |
+-------+-------+
Can I suggest to use a subquery:
SELECT name, count(*) AS count
FROM (SELECT CASE WHEN name = 'cola' OR name = 'fanta'
THEN 'soda' ELSE name END as name
FROM #MyTable) x
GROUP BY name;
If you need the aggregate as well as the individual products, then an alternative may be to use a UNION and select the aggregates as a second query.
SELECT name, count(distinct id) as count
FROM #MyTable
GROUP BY name
UNION
SELECT 'SODA', COUNT(distinct id) as count
FROM #MyTable
WHERE name = 'cola' or name ='fanta'
You might also use Søren Høyer Kristensen's summary table to get the aggregate names if you need more groupings.
I do have following table
ID Name
1 Jagan Mohan Reddy868
2 Jagan Mohan Reddy869
3 Jagan Mohan Reddy
Name column size is VARCHAR(55).
Now for some other task we need to take only 10 varchar length i.e. VARCHAR(10).
My requirement is to check that after taking the only 10 bits length of Name column value for eg if i take Name value of ID 1 i.e. Jagan Mohan Reddy868 by SUBSTRING(Name, 0,11) if it equals with another row value. here in this case the final value of SUBSTRING(Jagan Mohan Reddy868, 0,11) is equal to Name value of ID 3 row whose Name is 'Jagan Mohan Reddy'. I need to make a list of those kind rows. Can somebody help me out on how can i achieve in SQL Server.
My main check is that the truncated values of my Name column should not match with any non truncated values of Name column. If so i need to get those records.
Assuming I understand the question, I think you are looking for something like this:
Create and populate sample data (Please save us this step in your future questions)
DECLARE #T as TABLE
(
Id int identity(1,1),
Name varchar(15)
)
INSERT INTO #T VALUES
('Hi, I am Zohar.'),
('Hi, I am Peled.'),
('Hi, I am Z'),
('I''m Zohar peled')
Use a cte with a self inner join to get the list of ids that match the first 10 chars:
;WITH cte as
(
SELECT T2.Id As Id1, T1.Id As Id2
FROM #T T1
INNER JOIN #T T2 ON LEFT(T1.Name, 10) = t2.Name AND T1.Id <> T2.Id
)
Select the records from the original table, inner joined with a union of the Id1 and Id2 from the cte:
SELECT T.Id, Name
FROM #T T
INNER JOIN
(
SELECT Id1 As Id
FROM CTE
UNION
SELECT Id2
FROM CTE
) U ON T.Id = U.Id
Results:
Id Name
----------- ---------------
1 Hi, I am Zohar.
3 Hi, I am Z
Try this
SELECT Id,Name
FROM(
SELECT *,ROW_NUMBER() OVER(PARTITION BY Name, LEFT(Name,11) ORDER BY ID) RN
FROM Tbale1 T
) Tmp
WHERE Tmp.RN = 1
loop over your column for all the values and put your substring() function inside this loop and I think in Sql index of string starts from 1 instead of 0. If you pass your string to charindex() like this
CHARINDEX('Y', 'Your String')
thus you will come to know whether it is starting from 0 or 1
and you can save your substring value as value of other column with length 10
I hope it will help you..
I think this should cover all the cases you are looking for.
-- Create Table
DECLARE #T as TABLE
(
Id int identity(1,1),
Name varchar(55)
)
-- Create Data
INSERT INTO #T VALUES
('Jagan Mohan Reddy868'),
('Jagan Mohan Reddy869'),
('Jagan Mohan Reddy'),
('Mohan Reddy'),
('Mohan Reddy123551'),
('Mohan R')
-- Get Matching Items
select *, SUBSTRING(name, 0, 11) as ShorterName
from #T
where SUBSTRING(name, 0, 11) in
(
-- get all shortnames with a count > 1
select SUBSTRING(name, 0, 11) as ShortName
from #T
group by SUBSTRING(name, 0, 11)
having COUNT(*) > 1
)
order by Name, LEN(Name)
I have some parent/child data across two tables. I need to copy the parent rows back into the parent table, but then also copy the child rows as child rows of the new rows created.
I have been searching this site and Google, but can only find examples from Oracle or that use XML (or have many warnings about not being reliable), so am posting here for a complete easy-to-refer-back-to solution.
Take the following code (SqlFiddle):
DECLARE #tbl_person TABLE
(
ID int IDENTITY(1,1),
person nvarchar(20)
);
DECLARE #tbl_drinks TABLE
(
ID int IDENTITY(1,1),
personID int,
drink nvarchar(20)
);
DECLARE #i int;
INSERT INTO #tbl_person (person) VALUES ('Bob');
SET #i = SCOPE_IDENTITY();
INSERT INTO #tbl_drinks (personID, drink) VALUES (#i, 'Beer');
INSERT INTO #tbl_person (person) VALUES ('Wendy');
SET #i = SCOPE_IDENTITY();
INSERT INTO #tbl_drinks (personID, drink) VALUES (#i, 'Champage');
INSERT INTO #tbl_drinks (personID, drink) VALUES (#i, 'Water');
INSERT INTO #tbl_person (person) VALUES ('Mike');
SET #i = SCOPE_IDENTITY();
INSERT INTO #tbl_drinks (personID, drink) VALUES (#i, 'Beer');
INSERT INTO #tbl_drinks (personID, drink) VALUES (#i, 'Lemonade');
SELECT * FROM #tbl_person;
SELECT * FROM #tbl_drinks;
This produces this output:
ID person
----------- --------------------
1 Bob
2 Wendy
3 Mike
ID personID drink
----------- ----------- --------------------
1 1 Beer
2 2 Champage
3 2 Water
4 3 Beer
5 3 Lemonade
I know how to easily duplicate a single person plus their drinks, but not multiple people. Assuming I need to duplicate Bob and Wendy I need to get to this output:
ID person
----------- --------------------
1 Bob
2 Wendy
3 Mike
4 Bob
5 Wendy
ID personID drink
----------- ----------- --------------------
1 1 Beer
2 2 Champage
3 2 Water
4 3 Beer
5 3 Lemonade
6 4 Beer
7 5 Champagne
8 5 Water
I cannot figure out how to compare the old and new parent ID columns in order to get the child data.
The problem is that INSERT doesn't really have a "from table" that you could reference in the OUTPUT clause. But you could achieve the same with MERGE statement:
declare #tbl_IDmap table (newID int, oldID int)
merge #tbl_person as target
using (
select ID, person from #tbl_person where ID in (1,2)
) as source(ID, person)
on 1=0
when not matched then
insert (person) values(person)
output inserted.ID, source.ID into #tbl_IDmap;
And then duplicate the drinks with the new IDs:
insert into #tbl_drinks(personID, drink)
select m.newID, d.drink
from #tbl_drinks d
inner join #tbl_IDmap m
on m.oldID = d.personID
Here is your SqlFiddle updated.
Determined to add some additional solutions (and having thought about this most of the night!) I am posting an additional solution that doesn't use MERGE, hopefully to help users with older versions of SQL. It's more verbose than #TomT's suggestion but works okay.
SQLFiddle
-- Gather the people we need to copy
DECLARE #tbl_IdsToCopy TABLE
(
[counter] int IDENTITY(1,1),
[existingId] int
);
INSERT INTO #tbl_IdsToCopy (existingId) VALUES (1),(2); -- Bob & Wendy
-- Table to save new person ID's
DECLARE #tbl_newIds TABLE
(
[counter] int IDENTITY(1,1),
[newId] int
);
-- Create new people and save their new Id's
INSERT INTO #tbl_person
(
person
)
OUTPUT
INSERTED.ID
INTO
#tbl_newIds
(
[newId]
)
SELECT
p.person
FROM
#tbl_person p INNER JOIN
#tbl_IdsToCopy c ON c.existingId = p.ID
ORDER BY
c.[counter]; -- use counter to preserve ordering
-- map the old ID's to the new ID's and find the drinks for the old ID's
INSERT INTO #tbl_drinks
(
personID,
drink
)
SELECT
n.[newId],
d.drink
FROM
#tbl_IdsToCopy c INNER JOIN
#tbl_newIds n ON c.[counter] = n.[counter] INNER JOIN -- <-- map the old person ID to the new person Id
#tbl_drinks d ON d.personID = c.existingId; -- <-- find the drinks of the old person Id
-- Results
SELECT
p.ID,
p.person,
d.ID,
d.drink
FROM
#tbl_person p INNER JOIN
#tbl_drinks d ON d.personID = p.ID;