SQL update row with info from another row (rookie) - sql

I'm new to SQL so be gentle! I'm working for a charity in London so I'm having a stab at SQL and I've gotten stuck with something you can probably all do with your eyes shut.
I've got a table with rows that I want to update:
It's a table showing up potential duplicate records in our database. There's a stored procedure that creates a criterion field from data on a customer account and if criterion fields from 2 records match, they are flagged as potential duplicates.
We have some known duplicates that we had to create a while ago that have Placeholder as the last name.
The criterion field matches other 'real' records with real last names that we want to keep.
What I want to do is:
update the status of the Placeholder ones to Delete; and the 'real' ones to Keep (even I can totally blitz that!)
update the keep ID field of the Placeholder ones so it's the customer_no field of the row with the matching criterion field
Once that's done, another stored procedure will take it from there.
Help!

Hi Welcome to Stack Overflow.
If I have understood you correctly you need something like this:
declare #customers table(
customer_no int NOT NULL,
matching_criteria varchar(50) NOT NULL,
lname varchar(50) NOT NULL,
keepID int NULL,
[status] varchar(50) NULL);
INSERT INTO #customers VALUES(1, 'a', 'Smith', null, null);
INSERT INTO #customers VALUES(2, 'a', 'Placeholder', null, null);
INSERT INTO #customers VALUES(3, 'b', 'Jones', null, null);
INSERT INTO #customers VALUES(4, 'b', 'Placeholder', null, null);
INSERT INTO #customers VALUES(5, 'c', 'Brown', null, null);
INSERT INTO #customers VALUES(6, 'c', 'Placeholder', null, null);
INSERT INTO #customers VALUES(7, 'd', 'Williams', null, null);
INSERT INTO #customers VALUES(8, 'd', 'Placeholder', null, null);
INSERT INTO #customers VALUES(9, 'e', 'Taylor', null, null);
INSERT INTO #customers VALUES(10, 'e', 'Placeholder', null, null);
SELECT * FROM #customers;
UPDATE #customers set [status] = case lname WHEN 'Placeholder' THEN 'DELETE' ELSE 'Keep' END;
SELECT * FROM #customers;
UPDATE k
SET keepID = d.customer_no
FROM
#customers k
INNER JOIN #customers d
on k.matching_criteria = d.matching_criteria
WHERE d.[status] = 'Delete' AND k.[status] = 'Keep';
SELECT * FROM #customers;
If you are using SQL Server you can paste this in a Query window and observe the results.
Please for future reference tell us which DB you are using, as the answers do vary from db to db. The above is specifically SQL Server. Whilst most dbs will behave similarly, some do not. The above updates on a join. Oracle, for example, does not allow this, so the syntax when using Oracle is different - Oracle can of course achieve the same thing, it just uses different SQL to do so.

Related

Most efficient way to update table column based on sum

I am looking for the most efficient / minimal code way to update a table column based on the sum of another value in the same table. A method which works and the temp table are shown below.
if object_id('tempdb..#t1') is not null drop table #t1
CREATE TABLE #t1 (id nvarchar(max), astate varchar(16), code varchar(16), price decimal(16,2), total_id_price_bystate decimal(16,2), total_id_price decimal(16,2))
INSERT into #t1 VALUES
(100, 'CA', '0123', 123.01, null, null),
(100, 'CA', '0124', 0.00, null, null),
(100, 'PA', '0256', 12.10, null, null),
(200, 'MA', '0452', 145.00, null, null),
(300, 'MA', '0578', 134.23, null, null),
(400, 'CA', '1111', 94.12, null, null),
(600, 'CA', '0000', 86.34, null, null),
(500, 'CO', '1111', 0.00, null, null);
update t1
set total_id_price_bystate = sum_price_bystate
from #t1 t1
inner join (
select t2_in.Id,
t2_in.astate,
sum(t2_in.price) as sum_price_bystate
from #t1 t2_in
group by t2_in.id, t2_in.astate
) t2
on t1.id = t2.id
and t1.astate = t2.astate
update t1
set total_id_price = sum_price
from #t1 t1
inner join (
select t3_in.Id,
sum(t3_in.price) as sum_price
from #t1 t3_in
group by t3_in.id
) t3
on t1.id = t3.id
select * from #t1
The main thing I don't like about my method is that it requires an inner join with a subquery that requires the same table itself. So I am looking for a way that might be able to avoid this, although I don't think this method I have is overly complicated. Maybe there isn't any method too much more efficient.
To add, I am wondering what the best way would be to combine the two updates together, since they are very similar, but only differ by the group by clause.
As pointed out in the comments, this is not a good way to store data as it violates the basic principles of normalisation -
you are storing data that you can compute
you are storing the same data multiple times, ie, duplicates.
you need to re-calculate the totals whenever any individual values changes
it's possible to update a single row and create a data contradiction
it's also not a bad thing to pre-calculate aggregations, especially in a data warehouse scenario, but you would still only store the value once per unique key.
Normalisation prevents these issues.
Saying that, you can utilise analytic window functions to compute your values in a single pass over the table:
select *,
Sum(price) over(partition by id, astate) total_id_price_bystate,
Sum(price) over(partition by id) total_id_price
from #t1;
If you really want the data in this format you could create a view and query it:
create view Totals as
select id, astate, code, price, total_id_price_bystate, total_id_price,
Sum(price) over(partition by id, astate) total_bystate,
Sum(price) over(partition by id) total
from t1;
select *
from Totals where id = 100;
And to answer your specific question, a view (or a CTE) that touches a single base table can be updated so you can accomplish what you are doing like so:
drop view Totals;
create view Totals as
select id, astate, code, price, total_id_price_bystate, total_id_price,
Sum(price) over(partition by id, astate) total_bystate,
Sum(price) over(partition by id) total
from t1;
update totals set
total_id_price_bystate = total_bystate,
total_id_price = total;
You can use PARTITION BY to get the two different aggregated value,
if object_id('tempdb..#t1') is not null drop table #t1
CREATE TABLE #t1 (id nvarchar(max), astate varchar(16), code varchar(16), price decimal(16,2), total_id_price_bystate decimal(16,2), total_id_price decimal(16,2))
INSERT into #t1 VALUES
(100, 'CA', '0123', 123.01, null, null),
(100, 'CA', '0124', 0.00, null, null),
(100, 'PA', '0256', 12.10, null, null),
(200, 'MA', '0452', 145.00, null, null),
(300, 'MA', '0578', 134.23, null, null),
(400, 'CA', '1111', 94.12, null, null),
(600, 'CA', '0000', 86.34, null, null),
(500, 'CO', '1111', 0.00, null, null);
update t1
set total_id_price_bystate = sum_price_bystate,total_id_price=sum_price
from #t1 t1
inner join (
select t2_in.Id,
t2_in.astate,
sum(t2_in.price) over(partition by t2_in.id, t2_in.astate) as sum_price_bystate,
sum(t2_in.price) over(partition by t2_in.id) as sum_price
from #t1 t2_in
) t2
on t1.id = t2.id
and t1.astate = t2.astate
select * from #t1

SQL query available rooms for booking

I have been attempting to create a query for checking available rooms between two dates but have been thus far unsuccessful. Please find the sql code for creating the tables below. I really really need your help. this is done because the system wants me to add more details
CREATE TABLE rooms (
id NUMERIC(3) NOT NULL,
type VARCHAR2(11) NOT NULL,
CONSTRAINT pk_rooms PRIMARY KEY (id),
CONSTRAINT fk_type
);
INSERT INTO rooms VALUES (101, 'Excellent');
INSERT INTO rooms VALUES (102, 'Excellent');
INSERT INTO rooms VALUES (103, 'Excellent');
INSERT INTO rooms VALUES (104, 'Excellent');
INSERT INTO rooms VALUES (105, 'Excellent');
INSERT INTO rooms VALUES (106, 'Excellent');
INSERT INTO rooms VALUES (107, 'Excellent');
INSERT INTO rooms VALUES (108, 'Excellent');
INSERT INTO rooms VALUES (109, 'Excellent');
INSERT INTO rooms VALUES (110, 'Excellent');
INSERT INTO rooms VALUES (111, 'Excellent');
INSERT INTO rooms VALUES (112, 'Excellent');
CREATE TABLE bookings_roombookings (
book_id VARCHAR(20) NOT NULL,
room_id NUMBER NOT NULL,
fromdate DATE NOT NULL,
todate DATE NOT NULL,
guest1 VARCHAR(40) NOT NULL,
guest2 VARCHAR(40),
guest3 VARCHAR(40),
CONSTRAINT cpk PRIMARY KEY (book_id, room_id),
CONSTRAINT fk_rid FOREIGN KEY (room_id) REFERENCES rooms(id));
INSERT INTO bookings_roombookings VALUES (
'DTR5000000', 320,
'01-JUN-2020', '01-SEP-2020',
'D.Trump', 'H.Clinton', 'S.Daniels');
INSERT INTO bookings_roombookings VALUES (
'DRI0000002', 102,
'11-DEC-19', '01-SEP-20',
'D.Ridley', NULL, NULL);
my query is as follows:
select r.id from rooms r
where r.id not in
( select bkr.room_id from bookings_roombookings bkr
where (fromdate > '15-JUN-20') and (todate < '18-JUN-20') );
but this will return both rows of the bkr table. I would appreciate any help.
You can start from the room table, and use a not exists condition with a correlated subquery to eliminate rooms that have a reservation period which overlaps the target date range:
select r.*
from rooms r
where not exists (
select 1
from bookings_roombookings bkr
where
bkr.room_id = r.id
and bkr.fromdate <= date'2020-06-15'
and bkr.todate >= date'2020-06-18'
)
Note: do not rely on implicit conversions from strings to date (as in fromdate > '15-JUN-20'), that depends on the nls settings of your database and session; instead, you can use date litterals, that are part of the ANSI SQL standard and which Oracle supports, like date'yyyy-mm-dd'.

SQL Server ranking weirdness using FREETEXTTABLE across multiple columns

I have been struggling to get my head around how SQL Server full text search ranks my results.
Consider the following FREETEXTTABLE search:
DECLARE #SearchTerm varchar(55) = 'Peter Alex'
SELECT ftt.[RANK], v.*
FROM FREETEXTTABLE (vMembersFTS, (Surname, FirstName, MiddleName, MemberRef, Passport), #SearchTerm) ftt
INNER JOIN vMembersFTS v ON v.ID = ftt.[KEY]
ORDER BY ftt.[RANK] DESC;
This returns the following results and rankings:
RANK ID MemberRef Passport FirstName MiddleName Surname Salutation
----- ---- ---------- ----------- ----------- ------------ ---------- ------------
18 2 AB-002 Pete Peters
18 9 AB-006 George Alex Mr Alex
18 13 AB-009 Peter David Alex Mr Alex
14 3 AB-003 Peter Alex Jones
As you may be able to tell from the results posted above, the last row, although having, what I consider, a good match on both 'Peter' and 'Alex', appears with a rank of only 14 where the result in the first row has only a single match on 'Peter' (admittedly the surname is 'Peters').
This is a contrived example, but goes some way to illustrate my frustrations and lack of knowledge.
I have spent quite a bit of time researching, but I am feeling a bit out of my depth now. I'm sure that I'm doing something stupid such as searching across multiple columns.
I welcome your help and support. Thanks in advance.
Thanks,
Kaine
(BTW I am using SQL Server 2012)
Here is the SQL you can use to repeat the test yourself:
-- Create the Contacts table.
CREATE TABLE dbo.Contacts
(
ID int NOT NULL PRIMARY KEY,
FirstName varchar(55) NULL,
MiddleName varchar(55) NULL,
Surname varchar(55) NOT NULL,
Salutation varchar(55) NULL,
Passport varchar(55) NULL
);
GO
-- Create the Members table.
CREATE TABLE dbo.Members
(
ContactsID int NOT NULL PRIMARY KEY,
MemberRef varchar(55) NOT NULL
);
GO
-- Create the FTS view.
CREATE VIEW dbo.vMembersFTS WITH SCHEMABINDING AS
SELECT c.ID,
m.MemberRef,
ISNULL(c.Passport, '') AS Passport,
ISNULL(c.FirstName, '') AS FirstName,
ISNULL(c.MiddleName, '') AS MiddleName,
c.Surname,
ISNULL(c.Salutation, '') AS Salutation
FROM dbo.Contacts c
INNER JOIN dbo.Members AS m ON m.ContactsID = c.ID
GO
-- Create the view index for FTS.
CREATE UNIQUE CLUSTERED INDEX IX_vMembersFTS_ID ON dbo.vMembersFTS (ID);
GO
-- Create the FTS catalogue and stop-list.
CREATE FULLTEXT CATALOG ContactsFTSCatalog WITH ACCENT_SENSITIVITY = OFF;
CREATE FULLTEXT STOPLIST ContactsSL FROM SYSTEM STOPLIST;
GO
-- Create the member full-text index.
CREATE FULLTEXT INDEX ON dbo.vMembersFTS
(Surname, Firstname, MiddleName, Salutation, MemberRef, Passport)
KEY INDEX IX_vMembersFTS_ID
ON ContactsFTSCatalog
WITH STOPLIST = ContactsSL;
GO
-- Insert some data.
INSERT INTO Contacts VALUES (1, 'John', NULL, 'Smith', NULL, NULL);
INSERT INTO Contacts VALUES (2, 'Pete', NULL, 'Peters', NULL, NULL);
INSERT INTO Contacts VALUES (3, 'Peter', 'Alex', 'Jones', NULL, NULL);
INSERT INTO Contacts VALUES (4, 'Philip', NULL, 'Smith', NULL, NULL);
INSERT INTO Contacts VALUES (5, 'Harry', NULL, 'Dukes', NULL, NULL);
INSERT INTO Contacts VALUES (6, 'Joe', NULL, 'Jones', NULL, NULL);
INSERT INTO Contacts VALUES (7, 'Alex', NULL, 'Phillips', 'Mr Phillips', NULL);
INSERT INTO Contacts VALUES (8, 'Alexander', NULL, 'Paul', 'Alex', NULL);
INSERT INTO Contacts VALUES (9, 'George', NULL, 'Alex', 'Mr Alex', NULL);
INSERT INTO Contacts VALUES (10, 'James', NULL, 'Castle', NULL, NULL);
INSERT INTO Contacts VALUES (11, 'John', NULL, 'Alexander', NULL, NULL);
INSERT INTO Contacts VALUES (12, 'Robert', NULL, 'James', 'Mr James', NULL);
INSERT INTO Contacts VALUES (13, 'Peter', 'David', 'Alex', 'Mr Alex', NULL);
INSERT INTO Members VALUES (1, 'AB-001');
INSERT INTO Members VALUES (2, 'AB-002');
INSERT INTO Members VALUES (3, 'AB-003');
INSERT INTO Members VALUES (5, 'AB-004');
INSERT INTO Members VALUES (8, 'AB-005');
INSERT INTO Members VALUES (9, 'AB-006');
INSERT INTO Members VALUES (11, 'AB-007');
INSERT INTO Members VALUES (12, 'AB-008');
INSERT INTO Members VALUES (13, 'AB-009');
-- Run the FTS query.
DECLARE #SearchTerm varchar(55) = 'Peter Alex'
SELECT ftt.[RANK], v.*
FROM FREETEXTTABLE (vMembersFTS, (Surname, FirstName, MiddleName, MemberRef, Passport), #SearchTerm) ftt
INNER JOIN vMembersFTS v ON v.ID = ftt.[KEY]
ORDER BY ftt.[RANK] DESC;
The rank is assigning based on the order in your query:
DECLARE #SearchTerm varchar(55) = 'Peter Alex'
SELECT ftt.[RANK], v.*
FROM FREETEXTTABLE (vMembersFTS, (Surname, FirstName, MiddleName, MemberRef, Passport), #SearchTerm) ftt
INNER JOIN vMembersFTS v ON v.ID = ftt.[KEY]
ORDER BY ftt.[RANK] DESC;
So in your case, a match on SurName trumps FirstName, and both trump MiddleName.
Your top 3 results have a rank of 18 as all three match on Surname. The last record has a rank of 14 for matching on FirstName and MiddleName but not SurName.
You can find details on the rank calculations here: https://technet.microsoft.com/en-us/library/ms142524(v=sql.105).aspx
If you want to allocate equal weight to these you can, but you'd have to use CONTAINSTABLE and not FREETEXTTABLE.
Info can be found here: https://technet.microsoft.com/en-us/library/ms189760(v=sql.105).aspx

Incorrect recursion query?

Perhaps I have this wrong but I thought this query would use recursion however I can't seem to write it correctly. Here is the sample data:
DECLARE #Friends TABLE
(
[person_id] [int] NOT NULL,
[first_name] [varchar](50) NOT NULL,
[favorite_color] [varchar](50) NOT NULL,
[best_friend] [varchar](50) NOT NULL
)
INSERT #Friends VALUES (1, 'John', 'blue', 'Mark')
INSERT #Friends VALUES (2, 'Mark', 'green', 'Sally')
INSERT #Friends VALUES (3, 'David', 'purple', 'John')
INSERT #Friends VALUES (4, 'Sally', 'red', 'Rose')
INSERT #Friends VALUES (5, 'Stephanie', 'blue', 'Rose')
INSERT #Friends VALUES (6, 'Rose', 'yellow', 'David')
Now I need to list each person's name in first column then their BEST FRIENDS favorite color in the second.
My thought was to use a cte and the initialization query would get the list of names and the recursion would get their best friends color.
However now I am unsure how to write the recursion part to find the best friends color?
Not looking for anyone to do my homework just trying to get headed in the right direction.
TIA
You don't need recursion, you don't need sub-query. All you need is simple join:
SELECT f.*, bf.favorite_color AS BF_favorite_color
FROM #Friends f
LEFT JOIN #Friends bf ON f.best_friend = bf.first_name
SQLFiddle DEMO

SQL Insert Multiple Rows

I want to insert multiple rows in a single table. How can I do this using single insert statement?
Wrap each row of values to be inserted in brackets/parenthesis (value1, value2, value3) and separate the brackets/parenthesis by comma for as many as you wish to insert into the table.
INSERT INTO example
VALUES
(100, 'Name 1', 'Value 1', 'Other 1'),
(101, 'Name 2', 'Value 2', 'Other 2'),
(102, 'Name 3', 'Value 3', 'Other 3'),
(103, 'Name 4', 'Value 4', 'Other 4');
You can use SQL Bulk Insert Statement
BULK INSERT TableName
FROM 'filePath'
WITH
(
FIELDTERMINATOR = '','',
ROWTERMINATOR = ''\n'',
ROWS_PER_BATCH = 10000,
FIRSTROW = 2,
TABLOCK
)
for more reference check
https://www.google.co.in/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=sql%20bulk%20insert
You Can Also Bulk Insert Your data from Code as well
for that Please check below Link:
http://www.codeproject.com/Articles/439843/Handling-BULK-Data-insert-from-CSV-to-SQL-Server
1--> {Simple Insertion when table column sequence is known}
Insert into Table1
values(1,2,...)
2--> {Simple insertion mention column}
Insert into Table1(col2,col4)
values(1,2)
3--> {bulk insertion when num of selected collumns of a table(#table2) are equal to Insertion table(Table1) }
Insert into Table1 {Column sequence}
Select * -- column sequence should be same.
from #table2
4--> {bulk insertion when you want to insert only into desired column of a table(table1)}
Insert into Table1 (Column1,Column2 ....Desired Column from Table1)
Select Column1,Column2..desired column from #table2
You can use UNION All clause to perform multiple insert in a table.
ex:
INSERT INTO dbo.MyTable (ID, Name)
SELECT 123, 'Timmy'
UNION ALL
SELECT 124, 'Jonny'
UNION ALL
SELECT 125, 'Sally'
Check here
For MSSQL, there are two ways:(Consider you have a 'users' table,below both examples are using this table for example)
1) In case, you need to insert different values in users table. Then you can write statement like:
INSERT INTO USERS VALUES
(2, 'Michael', 'Blythe'),
(3, 'Linda', 'Mitchell'),
(4, 'Jillian', 'Carson'),
(5, 'Garrett', 'Vargas');
2) Another case, if you need to insert same value for all rows(for example, 10 rows you need to insert here). Then you can use below sample statement:
INSERT INTO USERS VALUES
(2, 'Michael', 'Blythe')
GO 10
Hope this helps.
You can use the UNION ALL function
http://blog.sqlauthority.com/2007/06/08/sql-server-insert-multiple-records-using-one-insert-statement-use-of-union-all/
We will import the CSV file into the destination table in the simplest form. I placed my sample CSV file on the C: drive and now we will create a table which we will import data from the CSV file.
DROP TABLE IF EXISTS Sales
CREATE TABLE [dbo].[Sales](
[Region] [varchar](50) ,
[Country] [varchar](50) ,
[ItemType] [varchar](50) NULL,
[SalesChannel] [varchar](50) NULL,
[OrderPriority] [varchar](50) NULL,
[OrderDate] datetime,
[OrderID] bigint NULL,
[ShipDate] datetime,
[UnitsSold] float,
[UnitPrice] float,
[UnitCost] float,
[TotalRevenue] float,
[TotalCost] float,
[TotalProfit] float
)
The following BULK INSERT statement imports the CSV file to the Sales table.
BULK INSERT Sales
FROM 'C:\1500000 Sales Records.csv'
WITH (FIRSTROW = 2,
FIELDTERMINATOR = ',',
ROWTERMINATOR='\n' );