I've been reading other similar "multiple records into one" posts, but either cannot seem to get any to work, or they don't really apply to what I am trying to do.
Here are my 3 tables. vehicle, vehicle_repair, comments
vehicle columns: vehicle_name and other vehicle related info,vehicle_make, vehicle_model
vehicle_repair columns: vehicle_name, vehicle_repair_type, vehicle_repair_num, etc, etc
comments columns: vehicle_name,vehicle_repair_num, comments_detail
The way the program is written, if I write more than 1 line of comments, it doesn't concatenate them, it makes 1 entry for each line, ie:
comments table:
vehicle_name vehicle_rpr_num comments_detail
--------------------------------------------------------------------
150 1 replaced hose
750 1 replaced belt
750 2 replaced fuel and also saw that the
750 2 timing belt needs to be replaced
750 2 as well
I was trying to use something like:
select
substring((select ' '+comments_detail AS 'data()'
from comments
for xml path('')), 3, 80) as 'comments_detail'
from
comments
I tried to add the join and other tables inside the substring but then the comments_details become all jacked up, like it then combines 20 comments together instead of 1 at a time.
I'd rather start from scratch and see if I can get it working another way.
My issue comes in when I try to link the 3 tables above.
I do not know how to put in the other fields that I need from the vehicle table, ie vehicle_make, vehicle_model
Any ideas? Am I writing my concatenate completely wrong? I am trying to put this into a stored procedure, would a view be better?
SELECT c1.vehicle_name,
c1.vehicle_rpr_num,
STUFF((SELECT ' ' + comments_detail
FROM comments c2
WHERE c2.vehicle_name = c1.vehicle_name
AND c2.vehicle_rpr_num = c1.vehicle_rpr_num
FOR XML PATH(''), TYPE).value('.', 'varchar(max)'),
1,1,'')
AS comments
FROM comments c1
GROUP BY c1.vehicle_name, c1.vehicle_rpr_num;
SQLFiddle with the sample comments data.
You were on the right track using FOR XML PATH to concatenate the comments. There are many different ways to concatenate, a good article on the pros/cons of each is here. I'd put this into a view definition to allow for easier joining with other tables.
CREATE TABLE tbl (vehicle_name INT,vehicle_rpr_num INT,comments_detail NVARCHAR(1000))
INSERT INTO tbl
VALUES
(150,1,'replaced hose'),
(750,1,'replaced belt'),
(750,2,'replaced fuel and also saw that the'),
(750,2,'timing belt needs to be replaced'),
(750,2,'as well')
SELECT DISTINCT t.vehicle_name, t.vehicle_rpr_num , STUFF(List.Comments, 1 ,2, '') AS Comments
FROM tbl t
CROSS APPLY (
SELECT ' ' + comments_detail [text()]
FROM tbl
WHERE vehicle_name = t.vehicle_name
AND vehicle_rpr_num = t.vehicle_rpr_num
FOR XML PATH('')
)List(Comments)
Result Set
vehicle_name vehicle_rpr_num Comments
150 1 eplaced hose
750 1 eplaced belt
750 2 eplaced fuel and also saw that the timing belt needs to be replaced as well
Related
Lets say I have a table in SQLServer named Tasks. It contains some tasks with people assigned to tasks. Workers are in one column. They are separated by semicolon.
ID Workers
1 John Newman; Troy Batchelor; Mike Smith
2 Chris Portman
3 Sara Oldman; Greg House
I need to separate workers from column like below
The result:
ID Worker
1 John Newman
1 Troy Batchelor
1 Mike Smith
2 Chris Portman
3 Sara Oldman
3 Greg House
I have no idea what to do.
Do I have to use some procedure or simple query is enough?
i solved your problem without using any function or stored procedure
SELECT ID,Workers FROM tblSemiColon
SELECT ID,
LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS Workers
FROM
(
SELECT ID,CAST('<XMLRoot><RowData>' + REPLACE(Workers,';','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x
FROM tblSemiColon
)t
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n)
this will work across all the version of sql server..i had tested it....
you can reduce the size of varchar length..
If you are on sql-server 2016 or higher you can use STRING_SPLIT function
SELECT id, value
FROM Tasks
CROSS APPLY STRING_SPLIT(Workers, ';')
I have the following query
SELECT CONCAT(RIGHT('00' + CONVERT(VARCHAR(2),Procedures.SeriesNum),2),'-',RIGHT('0000' + CONVERT(VARCHAR(4),Procedures.ProcNum),4)) AS 'Procedure',
Procedures.Description,
Procedures.CurrentRev,
Procedures.DayToDayLevel,
Procedures.MaxLevel,
Users.Username,
UsersProcedures.RevTrained,
UsersProcedures.LevelTrained
FROM Procedures
CROSS JOIN Users
LEFT JOIN UsersProcedures ON UsersProcedures.Username = Users.Username AND Procedures.SeriesNum = UsersProcedures.SeriesNum AND Procedures.ProcNum = UsersProcedures.ProcNum
Which returns the following results:
However I would like to use a Pivot so that the info for each user (Specifically The Level they were trained at is shown as a value in a column (With the column title being that of the username).
I would like to ignore the CurrentRev, and instead have 1 row for each Revision for each procedure (if no users have been trained to the current revision - whether users have been trained to any previous revisions on that procedure or not, that should be there as an empty row), like below:
I presume I need to use a pivot, although i've never attempted it before, plus the examples i've seen on the web seem to use a static list for the pivoted columns, whereas I want to use all records in the Users table.
I should point out, when it comes to displaying the results in a datagrid, it'll be coloured for clarity.
How would I go about attempting this?
UPDATE: I've got as far as this query
SELECT *
FROM (SELECT CONCAT(RIGHT('00' + CONVERT(VARCHAR(2),Procedures.SeriesNum),2),'-',RIGHT('0000' + CONVERT(VARCHAR(4),Procedures.ProcNum),4)) AS 'Procedure',
Procedures.Description,
Procedures.CurrentRev,
UsersProcedures.RevTrained,
Users.Username,
UsersProcedures.LevelTrained FROM Procedures CROSS JOIN Users LEFT JOIN UsersProcedures ON UsersProcedures.Username = Users.Username AND Procedures.SeriesNum = UsersProcedures.SeriesNum AND Procedures.ProcNum = UsersProcedures.ProcNum) AS Procs
PIVOT
(
MAX(LevelTrained)
FOR Procs.Username
IN(User1,User2,User3)
) AS PivotTable
Which gives me this:
However i'd still like to group the results further similar to my 2nd screenshot. i.e. if at least 1 user has already been trained on a particular revision, then don't show another empty row for that revision as well.
Also if RevTrained is NULL, then show CurrentRev in its place.
OK, here's the finished solution, using a dynamic query to get all the users, and an IF statement to solve the above problems:
If at least 1 user has already been trained on a particular revision, then don't show another empty row for that revision as well.
Also if RevTrained is NULL, then show CurrentRev in its place.
DECLARE #users AS VARCHAR(MAX),#query AS VARCHAR(MAX)
SELECT #users = STUFF((SELECT ',' + CONCAT('[',Username,']') FROM Users FOR XML PATH(''),TYPE).value('.', 'VARCHAR(MAX)'),1,1,'')
SET #query = 'SELECT *
FROM (SELECT CONCAT(RIGHT(''00'' + CONVERT(VARCHAR(2),Procedures.SeriesNum),2),''-'',RIGHT(''0000'' + CONVERT(VARCHAR(4),Procedures.ProcNum),4)) AS ''Procedure Number'',
CASE WHEN UsersProcedures.RevTrained IS NULL THEN Procedures.CurrentRev ELSE UsersProcedures.RevTrained END AS ''Revision'',
Procedures.Description,
Procedures.DayToDayLevel AS ''Maxmimum Training Level'',
Procedures.DayToDayLevel AS ''Training level for Day to Day Usage'',
Users.Username,
UsersProcedures.LevelTrained FROM Procedures CROSS JOIN Users LEFT JOIN UsersProcedures ON UsersProcedures.Username = Users.Username AND Procedures.SeriesNum = UsersProcedures.SeriesNum AND Procedures.ProcNum = UsersProcedures.ProcNum) AS Procs
PIVOT(
MAX(LevelTrained)
FOR Procs.Username
IN('+#users+')) AS PivotTable'
execute(#query);
Sorry, this might be a bit out of scope for the community here, but I wanted to get a second opinion.
I have a table with the following structure
Table_1
TYPE ITEM DATE QTYA QTYB QTYC
X AAA 17/08/2015 100 200 300
X AAA 18/08/2015 100 170 240
Y BBB 17/08/2015 100 240 100
I need to use this table as a source for a merge, but the target table is formatted completely differently
Table_2
ITEM QTYA_1 QTYA_2......QTYA_31 QTYB_1 QTYB_2 QTYB_3......QTYB_31 QTYC_1 QTYC_2....QTYC_31
(the numbers suffixed at basically the day of the month)
I can convert Table 1 to the format of Table 2 using a mix of UNION ALL and PIVOT, but the performance isn't that good - particularly since I have to save the information in a temp table first before merging it in (Each 'type' in Table_1 has a different start date and I cannot overwrite previous values in Table_2 starting from a different date - basically I have to merge the table 3 or 4 times with a different item type and different start date)
Here's what I got so far
SELECT TOP 0 * INTO #TEMP_PIVOT_TABLE
INSERT INTO #TEMP_PIVOT_TABLE SELECT * FROM
(SELECT ITEM, 'QTYA_' + CONVERT(DATETIMEFROMPARTS(day,DATE) AS VARCHAR) as 'Quantity Type', QTY_A FROM TABLE_1
UNION_ALL
SELECT ITEM, 'QTYB_' + CONVERT(DATETIMEFROMPARTS(day,DATE) AS VARCHAR) as 'Quantity Type', QTY_B FROM TABLE_1
UNION_ALL
SELECT ITEM, 'QTYC_' + CONVERT(DATETIMEFROMPARTS(day,DATE) AS VARCHAR) as 'Quantity Type', QTY_C FROM TABLE_1
) A
PIVOT
(SUM(QUANTITY) FOR QUANTITY_TYPE IN ([QTYA_1], [QTYA_2],.....[QTYA_31],[QTYB_1].....[QTYC_31],[QTYC_1].....[QTYC_31])) AS B
----For each different date per item_type, construct a string only selecting those days in the month.
Then merge the results from #TEMP_PIVOT_TABLE into TABLE_2 for each Item TYPE` with dynamic SQL
1) Is there any better way to do the PIVOT command? The performance of the UNION ALL commands isn't encouraging - particularly since the table I'm reading from has large amounts of data. I'm also simplifying here for the sake of brevity - the actual table needs to map 5 columns or so, each with 31 days
2) Is there a better way to do the MERGE? I dislike using a loop + Dynamic SQL to basically read the same dataset repeatedly just so to merge on different columns, but I can't see a different way. That and building the MERGE command dynamically with so many columns will make it troublesome for future maintenance.
Does anyone have an idea how I can go about this more efficiently?
You can replace the union all internal query with the following query. This query needs only one table hit instead of hitting table for each column.
To unpivot the data use cross apply with table valued constructor
select ITEM,[Quantity Type],QTY
from yourtable
cross apply
(
values
('QTYA_' + CONVERT(DATEPART(day,DATE) AS VARCHAR),QTY_A),
('QTYB_' + CONVERT(DATEPART(day,DATE) AS VARCHAR),QTY_B),
('QTYC_' + CONVERT(DATEPART(day,DATE) AS VARCHAR),QTY_C),
)
CS ([Quantity Type],QTY)
We are currently upgrading a current data import process we have written in C#.
As part of the upgrade process, we need to check the results of the import process from the rewrite against the results of the old system.
One of the changes we made was breaking comma-delimited lists into rows in another table. This will enable us to filter results using a simple join.
This is the old schema:
FormNumber MainCategories
1 blue,green,red
2 yellow,red,blue
3 white
Which we normalized to:
FormNumber AttributeId Value
1 1 blue
1 1 green
1 1 red
2 1 yellow
2 1 red
2 1 blue
3 1 white
Now, our next step is to confirm that the results from the two processes are the same. One of these checks is to compare the MainCategories field of the old process with the results from the normalized tables.
This leads us, finally, to the question: How do I create a comma-delimited list of the new schema to compare to the value of the old.
We have tried the XMLPath solution proposed by #Ritesh here: Concatenate many rows into a single text string?
Here is the adapted sql statement:
Select distinct ST2.FormNumber,
(Select ST1.Value + ',' AS [text()]
From cache.ArtifactAttribute ST1
Where ST1.FormNumber= ST2.FormNumber
ORDER BY ST1.FormNumber
For XML PATH ('')) [Values]
From cache.ArtifactAttribute ST2
The problem is the results are not correct. Even though FormNumber 1 only has three entries in the table, the Values column (the dynamically built delimited string) shows incorrect results. Obviously we are not implementing the sql code correctly.
What are we doing wrong?
Here is a way for you to try:
SELECT DISTINCT A.FormNumber, MainCategories
FROM YourTable A
CROSS APPLY (SELECT STUFF((SELECT ',' + Value
FROM YourTable
WHERE FormNumber = A.FormNumber FOR XML PATH('')),1,1,'') MainCategories) B
Though there is the problem where you can't really be sure that the order of the items concatenated is the same as the one you have, since there isn't a column that explictly gives that order. Here is a working SQL Fiddle with this example.
This seems to work fine for me:
DECLARE #s TABLE(FormNumber int, AttributeId int, Value varchar(32));
INSERT #s VALUES
(1,1,'blue'),
(1,1,'green'),
(1,1,'red'),
(2,1,'yellow'),
(2,1,'red'),
(2,1,'blue'),
(3,1,'white');
SELECT ST2.FormNumber, [Values] = STUFF(
(SELECT ',' + ST1.Value AS [text()]
FROM #s ST1
WHERE ST1.FormNumber = ST2.FormNumber
ORDER BY ST1.FormNumber
FOR XML PATH (''),
TYPE).value(N'./text()[1]', N'varchar(max)'), 1, 1, '')
FROM #s ST2 GROUP BY ST2.FormNumber;
Results:
FormNumber
Values
1
blue,green,red
2
yellow,red,blue
3
white
Example db<>fiddle
I'm currently working on an application where we have a SQL-Server database and I need to get a full text search working that allows us to search people's names.
Currently the user can enter a into a name field that searches 3 different varchar cols. First, Last, Middle names
So say I have 3 rows with the following info.
1 - Phillip - J - Fry
2 - Amy - NULL - Wong
3 - Leo - NULL - Wong
If the user enters a name such as 'Fry' it will return row 1. However if they enter Phillip Fry, or Fr, or Phil they get nothing.. and I don't understand why its doing this. If they search for Wong they get rows 2 and 3 if they search for Amy Wong they again get nothing.
Currently the query is using CONTAINSTABLE but I have switched that with FREETEXTTABLE, CONTAINS, and FREETEXT without any noticeable differences in the results. The table methods are be preferred because they return the same results but with ranking.
Here is the query.
....
#Name nvarchar(100),
....
--""s added to prevent crash if searching on more then one word.
DECLARE #SearchString varchar(100)
SET #SearchString = '"'+#Name+'"'
SELECT Per.Lastname, Per.Firstname, Per.MiddleName
FROM Person as Per
INNER JOIN CONTAINSTABLE(Person, (LastName, Firstname, MiddleName), #SearchString)
AS KEYTBL
ON Per.Person_ID = KEYTBL.[KEY]
WHERE KEY_TBL.RANK > 2
ORDER BY KEYTBL.RANK DESC;
....
Any Ideas...? Why this full text search is not working correctly ?
If you're just searching people's names, it might be in your best interest to not even use the full text index. Full text index makes sense when you have large text fields, but if you're mostly dealing with one word per field, I'm not sure how much extra you would get out of full text indexes. Waiting for the full text index to reindex itself before you can search for new records can be one of the many problems.
You could just make a query such as the following. Split your searchstring on spaces, and create a list of the search terms.
Select FirstName,MiddleName,LastName
From person
WHERE
Firstname like #searchterm1 + '%'
or MiddleName like #searchterm1 + '%'
or LastName like #searchterm1 + '%'
or Firstname like #searchterm2 + '%'
etc....
FreeTextTable should work.
INNER JOIN FREETEXTTABLE(Person, (LastName, Firstname, MiddleName), #SearchString)
#SearchString should contain the values like 'Phillip Fry' (one long string containing all of the lookup strings separated by spaces).
If you would like to search for Fr or Phil, you should use asterisk: Phil* and Fr*
'Phil' is looking for exactly the word 'Phil'. 'Phil*' is looking for every word which is starting with 'Phil'
Thanks for the responses guys I finally was able to get it to work. With part of both Biri, and Kibbee's answers. I needed to add * to the string and break it up on spaces in order to work. So in the end I got
....
#Name nvarchar(100),
....
--""s added to prevent crash if searching on more then one word.
DECLARE #SearchString varchar(100)
--Added this line
SET #SearchString = REPLACE(#Name, ' ', '*" OR "*')
SET #SearchString = '"*'+#SearchString+'*"'
SELECT Per.Lastname, Per.Firstname, Per.MiddleName
FROM Person as Per
INNER JOIN CONTAINSTABLE(Person, (LastName, Firstname, MiddleName), #SearchString)
AS KEYTBL
ON Per.Person_ID = KEYTBL.[KEY]
WHERE KEY_TBL.RANK > 2
ORDER BY KEYTBL.RANK DESC;
....
There are more fields being searched upon I just simplified it for the question, sorry about that, I didn't think it would effect the answer. It actually searches a column that has a csv of nicknames and a notes column as well.
Thanks for the help.
Another approach could be to abstract the searching away from the individual fields.
In other words create a view on your data which turns all the split fields like firstname lastname into concatenated fields i.e. full_name
Then search on the view. This would likely make the search query simpler.
You might want to check out Lucene.net as an alternative to Full Text.