I have a data in one table. I need to copy it to another table. One of the column is text delimited string. So what I'm thinking to select all columns insert get indentity value and with subquery to split based on delimiter and insert it to another table.
Here is the data example
ID Name City Items
1 Michael Miami item|item2|item3|item4|item5
2 Jorge Hallandale item|item2|item3|item4|item5
copy Name, City to one table get identity
and split and copy Items to another table with Identity Column Value
So output should be
Users table
UserID Name City
1 Michael Miami
2 Jorge Hallandale
...
Items table
ItemID UserID Name
1 1 Item
2 1 Item2
3 1 Item3
4 1 Item4
5 2 Item
6 2 Item2
7 2 Item3
8 2 Item4
Not really sure how to do it with T-SQL. Answers with examples would be appreciated
You may create you custom function to split the string in T-Sql. You could then use the Split function as part of a JOIN with your base table to generate the final results for your INSERT statement. Have a look at this post. Hope this help.
You can do this using xml and cross apply.
See the following:
DECLARE #t table (ID int, Name varchar(20), City varchar(20), Items varchar(max));
INSERT #t
SELECT 1,'Michael','Miami' ,'item|item2|item3|item4|item5' UNION
SELECT 2,'Jorge' ,'Hallandale','item|item2|item3|item4|item5'
DECLARE #u table (UserID int identity(1,1), Name varchar(20), City varchar(20));
INSERT #u (Name, City)
SELECT DISTINCT Name, City FROM #t
DECLARE #i table (ItemID int identity(1,1), UserID int, Name varchar(20));
WITH cte_Items (Name, Items) as (
SELECT
Name
,CAST(REPLACE('<r><i>' + Items + '</i></r>','|','</i><i>') as xml) as Items
FROM
#t
)
INSERT #i (UserID, Name)
SELECT
u.UserID
,s.Name as Name
FROM
cte_Items t
CROSS APPLY (SELECT i.value('.','varchar(20)') as Name FROM t.Items.nodes('//r/i') as x(i) ) s
INNER JOIN #u u ON t.Name = u.Name
SELECT * FROM #i
See more here:
http://www.kodyaz.com/articles/t-sql-convert-split-delimeted-string-as-rows-using-xml.aspx
Can you accomplish this with recursion? My T-SQL is rusty but this may help send you in the right direction:
WITH CteList AS (
SELECT 0 AS ItemId
, 0 AS DelimPos
, 0 AS Item_Num
, CAST('' AS VARCHAR(100)) AS Item
, Items AS Remainder
FROM Table1
UNION ALL
SELECT Row_Number() OVER(ORDER BY UserID) AS ItemId
, UserID
, CASE WHEN CHARINDEX('|', Remainder) > 0
THEN CHARINDXEX('|', Remainder)
ELSE LEN(Remainder)
END AS dpos
, Item_num + 1 as Item_Num
, REPLACE(Remainder, '|', '') AS Element
, right(Remainder, dpos+1) AS Remainder
FROM CteList
WHERE dpos > 0
AND ItemNum < 20 /* Force a MAX depth for recursion */
)
SELECT ItemId
, Item
FROM CteList
WHERE item_num > 0
ORDER BY ItemID, Item_Num
Related
I currently have a query that looks like this:
Select val1, val2, val3, val4 from Table_A where someID = 10
UNION
Select oth1, val2, val3, oth4 from Table_B where someId = 10
I initially run this same query above but with EXCEPT, to identify which ID's are returned with differences, and then I do a UNION query to find which columns specifically are different.
My goal is to compare the values between the two tables (some columns have different names). And that's what I'm doing.
However, the two queries above have about 250 different field names, so it is quite mundane to scroll through to find the differences.
Is there a better and quicker way to identify which column names are different after running the two queries?
EDIT: Here's my current process:
DROP TABLE IF EXISTS #Table_1
DROP TABLE IF EXISTS #Table_2
SELECT 'Dave' AS Name, 'Smih' AS LName, 18 AS Age, 'Alabama' AS State
INTO #Table_1
SELECT 'Dave' AS Name, 'Smith' AS LName, 19 AS Age, 'Alabama' AS State
INTO #Table_2
--FInd differences
SELECT Name, LName,Age,State FROM #Table_1
EXCEPT
SELECT Name, LName,Age,State FROM #Table_2
--How I compare differences
SELECT Name, LName,Age,State FROM #Table_1
UNION
SELECT Name, LName,Age,State FROM #Table_2
Is there any way to streamline this so I can get a column list of differences?
Here is a generic way to handle two tables differences.
We just need to know their primary key column.
It is based on JSON, and will work starting from SQL Server 2016 onwards.
SQL
-- DDL and sample data population, start
DECLARE #TableA TABLE (rowid INT IDENTITY(1,1), FirstName VARCHAR(100), LastName VARCHAR(100), Phone VARCHAR(100));
DECLARE #TableB table (rowid int Identity(1,1), FirstName varchar(100), LastName varchar(100), Phone varchar(100));
INSERT INTO #TableA(FirstName, LastName, Phone) VALUES
('JORGE','LUIS','41514493'),
('JUAN','ROBERRTO','41324133'),
('ALBERTO','JOSE','41514461'),
('JULIO','ESTUARDO','56201550'),
('ALFREDO','JOSE','32356654'),
('LUIS','FERNANDO','98596210');
INSERT INTO #TableB(FirstName, LastName, Phone) VALUES
('JORGE','LUIS','41514493'),
('JUAN','ROBERTO','41324132'),
('ALBERTO','JOSE','41514461'),
('JULIO','ESTUARDO','56201551'),
('ALFRIDO','JOSE','32356653'),
('LUIS','FERNANDOO','98596210');
-- DDL and sample data population, end
SELECT rowid
,[key] AS [column]
,Org_Value = MAX( CASE WHEN Src=1 THEN Value END)
,New_Value = MAX( CASE WHEN Src=2 THEN Value END)
FROM (
SELECT Src=1
,rowid
,B.*
FROM #TableA A
CROSS APPLY ( SELECT [Key]
,Value
FROM OpenJson( (SELECT A.* For JSON Path,Without_Array_Wrapper,INCLUDE_NULL_VALUES))
) AS B
UNION ALL
SELECT Src=2
,rowid
,B.*
FROM #TableB A
CROSS APPLY ( SELECT [Key]
,Value
FROM OpenJson( (SELECT A.* For JSON Path,Without_Array_Wrapper,INCLUDE_NULL_VALUES))
) AS B
) AS A
GROUP BY rowid,[key]
HAVING MAX(CASE WHEN Src=1 THEN Value END)
<> MAX(CASE WHEN Src=2 THEN Value END)
ORDER BY rowid,[key];
Output
rowid
column
Org_Value
New_Value
2
LastName
ROBERRTO
ROBERTO
2
Phone
41324133
41324132
4
Phone
56201550
56201551
5
FirstName
ALFREDO
ALFRIDO
5
Phone
32356654
32356653
6
LastName
FERNANDO
FERNANDOO
I have a dbo.products where all the products are stored. These products have a unique ID, Name, category and Price.
In a customer database (dbo.customers) the selected orders are pipe separated in one column. For example "12|61|42|48|56|57". Is it possible to get the total price based on what the customer selects? So if 12 is the ID of a price 10 product and 61 is a price 8 product the outcome should be 18 when the product column is "12|61"?
I've tried to refer to the id of the dbo.prices but I can only select one price at a time.
select Price
from [dbo].[products]
where id in (12|61|42|48|56|57)
So what I expect is the total value of all the selected product, please help.
Made third table as orders store customer selected products in that table ,
after that manage foreign key,primary key structure which will identify customer and products data.
then will get what you want.
use query
sum() for calculating total price etc.
So let's assume that you get your data from some external system, and you are forced to accept that orders will be pipe-delimited garbage. Let's also assume you don't have the capacity to split this raw data out into a normalised format, storing it in a proper, relational manner. So in this world you are stuck with a bad data model, and all you can do is query it.
First thing you will need is a way to split out the orders into a usable format. You could use the built in SPLIT function, but if you don't have access to this because you're on an older version of SQL Server then you could write your own. Here's an example:
CREATE FUNCTION [dbo].[split] (
#string VARCHAR(MAX),
#delimiter CHAR(1))
RETURNS TABLE
AS
RETURN
(
WITH Pieces(id, [start], [stop]) AS (
SELECT 1, 1, CONVERT(INT, CHARINDEX(#delimiter, #string))
UNION ALL
SELECT ID + 1, [stop] + 1, CONVERT(INT, CHARINDEX(#delimiter, #string, [stop] + 1)) FROM Pieces WHERE [stop] > 0
)
SELECT
id,
SUBSTRING(#string, [start], CASE WHEN [stop] > 0 THEN [stop] - [start] ELSE LEN(#String) END) AS item
FROM
Pieces
);
Once you have that in place you can do things like this:
SELECT * FROM dbo.split('12|61|42|48|56|57', '|');
Results:
id item
1 12
2 61
3 42
4 48
5 56
6 57
Now you can finally construct a query to bring all these parts together. Here's an example, using table variables, as I had to guess what your schema looks like:
DECLARE #customer TABLE (id INT, customer_name VARCHAR(20), [order] VARCHAR(50));
DECLARE #price TABLE (id INT, price INT);
INSERT INTO #customer SELECT 1, 'Fred', '1|2|3';
INSERT INTO #customer SELECT 2, 'Bill', '2|4';
INSERT INTO #customer SELECT 3, 'Mary', '3';
INSERT INTO #price SELECT 1, 5;
INSERT INTO #price SELECT 2, 3;
INSERT INTO #price SELECT 3, 2;
INSERT INTO #price SELECT 4, 1;
SELECT c.id, c.customer_name, COUNT(s.item) AS items_ordered, SUM(p.price) AS total_price FROM #customer c CROSS APPLY dbo.split(c.[order], '|') s INNER JOIN #price p ON p.id = s.item GROUP BY c.id, c.customer_name ORDER BY 1;
Results:
id customer_name items_ordered total_price
1 Fred 3 10
2 Bill 2 4
3 Mary 1 2
I am trying to figure out how to go about getting the values of a comma separated string that's present in one of my cells.
This is the query I current am trying to figure out in my stored procedure:
SELECT
uT.id,
uT.permissions
FROM
usersTbl AS uT
INNER JOIN
usersPermissions AS uP
/*Need to loop here I think?*/
WHERE
uT.active = 'true'
AND
uT.email = 'bbarker#thepriceisright.com'
The usersPermissions table looks like this:
And so a row in the usersTbl table looks like this for permissions:
1,3
I need to find a way to loop through that cell and get each number and place the name ****, in my returned results for the usersTbl.permissions.
So instead of returning this:
Name | id | permissions | age |
------------------------------------
Bbarker | 5987 | 1,3 | 87 |
It needs to returns this:
Name | id | permissions | age |
------------------------------------
Bbarker | 5987 | Read,Upload | 87 |
Really just replacing 1,3 with Read,Upload.
Any help would be great from a SQL GURU!
Reworked query
SELECT
*
FROM
usersTbl AS uT
INNER JOIN
usersPermissionsTbl AS uPT
ON
uPT.userId = uT.id
INNER JOIN
usersPermissions AS uP
ON
uPT.permissionId = uP.id
WHERE
uT.active='true'
AND
uT.email='bBarker#thepriceisright.com'
I agree with all of the comments... but strictly trying to do what you want, here's a way with a splitter function
declare #usersTbl table ([Name] varchar(64), id int, [permissions] varchar(64), age int)
insert into #usersTbl
values
('Bbarker',5987,'1,3',87)
declare #usersTblpermissions table (id int, [type] varchar(64))
insert into #usersTblpermissions
values
(1,'Read'),
(2,'Write'),
(3,'Upload'),
(4,'Admin')
;with cte as(
select
u.[Name]
,u.id as UID
,p.id
,p.type
,u.age
from #usersTbl u
cross apply dbo.DelimitedSplit8K([permissions],',') x
inner join #usersTblpermissions p on p.id = x.Item)
select distinct
[Name]
,UID
,age
,STUFF((
SELECT ',' + t2.type
FROM cte t2
WHERE t.UID = t2.UID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
from cte t
Jeff Moden Splitter
CREATE FUNCTION [dbo].[DelimitedSplit8K] (#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
/* "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
enough to cover VARCHAR(8000)*/
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
GO
First, you should read Is storing a delimited list in a database column really that bad?, where you will see a lot of reasons why the answer to this question is Absolutely yes!
Second, you should add a table for user permissions since this is clearly a many to many relationship.
Your tables might look something like this (pseudo code):
usersTbl
(
Id int primary key
-- other user related columns
)
usersPermissionsTbl
(
UserId int, -- Foreign key to usersTbl
PermissionId int, -- Foreign key to permissionsTbl
Primary key (UserId, PermissionId)
)
permissionsTbl
(
Id int primary key,
Name varchar(20)
)
Once you have your tables correct, it's quite easy to get a list of comma separated values from the permissions table.
Adapting scsimon's sample data script to a correct many to many relationship:
declare #users table ([Name] varchar(64), id int, age int)
insert into #users values
('Bbarker',5987,87)
declare #permissions table (id int, [type] varchar(64))
insert into #permissions values
(1,'Read'),
(2,'Write'),
(3,'Upload'),
(4,'Admin')
declare #usersPermissions as table (userId int, permissionId int)
insert into #usersPermissions values (5987, 1), (5987, 3)
Now the query looks like this:
SELECT u.Name,
u.Id,
STUFF(
(
SELECT ','+ [type]
FROM #permissions p
INNER JOIN #usersPermissions up ON p.id = up.permissionId
WHERE up.userId = u.Id
FOR XML PATH('')
)
, 1, 1, '') As Permissions,
u.Age
FROM #Users As u
And the results:
Name Id Permissions Age
Bbarker 5987 Read,Upload 87
You can see a live demo on rextester.
I concur with much of the advice being presented to you in the other responses. The structure you're starting with is not going to be fun to maintain and work with. However, your situation may mean you are stuck with it so maybe some of the tools below will help you.
You can parse the delimiter with charindex() as others demonstrated here- MSSQL - How to split a string using a comma as a separator
... and even better here (several functions are provided) - Split function equivalent in T-SQL?
If you still want to do it with raw inline SQL and are committed to a loop, then pair the string manipulation with a CURSOR. Cursors have their own controversies BTW. The code below will work if your permission syntax remains consistent, which it probably doesn't.
They used charindex(',',columnName) and fed the location into the left() and right() functions along with some additional string evaluation to pull values out. You should be able to piece those together with a cursor
Your query might look like this...
--creating my temp structure
declare #userPermissions table (id int, [type] varchar(16))
insert into #userPermissions (id, [type]) values (1, 'Read')
insert into #userPermissions (id, [type]) values (2, 'Write')
insert into #userPermissions (id, [type]) values (3, 'Upload')
insert into #userPermissions (id, [type]) values (4, 'Admin')
declare #usersTbl table ([Name] varchar(16), id int, [permissions] varchar(8), age int)
insert into #usersTbl ([Name], id, [permissions], age) values ('Bbarker', 5987, '1,3', 87)
insert into #usersTbl ([Name], id, [permissions], age) values ('Mmouse', 5988, '2,4', 88)
--example query
select
ut.[Name]
, (select [type] from #userPermissions where [id] = left(ut.[permissions], charindex(',', ut.[permissions])-1) )
+ ','
+ (select [type] from #userPermissions where [id] = right(ut.[permissions], len(ut.[permissions])-charindex(',', ut.[permissions])) )
from #usersTbl ut
This question already has answers here:
Pivot in sql server
(4 answers)
Closed 8 years ago.
I have the two tables which need to be linked and present the data in human friendly way. Could you experts point me in right direction, I am bit stuck here.
Both table 1 and table2 are received through ftp and loaded to SQL table in SQL 2008 R2. These two tables are linked by nid and cid together.
Apologies i couldn't copy paste table here, please consider "-" are column separators
Table 1
ID nid cid pid form_key-name
1 4 1 33 Import_Gen_Title-Title
2 4 2 33 Import_Gen_Firstname-Firstname
3 4 3 33 Import_Gen_Surname-Surname
4 4 4 33 Import_Gen_AddressLine1-AddressLine1
5 4 5 33 Import_Gen_AddressLine2-AddressLine2
6 4 6 33 Import_Gen_City-Town/City
7 4 7 33 Import_Gen_Zip-Post code
Table 2
ID nid sid cid data
1 4 14 1 Mr
2 4 14 2 John
3 4 14 3 Smith
4 4 14 4 A Company
5 4 14 5 Nice Street
6 4 14 6 London
7 4 14 7 SE11 0TS
Now how can get this a Result Table like this one below ?
NiD SID Title Firstname Surname AddressLine1 AddressLine2 Town/City-Post code
4 14 Mr John Smith A Company Nice Street London-SE11 0TS
Check my answer for this same question here: Pivot in sql server
I made a stored procedure that uses the PIVOT statement shown by salvoo - but it's more generic, you don't have to know all the columns in advance.
Take a look.
Here is the example using your data - note that the default output of the pivot has the columns in alphabetical order which you don't want - so I used the option to output the pivot to a temp table then queried directly from the temp table to put the columns in the order you wanted.
drop table Fields
go
drop table Data
go
create table Fields
(
ID Integer,
nid Integer,
cid Integer,
pid Integer,
form_key_name varchar(50)
);
go
create table Data
(
ID Integer,
nid Integer,
sid Integer,
cid Integer,
data varchar(50)
);
go
insert into Fields values (1,4,1,33,'Import_Gen_Title-Title')
go
insert into Fields values (2,4,2,33,'Import_Gen_Firstname-Firstname')
go
insert into Fields values (3,4,3,33,'Import_Gen_Surname-Surname')
go
insert into Fields values (4,4,4,33,'Import_Gen_AddressLine1-AddressLine1')
go
insert into Fields values (5,4,5,33,'Import_Gen_AddressLine2-AddressLine2')
go
insert into Fields values (6,4,6,33,'Import_Gen_City-Town/City')
go
insert into Fields values (7,4,7,33,'Import_Gen_Zip-Post code')
go
insert into Data values (1,4,14,1,'Mr')
go
insert into Data values (2,4,14,2,'John')
go
insert into Data values (3,4,14,3,'Smith')
go
insert into Data values (4,4,14,4,'A Company')
go
insert into Data values (5,4,14,5,'Nice Street')
go
insert into Data values (6,4,14,6,'London')
go
insert into Data values (7,4,14,7,'SE11 0TS')
go
declare #mySQL varchar(MAX);
set #mySQL = '
select
f.nid,
d.sid,
right(f.form_key_name, len(f.form_key_name) - charindex(''-'',f.form_key_name)) form_key_name,
d.data
from
Fields f
JOIN Data d
on ( d.nid = f.nid
and d.cid = f.cid )
';
exec pivot_query #mySQL, 'nid, sid', 'form_key_name','max(data)', '##tmppivot';
select
nid,
sid,
Title,
Firstname,
Surname,
AddressLine1,
AddressLine2,
[Town/City],
[Post code]
from
##tmppivot;
go
With Pivot: Fiddle demo
SELECT sid, nid,
[Import_Gen_Title-Title] as Title,
[Import_Gen_Firstname-Firstname] as Name,
[Import_Gen_Surname-Surname] as SurName,
[Import_Gen_AddressLine1-AddressLine1] as Address1,
[Import_Gen_AddressLine2-AddressLine2] as Address2,
[Import_Gen_City-Town/City] as Town,
[Import_Gen_Zip-Post code] as PostCode
FROM
(
SELECT t2.sid, t2.nid, t2.dat, t1.form_key
FROM tab1 t1 INNER JOIN tab2 t2 ON t1.nid = t2.nid AND t1.cid = t2.cid
) x
PIVOT
(
min(x.dat)
for x.form_key in ([Import_Gen_Title-Title],
[Import_Gen_Firstname-Firstname],
[Import_Gen_Surname-Surname],
[Import_Gen_AddressLine1-AddressLine1],
[Import_Gen_AddressLine2-AddressLine2],
[Import_Gen_City-Town/City],
[Import_Gen_Zip-Post code]
)
) pvt
Edit (Added generic version): Fiddle demo
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT ',[' + t.form_key + ']'
FROM tab1 t
group by t.form_key, cid
order by t.cid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'');
SET #query = 'SELECT * FROM
(
SELECT t2.sid, t2.nid, t2.dat, t1.form_key
FROM tab1 t1 INNER JOIN tab2 t2 ON t1.nid = t2.nid AND t1.cid = t2.cid
) x
PIVOT
(
min(x.dat)
FOR x.form_key IN (' + #cols + ')
) pvt;';
execute(#query);
I can do this using cursors, but I'm trying to avoid it if at all possible. Here's a bit of sample code that I've got going:
declare #string varchar(max) = 'person1:item1~item2~item3~etc^person2:item1~item2~item3~etc'
declare #table1 table (id int, value varchar(500))
declare #table2 table (id varchar(50), value varchar(500))
declare #table3 table (id varchar(50), value varchar(50))
insert #table1 (id, value) select * from fn_ParseDelimitedStrings(#string, '^')
insert #table2 (id, value)
select
id = (select f.value from fn_ParseDelimitedStrings(t.value, ':') f where f.RowId=1),
value = (select f.value from fn_ParseDelimitedStrings(t.value, ':') f where f.RowId=2)
from #table1 t
select * from #table2
The above code gives me the data in the form of:
id value
-------------------------------
person1 item1~item2~item3~etc
person2 item1~item2~item3~etc
But I need the data in this form:
id value
-------------------------------
person1 item1
person1 item2
person1 item3
person1 etc
person2 item1
person2 item2
person2 item3
person3 etc
The input string can have any number of "persons", and each person can have any number of "items".
fn_ParseDelimitedStrings is a custom function we have that returns a table of index-value pairs for each delimited item. ie:
RowID Value
-------------
1 item1
2 item2
3 item3
4 etc
I'm having trouble associating each "item" from the final split to the "person" that they should be associated with.
Is there anything that can be done or am I going to have to use a cursor?
outer apply will join current row with all the rows found in derived table inside outer apply:
insert #table2 (id, value)
select
id = (select f.value from fn_ParseDelimitedStrings(t.value, ':') f where f.RowId=1),
value = v1.value
from #table1 t
outer apply
(
select v.value
from fn_ParseDelimitedStrings(
(select f.value
from fn_ParseDelimitedStrings(t.value, ':') f
where f.RowId=2)
, '~') v
) v1
*Edited value1 to v1 to match the outermost select