Insert values in table only one column changes value - sql

I've got a table with 2 columns,
GROUP PROJECTS
10001 1
10001 2
First column (GROUP) stays the same value 10001.
Second column (PROJECTS) changes values 3,5,9,100, etc. (I have 400 project ID's)
What would be to correct (loop?) statement to insert all 400 PROJECTS.
I used insert, values for smaller lists.
INSERT INTO table (GROUP_ID, PROJECTS) VALUES (10001, 1, 10001, 2, 10001, etc, 10001, etc);
I have the list in Excel (if needed I can create a Temp table with all 400 project ID's)
Thanks.

I typically write such inserts as:
INSERT INTO table(GROUP_ID, PROJECTS)
select 10001, 1 from dual union all
select 10001, 2 from dual union all
. . . ;
You should be able to generate the select statement pretty easily in Excel.

If the project IDs exist in their own table (or you can create one from your Excel data), then yu can get the list of values from there and cross-join those with all the group IDs:
insert into group_projects (group_id, project_id)
select g.group_id, p.project_id
from groups g
cross join projects p
where not exists (
select 1 from group_projects gp
where gp.group_id = g.group_id and gp.project_id = p.project_id
);
The where not exists() excludes all the existing pairs so you don't insert duplicates.
SQL Fiddle
If the groups don't have their own table then you can use the existing values from a subquery:
insert into group_projects (group_id, project_id)
select g.group_id, p.project_id
from (select distinct group_id from group_projects) g
cross join projects p
where not exists (
select 1 from group_projects gp
where gp.group_id = g.group_id and gp.project_id = p.project_id
);
You could use Gordon's approach to generate the project ID list as a subquery as well, if you didn't want to create a table for those.

I'd go with what I view as a simpler and much more readable solution... create the temp table with the data from Excel, then run this-
DECLARE
CURSOR c1
IS
SELECT project_id
FROM temp_table
WHERE project_id IS NOT NULL;
BEGIN
FOR rec in c1
LOOP
INSERT INTO table
VALUES (10001, rec.project_id);
COMMIT;
END LOOP;
END;
Seems cleaner than one giant insert statement or something complex with joins and sub-queries. If you wanted to make sure the value doesn't already exist in "table", add that criteria to the cursor select statement, or if you have constraints on the table add an exception handler in the loop.

Related

SQL clone/replicate records within same table with a condition

I have a table and i would like to replicate/clone records within the same table. However i would like to do that with a condition. And the condition is i have a column called recordcount with numeric values. For example Row 1 can take on a value of recordcount say 7, then i would like my row 1 to be replicated 7 times. Row 2 could take on a value say 9 then i would like row 2 to be replicated 9 times.
Any help is appreciated. Thank you
What you can do (and I'm pretty sure it's not a best practice),
Is to hold a table with just numbers, which has rowcount that correspond to the numeric value.
Join that with your table, and project your table only.
Example:
create table nums(x int);
insert into nums select 1;
insert into nums select 2;
insert into nums select 2;
insert into nums select 3;
insert into nums select 3;
insert into nums select 3;
create table t (txt varchar(10) , recordcount int);
insert into t select 'A',1;
insert into t select 'B',2;
insert into t select 'C',3;
select t.*
from t
inner join nums
on t.recordcount = nums.x
order by 1
;
Will project:
"A",1
"B",2
"B",2
"C",3
"C",3
"C",3

Deciphering SQL query

I am currently reviewing a query without access to the databases on which the query is performed. (It's not ideal but that's what I am tasked with). I am not a SQL expert and trying to identify what the below code does as I cannot run the query. It is reading from and writing to the same temp table (duplicating?). I don't know what the source of 'Y' is or what the end result is. Any help is appreciated. Thank you.
INSERT INTO #temp1
SELECT X.CURSTATUS ,X.GENDER ,Y.PACKAGE ,X.AGE ,1 AS factor1 ,1 AS factor2;
FROM #temp1 X WITH (NOLOCK) ,
( SELECT 'P1' AS PACKAGE UNION ALL SELECT 'P2' ) Y
WHERE X.PACKAGE = 'P5';
It is not really writing to the same table. It is "appending" rows to the same table. That is, existing data in the table is not affected.
What it is doing is adding rows for packages "P1" and "P2" for all "P5" packages. This adds the new rows to the table; the "P5" row remains.
For every row in #temp that has a PACKAGE value of "P5", the query is inserting two new rows with PACKAGE values of "P1" & "P2" respectivlly.
Reformatting the query and replacing obsolete syntax with modem syntax should make it easier to understand.
INSERT INTO #temp1 (CURSTATUS, GENDER, PACKAGE, AGE, factor1, factor2)
SELECT
X.CURSTATUS,
X.GENDER,
Y.PACKAGE,
X.AGE,
1 AS factor1,
1 AS factor2
FROM
#temp1 X
CROSS JOIN (
SELECT 'P1' AS PACKAGE
UNION ALL
SELECT 'P2'
) Y
WHERE
X.PACKAGE = 'P5';
INSERT INTO #temp1
-- this is where that data is being inserted into. It should BTW have columns explicitly defined, this format is a SQL antipattern
SELECT X.CURSTATUS ,X.GENDER ,Y.PACKAGE ,X.AGE ,1 AS factor1 ,1 AS factor2;
FROM #temp1 X WITH (NOLOCK) ,
-- this is selecting the current rows from #temp
( SELECT 'P1' AS PACKAGE UNION ALL SELECT 'P2' ) Y
--Y is a two record table with one column called package, since there is no specific join shown, it is a cross join - Again an antipattern, it is far better to explicitly use the Cross Join keywords to make it clear what is going on.
WHERE X.PACKAGE = 'P5';
-- this filters the records from #temp to grab only those where the record values is 'P5'. Since it cross joins to the Two record table Y, it takes the data in the other columns for the P% records and inserts new records for P1 and P2. If you have ten P5 records, this insert would insert 10 P1 records and 10 P2 records.

How to insert columns with adding sequence column?

In an Oracle database (11gR2), I have a table my_table with columns (sequence, col1, col2, col3). I want to insert values into the table that are queried from other tables, i.e. insert into my_table select <query from other tables>. The problem is that the primary key is the four columns, hence I need to add a sequence starting from 0 up till the count of the rows to be inserted (order is not a problem).
I tried using a loop like this:
DECLARE
j NUMBER;
r_count number;
BEGIN
select count(1) into r_count from <my query to be inserted>;
FOR j IN 0 .. r_count
LOOP
INSERT INTO my_table
select <my query, incorporating r_count as sequence column> ;
END LOOP;
END;
But it didn't work, actually looped r_count times trying to insert the entire rows every time, as logically it shall do. How can I achieve the expected goal and insert rows with adding a sequence column?
Don't do this in a loop. Just use row_number():
INSERT INTO my_table(seq, . . .)
select row_number() over (order by NULL) - 1, . . .
from . . .;
Let's create table with sample data (to simulate your source of data)
-- This is your source query table (can be anything)
CREATE TABLE source_table
(
source_a VARCHAR(255),
source_b VARCHAR(255),
source_c VARCHAR(255)
);
insert into source_table (source_a, source_b, source_c) values ('A', 'B', 'C');
insert into source_table (source_a, source_b, source_c) values ('D', 'E', 'F');
insert into source_table (source_a, source_b, source_c) values ('G', 'H', 'I');
Then create target table, with id and 3 data columns.
-- This is your target_table
CREATE TABLE target_table
(
id NUMBER(9,0),
target_a VARCHAR2(255),
target_b VARCHAR2(255),
target_c VARCHAR2(255)
);
-- This is sequence used to ensure unique number in 1st column
CREATE sequence target_table_id_seq start with 0 minvalue 0 increment BY 1;
Finally, perform insert, loading id from sequence, rest of the data from source table.
INSERT INTO target_table
SELECT target_table_id_seq.nextval,
source_a,
source_b,
source_c
FROM source_table;
Results might look like
1 A B C
2 D E F
3 G H I
If you added some values later, they will continue with numbering 4,5,6 etc.. Or do you want to get order only inside the group ? Thus if you added 2 more rows JKL and MNO, target table would look like this
1 A B C
2 D E F
3 G H I
1 J K L
2 M N O
For that you need different solution (don't even need sequencer)
SELECT
RANK() OVER (ORDER BY source_a, source_b, source_c),
source_a,
source_b,
source_c
FROM source_table;
Technically you could use ROWNUM directly, BUT I opt for RANK() OVER analytical function due to consistent result. Please note, that this will breach your complex primary key if you try to insert the same rows twice (My first solution doesn't)
Clearly, you should use Oracle sequence.
First, create a sequence:
create sequence seq_my_table start with 0 minvalue 0 increment by 1;
Then use it:
INSERT INTO my_table (sequence, ...)
select seq_my_table.nextval, <the rest of my query>;
Sequence numbers will be inserted in succession.
So, you already have the table, it has the required number of rows, and now you want to add numbers from 0 to total number of rows minus one in the column named sequence? (perhaps not "sequence" but something less likely to clash with Oracle reserved words?)
Then this should work:
update my_table set seq = rownum - 1;

INSERT multiple rows with SELECT and an array

In EXCEL/VBA I can program my way out of a thunderstorm, but in SQL I am still a novice. So apologies, after much Googling I can only get partway to a solution which I presume ultimately will be pretty simple, just not wrapping my head around it.
I need to create an INSERT script to add multiple rows in a 3-column table. A simple insert would be:
INSERT INTO table VALUES(StoreId, ItemID, 27)
First hurdle is dynamically repeat this for every StoreID in a different table. Which I think becomes this:
INSERT INTO table
SELECT (SELECT StoreID FROM Directory.Divisions), ItemID, 27)
If that is actually correct and would effectively create the 50-60 rows for each store, then I'm almost there. The problem is the ItemID. This will actually be an array of ItemIDs I want to feed in manually. So if there are 50 stores and 3 ItemIDs, it would enter 150 rows. Something like:
ItemID = (123,456,789,246,135)
So how can I merge these two ideas? Pull the StoreIDs from another table, feed in the array of items for the second parameter, then my hardcoded 27 at the end. 50 stores and 10 items should create 500 rows. Thanks in advance.
You can use into to insert into the target table. To generate itemid's you will have to use union all with your values and cross join on the divisions table.
select
d.storeid,
x.itemid,
27 as somecolumn
into targettablename
from Directory.Divisions d
cross join (select 123 as itemid union all select 456 union all select 789...) x
Edit: If the table to insert into isn't created yet, it should be created before inserting the data.
create table targettable as (store_id varchar(20), item_id varchar(20),
somecolumn int);
insert into targettable (store_id, item_id, somecolumn)
select
d.storeid,
x.itemid,
27
from Directory.Divisions d
cross join (select 123 as itemid union all select 456 union all select 789...) x
Firstly you need your array of item ids in a table of some sort. Either a permanent table, table variable or temporary table. For example using a temporary table, which you prefix with a hash symbol:
CREATE TABLE #ItemIds (item_id int)
INSERT INTO #ItemIds VALUES (1)
INSERT INTO #ItemIds VALUES (2)
...
INSERT INTO #ItemIds VALUES (10)
Then this should do the trick:
INSERT INTO table
SELECT StoreId, item_Id, 27
FROM Directory.Divisions, #ItemIds
The results set from the SELECT will be inserted into 'table'. This is an example of a cartesian join. Because there is no join condition, every row from Directory.Divisions is joined to every row in #ItemIds. Hence if you have 50 stores and 10 items, that will result in 50 x 10 = 500 rows.
You may declare table variable for item IDs and use CROSS JOIN to multiply division records to items: http://sqlfiddle.com/#!3/99438/1
create table Divisions(StoreId int)
insert into Divisions values (1), (2), (3)
declare #items table(ItemID int)
insert into #items values (5), (6), (7)
-- insert into target(stireid, itemid, otherColumn)
select d.StoreId, i.ItemID, 27
from Divisions d
cross join #items i

Matching a set of child records between two similar table hierarchies

I have two similar table hierarchies:
Owner -> OwnerGroup -> Parent
and
Owner2 -> OwnerGroup2
I would like to determine if there is an exact match of Owners that exists in Owner2 based on a set of values. There are approximately a million rows in each Owner table. Some OwnerGroups contain up to 100 Owners.
So basically if there is an OwnerGroup than contains Owners "Smith", "John" and "Smith, "Jane", I want to know the id of the OwnerGroup2s that are exact matches.
The first attempt at this was to generate a join per Owner (which required dynamic sql being generated in the application:
select og.id
from owner_group2 og
-- dynamic bit starts here
join owner2 o1 on
(og.id = o1.og_id) AND
(o1.given_names = 'JOHN' and o1.surname='SMITH')
-- dynamic bit ends here
join owner2 o2 on
(og.id = o2.og_id) AND
(o2.given_names = 'JANE' and o2.surname='SMITH');
This works fine until for small numbers of owners, but when we have to deal with the 100 Owners in a group scenario as this query plan means there 100 nested loops and it takes almost a minute to run.
Another option I had was to use something around the intersect operator. E.g.
select * from (
select o.surname, o.given_names
from owner1 o1
join owner_group1 og1 on o1.og_id = og1.id
where
og1.parent_id = 1936233
)
intersect
select o.surname, o.given_names
from owner2 o2
join owner_group2 og2 on og2.id = o2.og_id;
I'm not sure how to suck out the owner2.id in this scenario either - and it was still running in the 4-5 second range.
I feel like I am missing something obvious - so please feel free to provide some better solutions!
You're on the right track with intersect, you just need to go a bit further. You need to join the results of it back to the owner_groups2 table to find the ids.
You can use the listagg function to convert the groups into comma-separated lists of the names (note - requires 11g). You can then take the intersection of these name lists to find the matches and join this back to the list in owner_groups2.
I've created a simplified example below, in it "Dave, Jill" is the group that is present in both tables.
create table grps (id integer, name varchar2(100));
create table grps2 (id integer, name varchar2(100));
insert into grps values (1, 'Dave');
insert into grps values(1, 'Jill');
insert into grps values (2, 'Barry');
insert into grps values(2, 'Jane');
insert into grps2 values(3, 'Dave');
insert into grps2 values(3, 'Jill');
insert into grps2 values(4, 'Barry');
with grp1 as (
SELECT id, listagg(name, ',') within group (order by name) n
FROM grps
group by id
), grp2 as (
SELECT id, listagg(name, ',') within group (order by name) n
FROM grps2
group by id
)
SELECT * FROM grp2
where n in (
-- find the duplicates
select n from grp1
intersect
select n from grp2
);
Note this will still require a full scan of owner_groups2; I can't think of a way you can avoid this. So your query is likely to remain slow.