MS SQL Server : update with less amount of rows in source table - sql

What is the best way to update Table1 with all values from Table2 if Table2 has less rows than Table1? This considering that Table2 has no key that can be joined to Table1 for update.
TABLE1 TABLE2 RESULT TABLE1
id value value id value
----------------------------------------------------
1 NULL 4 1 4
2 NULL 6 2 6
3 NULL 8 3 8
4 NULL 4 4
5 NULL 5 6
6 NULL 6 8
7 NULL 7 4
Hope I make sense.
Thanks in advance.
EDIT: Pardon, did not specify its Microsoft SQL Server 2012. :/
EXAMPLE for SOLUTION:
DECLARE #t1 TABLE(id int, avalue int)
DECLARE #t2 TABLE(id INT, avalue int)
-- Generate 20 rows in #t1 table
INSERT
INTO #t1 (id)
SELECT Number
FROM dbo.Numbers
WHERE Number BETWEEN 1 AND 20
-- Generate 5 rows and value #t2 table
INSERT
INTO #t2 (id,avalue)
SELECT Number,
Number
FROM dbo.Numbers
WHERE Number BETWEEN 1 AND 5
-- The goal is to take all rows from #t2
-- and repeatively insert them in order into #t1
UPDATE t1
SET t1.avalue = t2.avalue
FROM #t1 t1
JOIN ( SELECT t2.*, COUNT(*) OVER () AS cnt
FROM #t2 t2
) AS t2
ON (t1.id - 1) % t2.cnt = t2.id - 1;
SELECT *
FROM #t1

Interesting problem. This first solution is for MySQL (I originally read the question as being about that database). After this solution is the one for SQL Server.
You need to generate a join key. Let me assume that id is really sequential. Then you can use modulo arithmetic to do the match:
update table1 t1
(select (#rn := #rn + 1) as seqnum, value
from table2 cross join
(select #rn := -1) vars
) t2 cross join
(select count(*) as cnt from table2) cnt
on mod((t1.id - 1), cnt.cnt) = t2.seqnum
set t1.value = t2.value;
If the id in table1 is not sequential, you can use a variable for that as well. It just further complicates the query:
update table1 t1 join
(select #rn1 := #rn + 1) as seqnum, id
from table1 t1 cross join
(select #rn1 := 0) vars
order by id
) t1s
on t1.id = t1s.id join
(select (#rn := #rn + 1) as seqnum, value
from table2 cross join
(select #rn := -1) vars
) t2 cross join
(select count(*) as cnt from table2) cnt
on mod((t1s.seqnum - 1), cnt.cnt) = t2.seqnum
set t1.value = t2.value;
EDIT:
You can readily do the same thing in SQL Server. It is actually easier:
update table1 t1
set t1.value = t2.value;
from table1 t1 join
(select t2.*, count(*) over () as cnt
from table2 t2
) t2
on (t1.id - 1) % t2.cnt = (t2.id - 1);
This formulation depends on the ids being sequential with no gaps. It is easy enough to loosen this restriction, but the query gets a wee bit more complicated.

Try this query.
DECLARE #Table1 AS TABLE
(
ID INT,
Value INT
)
DECLARE #Table2 AS TABLE
(
Value INT
)
INSERT INTO #Table1
SELECT 1, NULL UNION
SELECT 2, NULL UNION
SELECT 3, NULL UNION
SELECT 4, NULL UNION
SELECT 5, NULL UNION
SELECT 6, NULL UNION
SELECT 7, NULL
INSERT INTO #Table2
SELECT 4 UNION
SELECT 6 UNION
SELECT 8
DECLARE #nCOUNT as INT
SET #nCOUNT = (SELECT COUNT(*) FROM #Table2)
UPDATE TB1 SET TB1.Value = TB2.Value FROM #Table1 AS TB1
INNER JOIN
(SELECT T1.ID, T2.Value FROM
(SELECT *, CASE WHEN (ROW_NUMBER() OVER(ORDER BY ID) % #nCOUNT) = 0
THEN #nCOUNT
ELSE (ROW_NUMBER() OVER(ORDER BY ID) % #nCOUNT)
END AS ROID
FROM #Table1) AS T1
LEFT JOIN (SELECT VALUE, ROW_NUMBER() OVER(ORDER BY Value) AS ID FROM #Table2) AS T2 ON T2.ID = T1.ROID) AS TB2
ON TB1.ID = TB2.ID
SELECT * FROM #Table1

Related

Update values from multiple selected rows

I have 2 tables. Both have the same amount of rows.
Sample data:
Table1{
Id, IdTable2Row
}
Table2 {
Id, RedChicken -- LOL
}
Each row of Table1 (column IdTable2Row) should get the id of 1 row of table 2 (irrelevant which one).
Table1.IdTable2Row is null in every row before the operation.
How can I do something like this?
UPDATE Table1 SET IdTable2Row = (SELECT Id FROM [Table2])
update T1
set idtablerow = t2.id
from Table1 T1
inner join Table2 T2
on T1.something = T2.something
In your case, with no common column:
with T1 as
(
select Table1.*, row_number() over (order by anycolumn) rn
from Table1
)
update T1
set idtablerow = t2.id
from T1
inner join
(
select x1.*, row_number() over (order by id) rn
from Table2 x1
) T2
on t1.rn = t2.rn
Should work
If I understand correctly you need to match rows 1 to 1. Here is a solution I come up with:
declare #Table1 table(
Id INT,
IdTable2Row INT NULL
)
declare #Table2 table(
Id INT, RedChicken varchar(10) -- LOL
)
INSERT INTO #Table1
VALUES
(
1,NULL
),(
2,NULL
),(
3,NULL
);
INSERT INTO #Table2
VALUES
(
10,'s'
),(
20,'a'
),(
30,'b'
);
SELECT *
FROM #Table1 ;
SELECT *
FROM #Table2;
WITH cte1 AS
(
SELECT
ROW_NUMBER() over(ORDER by Id) AS rown
,Id
,IdTable2Row
FROM #Table1
),
cte2 AS
(
SELECT ROW_NUMBER() over(ORDER by Id) AS rown
,Id
,RedChicken
FROM #Table2
)
UPDATE t
SET IdTable2Row = c2.Id
FROM #Table1 t
JOIN cte1 c1 ON c1.Id = t.Id
JOIN cte2 c2 ON c1.rown = c2.rown
SELECT *
FROM #Table1
IN SQL SERVER if there is common column then you can join the table and update
update a set a.IdTable2Row=b.Id from
Table1 a ,Table2 b
where a.column=b.column

Delete row if column2 value exists anywhere in column1

A program starts with a table like the one below:
ID data1 data2 ... copyID
15 a b NULL
16 c d 11
You instruct the program to create new rows based off of information in existing rows. The copyID field holds the ID of the row in which data was copied from. After copying rows 1 and 2, the new table looks like the one below:
ID data1 data2 ... copyID
15 a b NULL
16 c d 11
17 a b 15
18 c d 16
Now, I would like to only select rows that aren't "duplicates" of rows we are already capturing. Since ID 15 is "original," it should be maintained. Since there is no ID = 11, we want to maintain ID 16. Since we already have ID = 15 and ID = 16, we do not need ID = 17 or ID = 18. What is the process to handle this decision making in SQL?
I think that the code below will resolve your issue:
select a.*
from yourtable a
left join yourtable b
on a.copid = b.id
where b.id is null
You can use EXISTS
For example:
declare #T table (ID int, data1 varchar(30), data2 varchar(30), copyID int);
-- adding the parent records
insert into #T (ID, data1, data2, copyID) values
(15,'a','b',NULL),
(16,'c','d',11);
-- adding the duplicates
insert into #T (ID, copyID, data1, data2)
select m.maxID + row_number() over (order by id) as newID, t.ID as copyID, t.data1, t.data2
from #T t
cross apply (select max(id) as maxID from #T) m;
-- Only selecting those the parent record does not exist
select *
from #T t1
where not exists (
select 1
from #T t2
where t1.copyID = t2.ID
and t1.data1 = t2.data1 and t1.data2 = t2.data2
);
-- Or via a LEFT JOIN and keeping those that are only on the left side
select t1.*
from #T t1
left join #T t2 on (t1.copyID = t2.ID and t1.data1 = t2.data1 and t1.data2 = t2.data2)
where t2.ID is null;
Returns:
ID data1 data2 copyID
15 a b NULL
16 c d 11
This is a simple exists:
select t.*
from t
where t.copyid is null or
not exists (select 1 from t t2 where t2.id = t.copyid);
This handles both the NULL case and the NOT EXISTS case.
This can be simplified to:
select t.*
from t
where not exists (select 1 from t t2 where t2.id = t.copyid);

Query for earliest datetime and corresponding number field

I'm attempting to update a table with a dollar amount based on the earliest datetime field from another table. For example:
Table 1
ID|INITIAL_ANNUAL_RATE_AMT|
1 | NULL (I want to update this to 25.02)
Table 2
ID|ANNUAL_RATE_AMT|STARTING_DATE|
1 |25.01 |1/1/2014
1 |25.02 |1/1/2013
I've got a query like this that retreives the earliest date from table 2 and the corresponding objects ID:
select ID,
MIN(t2.STARTING_DATE) as EARLIEST_START_DATE
from t2
group by t2.ID
But how can I leverage this into an update statement that sets the INITIAL_ANNUAL_RATE_AMT in table 1 to the earliest corresponding value in table 2?
Something like this (which currently fails):
update t1
set t1.Initial_Annual_Rate__c = t3.ANNUAL_RATE_AMT
from t1, t2
left join
(select t2.ID
MIN(t2.STARTING_DATE) as EARLIEST_START_DATE
from t2
group by t2.DEAL_ID)
as t3 ON (t3.DEAL_ID = t1.DEAL_ID)
One way is to use a CTE
;WITH C AS(
SELECT t.ID, EARLIEST_START_DATE, ANNUAL_RATE_AMT FROM(
select ID,
MIN(t2.STARTING_DATE) as EARLIEST_START_DATE
from #Table2 AS t2
group by t2.ID) t
INNER JOIN #Table2 AS t2 ON t2.ID = t.ID AND t.EARLIEST_START_DATE = t2.STARTING_DATE
)
UPDATE t1
SET INITIAL_ANNUAL_RATE_AMT = C.ANNUAL_RATE_AMT
FROM #Table1 AS t1
INNER JOIN C ON C.ID = t1.ID
SQLFIDDLE
Another method, using a window function to get the first row in each ID partitioned set:
-- Setup test data
declare #table1 table (ID int, INITIAL_ANNUAL_RATE_AMT decimal(9,2))
declare #table2 table (ID int, ANNUAL_RATE_AMT decimal(9,2), STARTING_DATE date)
INSERT INTO #table1 (ID, INITIAL_ANNUAL_RATE_AMT)
SELECT 1, NULL
INSERT INTO #table2 (ID, ANNUAL_RATE_AMT, STARTING_DATE)
SELECT 1,25.01,'1/1/2014'
UNION SELECT 1,25.02,'1/1/2013'
-- Do the update
;with table2WithIDRowNumbers as (
select ID, ANNUAL_RATE_AMT, STARTING_DATE, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY STARTING_DATE) as rowNumber
FROM #table2
)
UPDATE t1
SET INITIAL_ANNUAL_RATE_AMT=t2.ANNUAL_RATE_AMT
FROM table2WithIDRowNumbers t2
INNER JOIN #table1 t1 ON t1.ID=t2.ID
where t2.rowNumber=1
-- Show the result
SELECT * from #table1

alternative solution to too many JOINs

There is a table containing all names:
CREATE TABLE Names(
Name VARCHAR(20)
)
And there are multiple tables with similar schema.
Let's say:
CREATE TABLE T1
(
Name VARCHAR(20),
Description VARCHAR(30),
Version INT
)
CREATE TABLE T2
(
Name VARCHAR(20),
Description VARCHAR(30),
Version INT
)
I need to query description for each name, by following priority:
any records in T1 with matching name and version = 1
any records in T1 with matching name and version = 2
any records in T2 with matching name and version = 1
any records in T2 with matching name and version = 2
I want result from lower priority source only if there are no result from higher priority source.
So far that's I've got:
SELECT
N.Name AS Name, Description =
CASE
WHEN (T11.Description IS NOT NULL) THEN T11.Description
WHEN (T12.Description IS NOT NULL) THEN T12.Description
WHEN (T21.Description IS NOT NULL) THEN T21.Description
WHEN (T22.Description IS NOT NULL) THEN T22.Description
ELSE NULL
END
FROM Names AS N
LEFT JOIN T1 AS T11 ON T11.Name = N.Name AND T11.Version = 1
LEFT JOIN T1 AS T12 ON T12.Name = N.Name AND T12.Version = 2
LEFT JOIN T2 AS T21 ON T21.Name = N.Name AND T21.Version = 1
LEFT JOIN T2 AS T22 ON T22.Name = N.Name AND T22.Version = 2
It's working, but are there too much JOIN here? Is there any better approach?
sqlfiddle
Sample Input:
INSERT INTO Names VALUES('name1')
INSERT INTO Names VALUES('name2')
INSERT INTO Names VALUES('name3')
INSERT INTO Names VALUES('name4')
INSERT INTO Names VALUES('name5')
INSERT INTO Names VALUES('name6')
INSERT INTO T1 VALUES ('name1','name1_T1_1', 1)
INSERT INTO T1 VALUES ('name2','name2_T1_1', 1)
INSERT INTO T1 VALUES ('name3','name3_T1_1', 1)
INSERT INTO T1 VALUES ('name3','name3_T1_2', 2)
INSERT INTO T1 VALUES ('name5','name5_T1_2', 2)
INSERT INTO T2 VALUES ('name1','name1_T2_1', 1)
INSERT INTO T2 VALUES ('name4','name4_T2_1', 1)
Excepted result:
--
-- Excepted result:
-- Name Description
-- name1 name1_T1_1
-- name2 name2_T1_1
-- name3 name3_T1_1
-- name4 name4_T2_1
-- name5 name5_T1_2
-- name6 NULL
Well, this is a solution to eliminate the case statement and minimize the repetitive part of the query, it requires some joins of it's own of course, so you'd need quite some tables and/or versions to get any real benefit out of it:
;WITH
AllDescriptions AS
(
SELECT 1 AS Rank, * FROM T1
UNION ALL SELECT 2 AS Rank, * FROM T2
-- UNION ALL SELECT 3 AS Rank, * FROM T3
-- UNION ALL SELECT 4 AS Rank, * FROM T4
-- etc
),
Ranks AS
(
SELECT
AllDescriptions.Name,
MIN(AllDescriptions.Rank) AS Rank
FROM
AllDescriptions
GROUP BY
Name
),
Versions AS
(
SELECT
AllDescriptions.Name,
AllDescriptions.Rank,
MIN(AllDescriptions.Version) AS Version
FROM
AllDescriptions
INNER JOIN Ranks
ON Ranks.Name = AllDescriptions.Name
AND Ranks.Rank = AllDescriptions.Rank
GROUP BY
AllDescriptions.Name,
AllDescriptions.Rank
),
Descriptions AS
(
SELECT
AllDescriptions.Name,
AllDescriptions.Description
FROM
AllDescriptions
INNER JOIN Versions
ON Versions.Name = AllDescriptions.Name
AND Versions.Rank = AllDescriptions.Rank
AND Versions.Version = AllDescriptions.Version
)
SELECT
Names.*,
Descriptions.Description
FROM
Names
LEFT OUTER JOIN Descriptions
ON Descriptions.Name = Names.Name
Try this query and it will also give you the expected result.
SELECT N.name AS Name,
Description =
CASE
WHEN ( t1.description IS NOT NULL ) THEN t1.description
WHEN ( t2.description IS NOT NULL ) THEN t2.description
ELSE NULL
END
FROM names AS N
LEFT JOIN t1
ON t1.name = N.name
AND t1.version IN( 1, 2 )
LEFT JOIN t2
ON t2.name = N.name
AND t2.version IN ( 1, 2 )
select n.name, isnull(d.description,d1.Description) description
from Names n
outer apply (select top 1 t1.Name, t1.Description
from T1
WHERE t1.Name = n.name
order by Version asc
) d
outer apply (select top 1 t2.Name, t2.Description
from T2
WHERE t2.Name = n.name
order by Version asc
) d1

SQL need to return ranges from a column

I have a table that has an integer column named ID with values that may have gaps (e.g. 1,2,3,4,7,8,10,14,15,16,20)
I would like to find a query that would, in the example above, result in this:
1-4
7-8
10
14-16
20
= UPDATE =
Thanks to the code below (which seems to work well in SQL Server), I feel I am very close to getting this to work in MS-Access which is the goal. I'm still getting a syntax error though in my statment which I can't figure out...
SELECT val FROM
(
SELECT islands.PORTID, CSTR(islands.PORTID ) as val
FROM MYTABLE islands
WHERE NOT EXISTS (SELECT * FROM MYTABLE t2 WHERE t2.PORTID = islands.PORTID - 1)
AND NOT EXISTS (SELECT * FROM MYTABLE t2 WHERE t2.PORTID = islands.PORTID + 1)
UNION
SELECT
rngStart.PORTID
,CSTR(rngStart.PORTID ) + '-'
+ CSTR(MIN(rngEnd.PORTID)) as val
FROM MYTABLE rngStart
INNER JOIN MYTABLE checkNext ON checkNext.PORTID = rngStart.PORTID + 1
INNER JOIN
(
SELECT PORTID
FROM MYTABLE tblRangeEnd
WHERE NOT EXISTS (SELECT * FROM MYTABLE t2 WHERE t2.PORTID = tblRangeEnd.PORTID + 1)
) rngEnd on rngEnd.PORTID > rngStart.PORTID
WHERE NOT EXISTS (SELECT * FROM MYTABLE t2 WHERE t2.PORTID = rngStart.PORTID - 1)
GROUP BY rngStart.PORTID
) as tbl
ORDER BY PORTID ASC
SELECT val FROM
(
-- Get the islands
SELECT islands.ID, CAST(islands.ID as varchar(10)) as val
FROM #t1 islands
WHERE NOT EXISTS (SELECT * FROM #t1 t2 WHERE t2.ID = islands.ID - 1)
AND NOT EXISTS (SELECT * FROM #t1 t2 WHERE t2.ID = islands.ID + 1)
UNION
-- Get the ranges
SELECT
rngStart.ID
,CAST(rngStart.ID as varchar(10)) + '-'
+ CAST(MIN(rngEnd.ID) as varchar(10)) as val
FROM #t1 rngStart
INNER JOIN #t1 checkNext ON checkNext.ID = rngStart.ID + 1
INNER JOIN
(
SELECT ID
FROM #t1 tblRangeEnd
WHERE NOT EXISTS (SELECT * FROM #t1 t2 WHERE t2.ID = tblRangeEnd.ID + 1)
) rngEnd on rngEnd.ID > rngStart.ID
WHERE NOT EXISTS (SELECT * FROM #t1 t2 WHERE t2.ID = rngStart.ID - 1)
GROUP BY rngStart.ID
) as tbl
ORDER BY ID ASC
I used a table variable called #t1 but just replace that with your table name.
Here it is in action.
EDIT
To make this work in Access you will have to change the joins a little bit. Try this:
SELECT val FROM
(
SELECT islands.PORTID, CSTR(islands.PORTID) as val
FROM MYTABLE islands
WHERE NOT EXISTS
(SELECT * FROM MYTABLE t2 WHERE t2.PORTID = islands.PORTID - 1)
AND NOT EXISTS
(SELECT * FROM MYTABLE t2 WHERE t2.PORTID = islands.PORTID + 1)
UNION
SELECT
rngStart.PORTID
,CSTR(rngStart.PORTID) + '-' + CSTR(MIN(endPORTID)) as val
FROM MYTABLE rngStart
INNER JOIN
(
SELECT checkNext.PORTID as nextPORTID, rngEnd.PORTID as endPORTID
FROM MYTABLE checkNext
INNER JOIN
(
SELECT rngEnd.PORTID
FROM MYTABLE rngEnd
WHERE NOT EXISTS
(SELECT * FROM MYTABLE t2 WHERE t2.PORTID = rngEnd.PORTID + 1)
) AS rngEnd on rngEnd.PORTID > checkNext.PORTID - 1
) AS checkNext ON checkNext.nextPORTID = rngStart.PORTID + 1
WHERE NOT EXISTS
(SELECT * FROM MYTABLE t2 WHERE t2.PORTID = rngStart.PORTID - 1)
GROUP BY rngStart.PORTID
) as tbl
ORDER BY PORTID ASC
create table #sequence (id int not null primary KEY)
insert into #sequence(id)
select 1
union all select 2
union all select 3
union all select 4
union all select 7
union all select 8
union all select 10
union all select 14
union all select 15
union all select 16
union all select 20
--Find Contig ranges
select l.id as start,
(
select min(a.id) as id
from #sequence as a
left outer join #sequence as b on a.id = b.id - 1
where b.id is null
and a.id >= l.id
) AS fend
from #sequence as l
left outer join #sequence as r on r.id = l.id - 1
where r.id is null;
--Find missing values in sequence
select l.id + 1 as start, min(fr.id) - 1 as stop
from #sequence as l
left outer join #sequence as r on l.id = r.id - 1
left outer join #sequence as fr on l.id < fr.id
where r.id is null and fr.id is not null
group by l.id, r.id;
drop table #sequence
This will give you both the ranges and the gaps between the ranges so you can see what you have or what you need. sample data provided
This will work if you access to function row_number().
with C as
(
select ID,
ID - row_number() over(order by ID) as grp
from YourTable
)
select min(ID) as MinID,
max(ID) as MaxID
from C
group by grp
or with a sub-query instead of a common table expression.
select min(ID) as MinID,
max(ID) as MaxID
from (select ID,
ID - row_number() over(order by ID) as grp
from YourTable) as C
group by grp
Result:
MinID MaxID
----------- -----------
1 4
7 8
10 10
14 16
20 20
Try it on SQL Server https://data.stackexchange.com/stackoverflow/q/119411/
Another try:
SELECT
MIN(i), MAX(i)
FROM (
select
i - (SELECT COUNT(*) FROM tbl t WHERE t.i < tbl.i) g,
i
from tbl
) t
GROUP BY g
It'll be slow for sure, but I don't see other way to number rows in access.