Fill in Missing rows using T-SQL - sql-server

I am trying to fill in missing values. A simple example is table A has 10 rows with Id's of 1 through 10.
Table B has 5 rows of 1,3,5,7,9
I need to use table A to fill in the missing even numbers from Table B.
What is the best way to go about this?

CREATE TABLE #A (
Id INT
,Val VARCHAR(10)
)
CREATE TABLE #B (
Id INT
,Val VARCHAR(10)
)
INSERT INTO #A (
Id
,Val
)
VALUES (
1
,'1'
)
INSERT INTO #A (
Id
,Val
)
VALUES (
2
,'2'
)
INSERT INTO #A (
Id
,Val
)
VALUES (
3
,'3'
)
INSERT INTO #B (
Id
,Val
)
VALUES (
1
,'1'
)
INSERT INTO #B (
Id
,Val
)
SELECT Id
,Val
FROM #A a
WHERE NOT EXISTS (
SELECT NULL
FROM #B b
WHERE b.Id = a.Id
)
AND a.Id % 2 = 0
Select * from #B

Run a select query towards table A with a for loop that compares I’d for each loop. If I’d don’t match execute an insert. If you want to run it inside DB make an SP for it otherwise create a simple console app or such.

You can use INSERT INTO ... SELECT...
INSERT INTO A
SELECT B.ID FROM B
WHERE B.ID % 2 = 0
AND B.ID NOT IN (SELECT ID FROM A)
Just make sure to SELECT the same columns that you are inserting.

You can do using not exists like below :
insert into B
select ID from A
where ID % 2 = 0 and not exists(select 1 from B where ID = A.ID)
SQL HERE

Related

What is the optimal way to get only latest ID's from table in SQL

I'm trying to get only a single row per Appointment Number in a table storing a history of appointments. It works fine with a few rows but then gets slower? Is this the best way to do this kind of check and I'm just missing some indexes or is there a better way?
DECLARE #temptable TABLE
(
id INT PRIMARY KEY NOT NULL
, ApptNumber INT NOT NULL
, ApptDate DATE NOT NULL
, Notes VARCHAR(50) NULL
)
INSERT INTO #temptable VALUES (1,1,'01-DEC-2018','First Appointment')
INSERT INTO #temptable VALUES (2,1,'01-DEC-2018','')
INSERT INTO #temptable VALUES (3,1,'01-DEC-2018','Rescheduled')
INSERT INTO #temptable VALUES (4,2,'02-DEC-2018','Second Appointment')
INSERT INTO #temptable VALUES (5,2,'02-DEC-2018','Cancelled')
INSERT INTO #temptable VALUES (6,3,'03-DEC-2018','Third Appointment')
INSERT INTO #temptable VALUES (7,4,'04-DEC-2018','Fourth Appointment')
SELECT * FROM #temptable
SELECT MAX(id) FROM #temptable GROUP BY ApptNumber
SELECT tt.* FROM #temptable tt
INNER JOIN (SELECT MAX(id) [Id] FROM #temptable GROUP BY ApptNumber) appts ON appts.Id = tt.id
Solution 1:
select * from (
SELECT f1.*, row_number() over(partition by ApptNumber order by id desc ) rang FROM #temptable f1
) tmp where rang=1
Solution 2:
with tmp as (
select ApptNumber, max(ID) MaxID
from #temptable
group by ApptNumber
)
select f1.* from #temptable f1 inner join tmp f2 on f1.ID=f2.MaxID
Solution 3:
select distinct f3.* from #temptable f1
cross apply
(
select top 1 * from #temptable f2
where f1.ApptNumber=f2.ApptNumber
order by f2.ID desc
) f3
Window function
SELECT tt.*
FROM (
SELECT *, row_number() over (partition by ApptNumber order by id desc) as rn
) tt
where tt.rn = 1

Creating duplicates with a different ID for test in SQL

I have a table with 1000 unique records with one of the field as ID. For testing purpose, my requirement is that To update the last 200 records ID value to the first 200 records ID in the same table. Sequence isn't mandatory.
Appreciate help on this.
Typically I charge for doing other ppls homework, don't forget to cite your source ;)
declare #example as table (
exampleid int identity(1,1) not null
, color nvarchar(255) not null
);
insert into #example (color)
select 'black' union all
select 'green' union all
select 'purple' union all
select 'indigo' union all
select 'yellow' union all
select 'pink';
select *
from #example;
declare #max int = (select max(exampleId) from #example);
declare #min int = #max - 2
;with cte as (
select top 2 color
from #example
)
update #example
set color = a.color
from cte a
where exampleid <= #max and exampleid > #min;
select *
from #example
This script should solve the issue and will cover scenarios even if the id column is not sequential.I have included the comments to help you understand the joins and the flow of the script.
declare #test table
(
ID int not null,
Txt char(1)
)
declare #counter int = 1
/*******This variable is the top n or bottom n records in question it is 200 ,
for test purpose setting it to 20
************/
declare #delta int = 20
while(#counter <= 50)
begin
Insert into #test values(#counter * 5,CHAR(#counter+65))
set #counter = #counter + 1
end
/************Tag the records with a row id as we do not know if ID's are sequential or random ************/
Select ROW_NUMBER() over (order by ID) rownum,* into #tmp from #test
/************Logic to update the data ************/
/*Here we first do a self join on the tmp table with first 20 to last 20
then create another join to the test table based on the ID of the first 20
*/
update t
set t.ID = tid.lastID
--Select t.ID , tid.lastID
from #test t inner join
(
Select f20.rownum as first20 ,l20.rownum as last20,f20.ID as firstID, l20.ID lastID
from #tmp f20
inner join #tmp l20 on (f20.rownum + #delta) = l20.rownum
)tid on tid.firstID = t.ID and tid.first20 < = #delta
Select * from #test
drop table #tmp

SQL: If exists, limit user. If not exists show everything

I'm trying to found the best way to this requirements:
#fkStaffID INT = Current user.
If #fkStaffID got resource BLABLA only show rows of table X where is StaffID is here. If he DON'T have resource BLABLA, show everything.
SORRY I cannot paste full SQL, for employer's security policy. (I wish I show enough for help, not too much for security...)
What I do:
SELECT * FROM X
WHERE ((EXISTS
(SELECT 1 FROM STAFF WHERE pkStaff=#fkStaffID
AND STAFF.PkStaff IN (SELECT fkStaff FROM SECURITYSUBQUERY WHERE ResourceName='BLABLA')) AND X.fkStaff=#fkStaffID)
OR ((NOT EXISTS (SELECT 1 FROM STAFF WHERE pkStaff=#fkStaffID
AND STAFF.PkStaff IN (SELECT fkStaff FROM SECURITYSUBQUERY WHERE ResourceName='BLABLA')) )
PROBLEM: It's really slow. Can I do a more efficient way? Can I do another way? Thank you for your help!
I think you should be able to qrite the query thus:
SELECT * FROM x
WHERE #fkStaffID NOT IN (SELECT fkStaff FROM SecuritySubquery WHERE ResourceName= 'BLABLA')
OR #fkStaffID = fkStaff;
So either the #fkStaffID isn't 'BLABLA' or it matches the record's staff ID.
This NOT IN / OR still won't be very fast. Anyway, you should have the following indexes:
CREATE INDEX idx1 ON SecuritySubquery (ResourceName, fkStaff);
CREATE INDEX idx2 ON x (fkStaff);
I would try this:
if exists(select 1 from staff where pkstaff=#fkstaffid)
begin
select * from X where ResourceName = 'Blabla' and fkStaff = #fkStaffId
end
else
begin
select * from X where ResourceName = 'Blabla'
end
If we have a matching record, then we filter by that #fkStaffId, otherwise we select everything.
The below query will give you only the data for people in X who are in the STAFF table with a corresponding record in SECURITYSUBQUERY table ('BlaBla' records).
First, build test data.
IF OBJECT_ID(N'tempdb..#x') IS NOT NULL
DROP TABLE #x
CREATE TABLE #X ( fkStaff int, myStuff varchar(20) )
INSERT INTO #X ( fkStaff, myStuff )
VALUES
(1,'not me')
, (2,'not me')
, (3,'show me')
, (4,'not me')
, (5,'show me too')
IF OBJECT_ID(N'tempdb..#STAFF') IS NOT NULL
DROP TABLE #STAFF
CREATE TABLE #STAFF ( pkStaff int, name varchar(20) )
INSERT INTO #STAFF ( pkStaff, name )
VALUES
(1, 'Joe')
, (2, 'Jim')
, (3, 'Bill')
, (4, 'Ted')
, (5, 'Rufus')
IF OBJECT_ID(N'tempdb..#SECURITYSUBQUERY') IS NOT NULL
DROP TABLE #SECURITYSUBQUERY
CREATE TABLE #SECURITYSUBQUERY ( fkStaff int, ResourceName varchar(20) )
INSERT INTO #SECURITYSUBQUERY ( fkStaff, ResourceName )
VALUES
( 1, 'NotAuth' )
, ( 2, 'NotAuth' )
, ( 3, 'BlaBla' )
, ( 3, 'Extra Perm' )
, ( 4, 'NotAuth' )
, ( 5, 'BlaBla' )
Now for the query.
DECLARE #fkStaffID int ; /* Only 3 or 5 will return records. */
SELECT x.*
FROM #x x
LEFT OUTER JOIN (
SELECT s.pkStaff
FROM #STAFF s
INNER JOIN #SECURITYSUBQUERY ss ON s.pkStaff = ss.fkStaff
AND ss.ResourceName = 'BlaBla'
WHERE s.pkStaff = #fkStaffID
) t ON t.pkStaff = x.fkStaff
WHERE t.pkStaff IS NOT NULL
AND x.fkStaff = #fkStaffID
This will only give results if users Bill or Rufus are logged in (and passed as #fkStaffID).
I don't know how well this will scale, but the optimizer should work faster than EXISTS or NOT IN subqueries. Try it with your data.

SQL Server select (top) two rows into two temp variables

I have a query which results in two or more rows (just one column) and I want to catch the first row value into first temp variable and second row value into second temp variable without using multiple times the select top 1 and select top 1 order by desc
Something like this;
Select row1 value into #tempvariable1, row2 value into #tempvariable2 from blah blah
You need somehow to identify the row (I am using a row ID in the example below, ordering by value - you can order by id or something else):
DECLARE #DataSource TABLE
(
[value] VARCHAR(12)
);
INSERT INTO #DataSource
VALUES ('value 1')
,('value 2')
,('value 3');
DECLARE #tempVariable1 VARCHAR(12)
,#tempVariable2 VARCHAR(12);
WITH DataSource ([value], [rowID]) AS
(
SELECT [value]
,ROW_NUMBER() OVER (ORDER BY [value])
FROM #DataSource
)
SELECT #tempVariable1 = IIF([rowID] = 1, [value], #tempVariable1)
,#tempVariable2 = IIF([rowID] = 2, [value], #tempVariable2)
FROM DataSource;
SELECT #tempVariable1
,#tempVariable2;
You can use a CTE where you will get the X values you need and then select from it:
declare #data table(id int);
insert into #data(id) values(8), (6), (4), (3);
with vals(id, n) as (
Select top(2) id, ROW_NUMBER() over(order by id)
From #data
)
Select #A = (Select id From vals Where n = 1)
, #B = (Select id From vals Where n = 2)
You could also use PIVOT:
Select #A = [1], #B = [2]
From (
Select id, ROW_NUMBER() over(order by id)
From #data
) v(id, n)
PIVOT (
max(id) FOR n in ([1], [2])
) as piv
You have two options
Let's say we test case is build as below
create table dbo.Test
(
value varchar(100) not null
)
GO
insert into dbo.Test
values
('A'),('B'),('NO THIS ONE'),('NO THIS ONE'),('NO THIS ONE')
GO
Now let's say you fetch your data as below
select t.value
from dbo.Test t
where t.value != 'NO THIS ONE'
GO
The first and easier option is to save the data in a temp table
declare #results as Table (value varchar(100))
insert into #results
select t.value
from dbo.Test t
where t.value != 'NO THIS ONE'
you still use TOP 1 BUT not in the entire data, only in the results.
Use TOP 1 to find the first result and a second TOP 1 where value is different from the first.
declare #A varchar(100), #B varchar(100)
set #A = (select top 1 r.value from #results r)
set #B = (select top 1 r.value from #results r where r.value != #A)
select #A, #B
GO
This approach have the advantage of performance.
Of course that don't work great if both values are equal. You can fix it by using a top 1 and ordering in the inverse order.
There's a better alternative using rownumber.
It works because if you set a variable when returning multiple rows the varible sticks with the last one (in fact it's reseted for each row iteration).
The case statement makes sure the variable #A is seted only on the first row iteration.
declare #A varchar(100), #B varchar(100)
/* This way #B receives the last value and #A the first */
select #B = t.value,
#A = (case when ROW_NUMBER() OVER(order by t.Value) = 1
then t.Value else #A
end)
from dbo.Test t
where t.value != 'NO THIS ONE'
select #A, #B

SQL: How to limit the number of records the MERGE statement will insert

Some sample data:
DECLARE #TARGET TABLE ( ID INT, value INT ) ;
DECLARE #SOURCE TABLE ( ID INT, value INT )
INSERT INTO #TARGET VALUES ( 1, 213 )
INSERT INTO #TARGET VALUES ( 2, 3 )
INSERT INTO #TARGET VALUES ( 3, 310 )
INSERT INTO #TARGET VALUES ( 4, 43 )
INSERT INTO #SOURCE VALUES ( 1, 134 )
INSERT INTO #SOURCE VALUES ( 2, 34 )
INSERT INTO #SOURCE VALUES ( 13, 310 )
INSERT INTO #SOURCE VALUES ( 14, 43 )
INSERT INTO #SOURCE VALUES ( 15,32 )
INSERT INTO #SOURCE VALUES ( 16, 30 )
INSERT INTO #SOURCE VALUES ( 17, 60 )
INSERT INTO #SOURCE VALUES ( 18, 5 )
MERGE #TARGET t USING (SELECT * FROM #SOURCE) AS s ON (t.id = s.id)
WHEN NOT MATCHED THEN
INSERT VALUES (s.id,s.value);
SELECT * FROM #TARGET
So I'm having a target table , and a source table. What I want to accomplish is that when there is a large number of not matched items, to only insert the x top items with the highest value.
Using top on the merge itself won't work, because that would limit the whole source table, I want to do something like
WHEN NOT MATCHED
LIMIT(5) AND ORDER BY Value DESC --only insert the 5 non-matches with the highest value
INSERT VALUES (s.id,s.value)
---- UPDATE ----
My MERGE statement also contains an WHEN MATCHED THEN statement:
WHEN MATCHED THEN
UPDATE SET t.value = s.value
this sadly negates the answers given by Ian and Dog...
Isn't SET ROWCOUNT Deprecated, you could use the top clause if you do it like this:
;MERGE TOP (5) #TARGET t USING
(SELECT TOP (100) PERCENT * FROM #SOURCE ORDER BY VALUE DESC) AS s ON (t.id = s.id)
WHEN NOT MATCHED
THEN
INSERT VALUES (s.id,s.value);
SELECT * FROM #TARGET
The ORDER BY int the merge wont work unless you have a TOP Clause so using TOP (100) PERCENT tricks SQL into allowing the ordering.
Edit:
What about doing it in two steps?
;MERGE TOP (5) #TARGET t USING
(SELECT TOP (100) PERCENT * FROM #SOURCE ORDER BY VALUE DESC) AS s ON (t.id = s.id)
WHEN NOT MATCHED
THEN
INSERT VALUES (s.id,s.value);/*
WHEN MATCHED THEN
UPDATE SET t.value = s.value;*/
update t
set t.Value = s.Value
from #Target t
join #Source s on t.ID = s.ID
where t.Value <> s.Value
SELECT * FROM #TARGET
You can use SET ROWCOUNT n;
For example;
SET ROWCOUNT 4;
UPDATE Production.ProductInventory
SET Quantity = 400
WHERE Quantity < 300;
See; http://msdn.microsoft.com/en-us/library/ms188774.aspx
Or you can do
Insert to #Target
Select top 5 s.id, s.value from #Source s
order by s.value desc ... etc.

Resources