SQL - Match String and Update Row, using Excel list - sql

I have a DB Table [List1], 2 columns, Name, Number
SQLFiddle
I have an excel spreadsheet with 2 columns,names and numbers.
I want to match the Names in Excel to the Names column in SQL and If a match is found insert the relevant number in the second column.
Something tells me I will need to build an array / or csv and run some Tsql to achieve this.
I originally used the Task> Import data to build the DB Table.
Will importing the data again just overwrite the existing data?
What is the most efficient way to import the info, but update existing numbers? [EDIT, I have made some progress, read on]
I have managed to Create an conditional insert:
SET #PersonName = 'Andy
insert into People (Name, Number)
select
#PersonName
where not exists (
select * from People where Name = #PersonName
);
How do I pump the name list into the #PersonName variable and loop through the command in SQL?
Update:
I want to update the Datasets based on a dual column First/Last name.
Will this Work?
Update : Yes it worked, final code below.
update p
set p.number = s.numbers
from People p
join dbo.[spreadsheet] s on p.Firstname = s.Firstname AND p.lastname = s.lastname

If I understood you correctly and you want to match relatively small amount of data (up to 2k-5k rows) between excel and database table you may perform the next sequence of actions:
In SSMS execute: create table dbo.[spreadsheet] (firstname nvarchar(100), lastname nvarchar(100), numbers int);
In Excel spreadsheet copy to buffer data from firstname, lastname and numbers columns (without headers)
In SSMS Object Explorer: Tables->Right click->Refresh
Select dbo.spreadsheet table->Right click->Edit top 200 rows
In the designer select last row->Right click the on row header->Paste
And finally execute following update statement (see below)
update p
set p.number = s.numbers
from People p
join dbo.[spreadsheet] s on s.firstname = p.firstname and s.lastname = p.lastname

Related

Combine Multiple Rows Into A Single Column

I am working on a stored procedure for reporting purposes and I need to combine rows in a single column but I can't find a way to do this. I have four tables, Case, Debtor, DebtorAddress and CaseNote. Rather than try to do this in a single statement I decided to create a temp table with the columns needed, populate the ones that occupy a single row, then use an update statement to combine the multiple rows of the last column needed into a single row. My temp table has rows for CApKey (the ID of the Case table), OwnerName (from Debtor), Address (from DebtorAddress, and Note. For each Case there may be multiple Notes (stored in the CaseNote table). So I may have Case #1, with a CApKey value of 1, OwerName of John Jones, Address of 1234 Main St. There may be one Note the says 'Called and left message', another that says 'Sent letter', and another that says 'Left a second voicemail', etc. I'd like to combine the three notes into a single row with Note values of Called and left a message, Sent Letter, and Left a second voicemail. I can use space, period, or comma as a delimiter. I found a way to do the update in theory but I'm getting an error that the sub-query returned more than 1 value. Below is the "heart" of the procedure. I've been wracking my brain on this for two days now. Any assistance is greatly appreciated in advance. Here is the statement I'm trying:
CREATE TABLE #temp
(
CaseKey int,
OwnerName varchar(500),
Address varchar(500),
Note varchar(MAX)
)
DECLARE #val Varchar(MAX);
INSERT INTO #temp
(CaseKey, OwnerName, Address)
SELECT ca.CApKey, DEFirstName + ' ' + DELastName, da.DAAddress1
FROM [Case] ca INNER JOIN Debtor de ON ca.CApKey = de.CApKey INNER JOIN DebtorAddress da ON ca.CApKey = da.CApKey
WHERE ca.LFpKey = #LFpKey AND de.DEIsPrimary = 1
UPDATE #temp SET Note =
(SELECT COALESCE(#val + ', ' + CANNote, CANNote)
FROM CaseNote WHERE CApKey = 51)
--SELECT #val;)
SELECT * FROM #temp
Thanks!
If i understood you correctly, you need to combine all the notes.
Get your data, queried from the 4 tables into a table (ex. #t)
Then you can use XML PATH and Stuff to achieve your goal (later you can use that for whatever your purpose as inserting to a table or display in a report etc)
SELECT CaseKey, OwnerName, Address, (STUFF((
SELECT ', ' + Note
FROM #t tx
where tx.CaseKey = t.CaseKey and tx.OwnerName = t.OwnerName and tx.Address = t.Address
FOR XML PATH('')
), 1, 2, '')
) AS StringValue
From #t t
Group by CaseKey, OwnerName, Address
Here is the fiddle

Create Update Statement that uses Joins or Groups

As part of a larger store procedure I am trying to write a SQL command to update a particular field with a value depending on certain criteria. The issue I have is around linking Joins with Groups.
There are 3 tables involved the initial table holds the ID of the master record in the stored procedure and contains a unique value.
wrec_id
1
2
3
4
The second table may contain entries linked to the master table
Work_id and acts as a route to the Person Table.
If it does not contain a value or the value of pers_id is set to 0 then I need 'Not Assigned' as the Persons Name.
If more than one person has been allocated, then I need a concatenated list of all the people.
wrec_id, pers_id
1, 1
1, 2
2, 1
3, 3
The Person table contains the entries required in the stored procedure.
pers_id, Forename, Surname
1, For1, Sur1
2, For2, Sur2
3, For3, Sur3
The output expected for the above examples should look as follows :-
For1 Sur1 : For2 Sur2
For1 Sur1
For3 Sur3
Not Assigned
I have tried various combinations of Groups and Joins without success.
Any help most welcome.
In SQL Server, you can use the XML PATH expression to combine multiple rows. In this case, you can use something like the query here to show the people assigned to each work project. Then, depending on how you need to use the data, you could handle it either in SQL Server or the calling application. For example, if you're only going to be showing the final results as you have it in the calling application, you could use XSLT to display the XML the way you wanted (if you go this route, you could wrap the entire query in a FOR XML expresion, and display it however you wanted).
If you absolutely need to display the data as you have it in SQL Server, you can instead do something like this:
select
wrec_id
,case
when
( select
' : ' + Persons.Forename + ' ' + Persons.Surname
from WorkAssignments
inner join Persons on
Persons.pers_id = WorkAssignments.pers_id
where
WorkAssignments.wrec_id = WorkProjects.wrec_id
for xml path('')
) is null then 'Not Assigned'
else
( select
' : ' + Persons.Forename + ' ' + Persons.Surname
from WorkAssignments
inner join Persons on
Persons.pers_id = WorkAssignments.pers_id
where
WorkAssignments.wrec_id = WorkProjects.wrec_id
for xml path('')
)
end AssignedPersons
from WorkProjects
Although, as you can see, it starts to get a bit ugly. Again, if you can, you might want to leave the niceties of how to display work assignments to your calling application, rather than doing it directly in SQL Server.

SQL Server, updating item quantities of new items that are replacing old items

I have a CSV with two columns OldItem and NewItem; each column holds a list of integers. Note - the CSV will hold around 1,000 rows.
OldItem | NewItem
-----------------
1021669 | 1167467
1021680 | 1167468
1021712 | 1167466
1049043 | 1000062
We have old items in the system that are being replaced by the new items and we would like to capture the current quantity of the first OldItem and assign it to the first NewItem, quantity of second OldItem assigned to quantity of third OldItem, etc.
The other fun part of the issue is that the Item Numbers that are in the spreadsheet don't match up to the item numbers associated with the quantities, there's a translation table in the system called Alias.
Here are the tables and columns we're interacting with:
table Alias (essentially a translation table)
column Alias (the numbers in the spreadsheet)
column ItemID (the numbers in table "Items" that hold the quantities)
table Items (this holds all the items, new and old)
column ItemID
column Quantity
The only way I can think of doing this is doing a foreach on every OldItem like this, pseudo-code incoming:
foreach OldItem (Select Alias.ItemID WHERE Alias.Alias = OldItem)
then somehow, as I don't know how to return and use that result in SQL:
Select Item.Quantity where Item.ItemID = Alias.ItemID.
At this point I have the quantity that I want, now I have to reference back to the CSV, find the NewItem associated with the OldItem, and do this all over again with the NewItem and then update the NewItem Quantity to the one I found from the OldItem.
-dizzy-
Please help, I could solve this problem by wrapping SQL in PowerShell to handle the logical bits but it has severe performance consequences and I have to do this on MANY databases remotely with very bad network connections!
Given that you have connectivity issues, I suggest the following:
Create a working table in your database
Import your CSV into the working table
Run a script that copies aliases and quantities into the working table. Not required but helps with auditing
Run a script that validates the data
Run a script that copies required data into Items
It's important to note that this assumes that olditems are unique, and only ever map to one new item. There is a checks in the 'testing section' for that
Create a working table
Open SQL Server Management Studio and run this script in your database (choose it in the dropdown)
-- Create a schema to hold working tables that aren't required by the application
CREATE SCHEMA adm;
-- Now create a table in this schema
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'ItemTransfer'
AND type = 'U'
AND schema_id = SCHEMA_ID('adm'))
DROP TABLE adm.ItemTransfer;
CREATE TABLE adm.ItemTransfer (
OldItem INT NOT NULL,
NewItem INT NOT NULL,
OldAlias VARCHAR(50) NULL,
NewAlias VARCHAR(50) NULL,
OldQuantity NUMERIC(19,2) NULL
);
Import the CSV data
There are a number of ways to do this. Your constraint is your unreliable network, and how comfortable you are troubleshooting unfamiliar tools. Here is one method that can be rerun without causing duplicates:
Open your CSV in excel and paste this monstrosity into in column 3, row 2:
="INSERT INTO adm.ItemTransfer (OldItem, NewItem) SELECT " & A2 & "," & B2 & " WHERE NOT EXISTS (SELECT * FROM adm.ItemTransfer WHERE OldItem=" & A2 & " AND NewItem=" & B2 & ");"
This will generate an insert statement for that data. Drag it down to generate all insert statements. There will be a bunch of lines that look something like this:
INSERT INTO adm.ItemTransfer (OldItem, NewItem) SELECT 1,2 WHERE NOT EXISTS (SELECT * FROM adm.ItemTransfer WHERE OldItem=1 AND NewItem=2);
Copy/paste this string of inserts into SQL Server Management Studio and run it. It should insert all of the data into your working table.
I also suggest that you save this file to a .SQL file. This insert statement only inserts if the record isn't already there, so it can be rerun.
Note: There are many ways to import data into SQL Server. the next easiest way is to right click on the database / tasks / import flat file, but it's more complicated to stop duplicates / restarting import
Now you can run SELECT * FROM adm.ItemTransfer and you should see all of your records
Map Alias and Qty
This step can actually be done on the fly but lets just write them into the working table as it will allow us to audit afterwards
These two scripts copy the alias into the working table:
UPDATE adm.ItemTransfer
SET OldAlias = SRC.Alias
FROM
adm.ItemTransfer TGT
INNER JOIN
Alias SRC
ON TGT.OldItem = SRC.ItemID;
UPDATE adm.ItemTransfer
SET NewAlias = SRC.Alias
FROM
adm.ItemTransfer TGT
INNER JOIN
Alias SRC
ON TGT.NewItem = SRC.ItemID;
This one copies in the old item quantity
UPDATE adm.ItemTransfer
SET OldQuantity = SRC.Quantity
FROM
adm.ItemTransfer TGT
INNER JOIN
Items SRC
ON TGT.OldAlias = SRC.ItemID;
After these steps, again run the select statement to inspect.
Pre update check
Before you actually do the update you should check data consistency
Count of records in the staging table:
SELECT
COUNT(*) AS TableCount,
COUNT(DISTINCT OldAlias) UniqueOldAlias,
COUNT(DISTINCT NewAlias) UniqueNewAlias,
FROM adm.ItemTransfer
The numbers should all be the same and should match the CSV record count. If not you have a problem as you are missing records or you are not mapping one to one
This select shows you old items missing an alias:
SELECT * FROM adm.ItemTransfer WHERE OldAlias IS NULL
This select shows you new items missing an alias:
SELECT * FROM adm.ItemTransfer WHERE NewAlias IS NULL
This select shows you old items missing from the item table
SELECT *
FROM adm.ItemTransfer T
WHERE NOT EXISTS (
SELECT * FROM Items I WHERE I.ItemID = T.OldItem)
This select shows you new items missing from the item table
SELECT *
FROM adm.ItemTransfer T
WHERE NOT EXISTS (
SELECT * FROM Items I WHERE I.ItemID = T.NewItem)
Backup the table and do the update
First backup the table inside the database like this:
SELECT *
INTO adm.Items_<dateandtime>
FROM Items
This script makes a copy of the Items table before you update it. You can delete it later if you like
The actual update is pretty simple because we worked it all out in the working table beforehand:
UPDATE Items
SET Quantity = SRC.OldQuantity
FROM Items TGT
INNER JOIN
adm.ItemTransfer SRC
ON SRC.NewAlias = TGT.ItemID;
Summary
All of this can be bundled up into a script and automated if required. As is, you should save all working files to a SQL file, as well as the outputs from the SELECT test statements

sql concat within a SELECT statement

This is similiar to this one. How to concatenate all columns in a select with SQL Server
But not quite. This is MS SQL 2008. I am pulling patient demographics, and one of those is race, which is a multi-choice field (you could be asian and chinese for example). The race table 'PatientRace' is linked to the patient table by patientid. So the table structure is thus:
Patient table
PatientID
PatientName
PatientAddress
PatientRace table
PatientRaceID
PatientID (FK)
Description
I only want one row, and I want race to be concatenated. Is it possible to do this within a single SELECT statement or do I need to do a cursor? I am envisioning the cursor to be like this: Initial select for all the other demographics, insert into a temp table. Go through the temp table by patientID, then for each, grab out the race, concat, and add to the temp table.
The desired output is like this: 1 row per patient.
Name: "Joe Blow"
Race: "Asian, Chinese"
You need to use STUFF and FOR XML like this
SELECT p.PatientName,
(STUFF(SELECT ',' + r.Description
FROM PatientRace r
WHERE r.PatientID = p.PatientID
FOR XML('')), 1, 1, '')
FROM Patients p
Concatenating string values in SQL Server is not obvious. It requires using "xml" data processing and a subquery:
select p.*,
stuff((select ', ' + Description
from patientrace pr
where pr.patientid = p.patientid
for xml path ('')
), 1, 2, ''
) as races
from patients p;
As for me, you have write function like fn_GetRaceByID(int PatientID), that returns desired string. So use it in your select. Link in your question has good example, how to do this.

Return multiple values in one column within a main query

I am trying to find Relative information from a table and return those results (along with other unrelated results) in one row as part of a larger query.
I already tried using this example, modified for my data.
How to return multiple values in one column (T-SQL)?
But I cannot get it to work. It will not pull any data (I am sure it is is user[me] error).
If I query the table directly using a TempTable, I can get the results correctly.
DECLARE #res NVARCHAR(100)
SET #res = ''
CREATE TABLE #tempResult ( item nvarchar(100) )
INSERT INTO #tempResult
SELECT Relation AS item
FROM tblNextOfKin
WHERE ID ='xxx' AND Address ='yyy'
ORDER BY Relation
SELECT #res = #res + item + ', ' from #tempResult
SELECT substring(#res,1,len(#res)-1) as Result
DROP TABLE #tempResult
Note the WHERE line above, xxx and yyy would vary based on the input criteria for the function. but since you cannot use TempTables in a function... I am stuck.
The relevant fields in the table I am trying to query are as follows.
tblNextOfKin
ID - varchar(12)
Name - varchar(60)
Relation - varchar(30)
Address - varchar(100)
I hope this makes enough sense... I saw on another post an expression that fits.
My SQL-fu is not so good.
Once I get a working function, I will place it into the main query for the SSIS package I am working on which is pulling data from many other tables.
I can provide more details if needed, but the site said to keep it simple, and I tried to do so.
Thanks !!!
Follow-up (because when I added a comment to the reponse below, I could not edit formatting)
I need to be able to get results from different columns.
ID Name Relation Address
1, Mike, SON, 100 Main St.
1, Sara, DAU, 100 Main St.
2, Tim , SON, 123 South St.
Both the first two people live at the same address, so if I query for ID='1' and Address='100 Main St.' I need the results to look something like...
"DAU, SON"
Mysql has GROUP_CONCAT
SELECT GROUP_CONCAT(Relation ORDER BY Relation SEPARATOR ', ') AS item
FROM tblNextOfKin
WHERE ID ='xxx' AND Address ='yyy'
You can do it for the whole table with
SELECT ID, Address, GROUP_CONCAT(Relation ORDER BY Relation SEPARATOR ', ') AS item
FROM tblNextOfKin
GROUP BY ID, Address
(assuming ID is not unique)
note: this is usually bad practice as an intermediate step, this is acceptable only as final formatting for presentation (otherwise you will end up ungrouping it which will be pain)
I think you need something like this (SQL Server):
SELECT stuff((select ',' +Relation
FROM tblNextOfKin a
WHERE ID ='xxx' AND Address ='yyy'
ORDER BY Relation
FOR XML path('')),1,1,'') AS res;