mysql: duplicate certain rows using SQL? - sql

I have a table that looks like this:
nid vid tid
1 2 3
2 2 4
3 2 4
4 2 3
I'd like to run some SQL that will take each one of those rows and create another based on it like so:
foreach row where vid=2, create a duplicate row where vid = 3
So I'll end up with this:
nid vid tid
1 2 3
2 2 4
3 2 4
4 2 3
1 3 3
2 3 4
3 3 4
4 3 3
The table is not that big (less than 500 rows, I think) and this is a one-time thing (so the code does not need to be amazingly optimized or anything). Thanks!

you’ll probably want this:
INSERT INTO `table`(`nid`, `vid`, `nid`)
SELECT `nid`, `vid`+1, `nid`
FROM `table`
WHERE `vid` = 2
it inserts all rows, with vid incremented by 1

If you want a general solution (i.e. all values of vid, but adding one to each of them), this is just adapting dandres' answer:
INSERT INTO TheTable(nid, vid_incremented, nid)
SELECT nid, vid + 1 as vid_incremented , nid
FROM TheTable
Edit: fixed to do arithmetic in select statement.

Could you do the following:
INSERT INTO TheTable(nid, vid, nid)
SELECT nid, 3, nid
FROM TheTable
WHERE vid = 2
Two columns appear to have the same name, but the above should work. It takes the set of Vid = 2 elements, inserts them again but using Vid = 3.

Create another table with 2 fixed records, e.g. TwoRec (vvid), with values 2 and 3
Do a cartesian join with TwoRec, i.e.
SELECT TheTable.nid, TwoRec.vvid, TheTable.nid
FROM TheTable, TwoRec
WHERE TheTable.vid = 2;

I'd create a temp table of all of the vids you want to use:
create temporary table Vids
(
vid int
)
declare #vid int
declare #maxvid int
set #vid = 1
set #maxvid = 500
while #vid < #maxvid
begin
insert into vids values(#vid)
set #vid = #vid + 1
end
Then, I'd use that temp table to build out your other table:
select
t2.id,
t1.vid,
t2.nid
into newtable
from
vids t1
cross join tbl t2
order by
t1.vid,
t2.id

Related

SQL keep selecting records based on result of last selection

The title of this post is probably not correct. I have a table like the one below and I need SQL that will select all the records that are related to a certain value. This is a "history" table that keeps track of ID values where I keep track of what an ID used to be. Record 1 splits into two and becomes 2 and 3, then maybe 2 and 3 merge together and become 4. For example:
OldID NewID
1 2
1 3
3 4
5 4
2 6
2 7
In the above example, record 1 has become 2 and 3. Record 4 is 3 and 5 merged together. Record 2 was part of 1 and has now split into 6 and 7.
So if we look at record NewID = 7, it is related to record 2, 6, and 1
NewID 4 is related to 3, 5, 1, and 2.
So in the end I need syntax that will select all records that were related in this way to NewID = X. Is this possible? Is this like a recursion?
Are you looking something like this?
declare #x int = 4
;with cte as (
select oldid, newid from table4 where newid = #x
union all
select t.oldid, t.newid from cte c inner join table4 t on c.OldId = t.newid
) select * from cte

SQL Server find unique combinations

I have a table
rate_id service_id
1 1
1 2
2 1
2 3
3 1
3 2
4 1
4 2
4 3
I need to find and insert in a table the unique combinations of sevice_ids by rate_id...but when the combination is repeated in another rate_id I do not want it to be inserted
In the above example there are 3 combinations
1,2 1,3 1,2,3
How can I query the first table to get the unique combinations?
Thanx!
Try doing something like this:
DECLARE #TempTable TABLE ([rate_id] INT, [service_id] INT)
INSERT INTO #TempTable
VALUES (1,1),(1,2),(2,1),(2,3),(3,1),(3,2),(4,1),(4,2),(4,3)
SELECT DISTINCT
--[rate_id], --include if required
(
SELECT
CAST(t2.[service_id] AS VARCHAR) + ' '
FROM
#TempTable t2
WHERE
t1.[rate_id] = t2.[rate_id]
ORDER BY
t2.[rate_id]
FOR XML PATH ('')
) AS 'Combinations'
FROM
#TempTable t1
I put the values in a table variable just for ease of testing the SELECT query.

Loop through table data and compare using split

I have two tables and i need to compare data and update/insert one table records. What iam trying to do is I need to take each record from Table1,
use a split function then for each text in split, compare dataelement field between both these tables. We are syncing data in Table2 to similar to Table1.
Please let me know how this can be done. I am ok using cursor or merge. This is the scenario
DataTable:
dataId dataelement
1 Check
2 System
3 Balances
4 City
5 State
6 Zip
7 Other
Table1:
Id reqId dataelementValues
1 52 Check
2 52 City;State;System
3 52 Other
Table2:
elId dataId dataelement reqId Active
1 6 Zip 52 1
2 1 Check 52 1
3 4 city 52 1
4 5 State 52 1
Outcome Should be similar to after compare in table2
Table2:
elId dataId dataelement reqId Active
1 6 Zip 52 0 (Should be set to inactive as it exists in table2 but not in table1)
2 1 Check 52 1 (NO Updates as it exists in both the tables)
3 4 city 52 1 (NO Updates as it exists in both the tables)
4 5 State 52 1 (NO Updates as it exists in both the tables)
5 2 System 52 1 (Get the dataid for system from datatable and insert in table2 as it exists in table1 but not in table2)
6 7 Other 52 1 (Get the dataid for other from datatable and insert in table2 as it exists in table1 but not in table2)
This is where iam at, not sure how to set inactive on table2.
WHILE Exists(Select * from #Table1)
BEGIN
Select #currentId = Id, #dataValue = dataelementValues FROM #Table1 where rowID=(SELECT top 1 rowID from #Table1 order by rowID asc)
SET #pos = 0
SET #len = 0
WHILE CHARINDEX(';', #dataValue, #pos+1)>0
BEGIN
SET #dataValueValue = SUBSTRING(#dataValue, #pos, CHARINDEX('|', #dataValue, #pos+1) - #pos)
SET #glbaDEId = (Select DataTable.dataId from datatable where dataelement = #dataValue)
IF NOT Exists (Select * from #Table2 Where DataElement=#dataValue)
BEGIN
--Insert into table2
END
SET #pos = CHARINDEX('|', #dataValue, #pos+#len) +1
END
DELETE from #Table1 where rowID=(SELECT top 1 rowID from #Table1 order by rowID asc )
END
You can try using a MERGE statement with a few other tricks.
Merge Guide
-- Create a CTE that will split out the combined column and join to DataTable
-- to get the dataId
;WITH cteTable1Split AS
(
SELECT reqId, dt.* FROM
(
SELECT
[dataelement] = y.i.value('(./text())[1]', 'nvarchar(4000)'),
reqId
FROM
(
-- use xml to split column
-- http://sqlperformance.com/2012/07/t-sql-queries/split-strings
SELECT x = CONVERT(XML, '<i>'
+ REPLACE([dataelementValues], ';', '</i><i>')
+ '</i>').query('.'),
reqId
FROM Table1
) AS a CROSS APPLY x.nodes('i') AS y(i)
) a
JOIN DataTable dt ON dt.[dataelement] = a.[dataelement]
)
-- Merge Table2 with the CTE
MERGE INTO Table2 AS Target
USING cteTable1Split AS Source
ON Target.[dataelement] = Source.[dataelement]
-- If exists in Target (Table2) but not Source (CTE) then UPDATE Active flag
WHEN NOT MATCHED BY Source THEN
UPDATE SET ACTIVE = 0
-- If exists in Source (CTE) but not Target (Table2) then INSERT new record
WHEN NOT MATCHED BY TARGET THEN
INSERT ([dataId], [dataelement], [reqId], [Active])
VALUES (SOURCE.[dataId], SOURCE.[dataelement], SOURCE.[reqId], 1);
SQL Fiddle
You've not mentioned whether you have control over the structure of these tables, so I'm going to go ahead and suggest you redesign Table1 to normalise the dataelementValues column.
That is, instead of this:
Table1:
Id reqId dataelementValues
1 52 Check
2 52 City;State;System
3 52 Other
You should be storing this:
Table1_New:
Id reqId dataelementValues
1 52 Check
2 52 City
2 52 State
2 52 System
3 52 Other
You may also need a new, surrogate, primary key column on the table, using an IDENTITY(1,1) specification.
Storing your data like this is how relational databases are intended to be used/designed. As well as simplifying the problem at hand right now, you might find it removes potential problems in the future as well.
The main challenge here is creating a rowset with the dataelementValues correctly split into separate rows. The accepted answer shows clearly how this can be used as the source for a merge statement to achieve the update and insert operation.
However, there are alternative ways to create the split or normalised rowset.
One way, which I personally prefer for clarity over resorting to xml in this particular case, is a like join. This takes advantage of the fact that you already have the separate rows you need in DataTable, just not with all the columns you need.
Select DT.dataId, DT.dataelement, T1.reqid
From Table1 T1
Inner join DataTable DT
On T1.dataelementValues like '%' + DT.dataelement + '%'
I've not been able to test this right now but should give the rowset you need, because the like operator causes one T1 row to match three times to the corresponding DT rows.

How do you find a missing number in a table field starting from a parameter and incrementing sequentially?

Let's say I have an sql server table:
NumberTaken CompanyName
2 Fred 3 Fred 4 Fred 6 Fred 7 Fred 8 Fred 11 Fred
I need an efficient way to pass in a parameter [StartingNumber] and to count from [StartingNumber] sequentially until I find a number that is missing.
For example notice that 1, 5, 9 and 10 are missing from the table.
If I supplied the parameter [StartingNumber] = 1, it would check to see if 1 exists, if it does it would check to see if 2 exists and so on and so forth so 1 would be returned here.
If [StartNumber] = 6 the function would return 9.
In c# pseudo code it would basically be:
int ctr = [StartingNumber]
while([SELECT NumberTaken FROM tblNumbers Where NumberTaken = ctr] != null)
ctr++;
return ctr;
The problem with that code is that is seems really inefficient if there are thousands of numbers in the table. Also, I can write it in c# code or in a stored procedure whichever is more efficient.
Thanks for the help
Fine, if this question isn't going to be closed, I may as well Copy and paste my answer from the other one:
I called my table Blank, and used the following:
declare #StartOffset int = 2
; With Missing as (
select #StartOffset as N where not exists(select * from Blank where ID = #StartOffset)
), Sequence as (
select #StartOffset as N from Blank where ID = #StartOffset
union all
select b.ID from Blank b inner join Sequence s on b.ID = s.N + 1
)
select COALESCE((select N from Missing),(select MAX(N)+1 from Sequence))
You basically have two cases - either your starting value is missing (so the Missing CTE will contain one row), or it's present, so you count forwards using a recursive CTE (Sequence), and take the max from that and add 1
Tables:
create table Blank (
ID int not null,
Name varchar(20) not null
)
insert into Blank(ID,Name)
select 2 ,'Fred' union all
select 3 ,'Fred' union all
select 4 ,'Fred' union all
select 6 ,'Fred' union all
select 7 ,'Fred' union all
select 8 ,'Fred' union all
select 11 ,'Fred'
go
I would create a temp table containing all numbers from StartingNumber to EndNumber and LEFT JOIN to it to receive the list of rows not contained in the temp table.
If NumberTaken is indexed you could do it with a join on the same table:
select T.NumberTaken -1 as MISSING_NUMBER
from myTable T
left outer join myTable T1
on T.NumberTaken= T1.NumberTaken+1
where T1.NumberTaken is null and t.NumberTaken >= STARTING_NUMBER
order by T.NumberTaken
EDIT
Edited to get 1 too
1> select 1+ID as ID from #b as b
where not exists (select 1 from #b where ID = 1+b.ID)
2> go
ID
-----------
5
9
12
Take max(1+ID) and/or add your starting value to the where clause, depending on what you actually want.

Update a column of a table to be a sequence

I want to update a table's column using the results of a stored procedure
create procedure seq
as
DECLARE #NextValue INT;
INSERT SequencesTable DEFAULT VALUES;
DELETE SequencesTable WITH(READPAST);
SELECT SCOPE_IDENTITY();
go
I can't use a UDF since it is nodeterministic. Something like this won't work
UPDATE [dbo].[t1] SET [c1] = seq
I have a feeling I am approaching this the wrong way.
I just want to do update a table that looks like this
1 1
2 2
1 4
1 4
5 5
1 2
To look like this
1 1
2 2
3 4
4 4
5 5
6 2
Try something like this:
update a set c1 = c1_new
from (
select
c1, c2
, c1_new = row_number() over (order by nullif(c1,c1))
from dbo.t1
) a
Play around with the inner query until you get something you like, then apply the update.
The NULLIF(c1,c1) attempts to preserve original order on disk. It always returns null.