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
Related
I have the following table:
EventID=00002,DocumentID=0005,EventDesc=ItemsReceived
I have the quantity in another table
DocumentID=0005,Qty=20
I want to generate a result of 20 lines (depending on the quantity) with an auto generated column which will have a sequence of:
ITEM_TAG_001,
ITEM_TAG_002,
ITEM_TAG_003,
ITEM_TAG_004,
..
ITEM_TAG_020
Here's your sql query.
with cte as (
select 1 as ctr, t2.Qty, t1.EventID, t1.DocumentId, t1.EventDesc from tableA t1
inner join tableB t2 on t2.DocumentId = t1.DocumentId
union all
select ctr + 1, Qty, EventID, DocumentId, EventDesc from cte
where ctr <= Qty
)select *, concat('ITEM_TAG_', right('000'+ cast(ctr AS varchar(3)),3)) from cte
option (maxrecursion 0);
Output:
Best is to introduce a numbers table, very handsome in many places...
Something along:
Create some test data:
DECLARE #MockNumbers TABLE(Number BIGINT);
DECLARE #YourTable1 TABLE(DocumentID INT,ItemTag VARCHAR(100),SomeText VARCHAR(100));
DECLARE #YourTable2 TABLE(DocumentID INT, Qty INT);
INSERT INTO #MockNumbers SELECT TOP 100 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values;
INSERT INTO #YourTable1 VALUES(1,'FirstItem','qty 5'),(2,'SecondItem','qty 7');
INSERT INTO #YourTable2 VALUES(1,5), (2,7);
--The query
SELECT CONCAT(t1.ItemTag,'_',REPLACE(STR(A.Number,3),' ','0'))
FROM #YourTable1 t1
INNER JOIN #YourTable2 t2 ON t1.DocumentID=t2.DocumentID
CROSS APPLY(SELECT Number FROM #MockNumbers WHERE Number BETWEEN 1 AND t2.Qty) A;
The result
FirstItem_001
FirstItem_002
[...]
FirstItem_005
SecondItem_001
SecondItem_002
[...]
SecondItem_007
The idea in short:
We use an INNER JOIN to get the quantity joined to the item.
Now we use APPLY, which is a row-wise action, to bind as many rows to the set, as we need it.
The first item will return with 5 lines, the second with 7. And the trick with STR() and REPLACE() is one way to create a padded number. You might use FORMAT() (v2012+), but this is working rather slowly...
The table #MockNumbers is a declared table variable containing a list of numbers from 1 to 100. This answer provides an example how to create a pyhsical numbers and date table. Any database should have such a table...
If you don't want to create a numbers table, you can search for a tally table or tally on the fly. There are many answers showing approaches how to create a list of running numbers...a
I have T-SQL Table below.
ID Cost MaxCost
-------------------------------
2 200 300
3 400 1000
6 20 100
The above table must have 10 rows with IDs 1 to 10. So its missing 7 rows. How do i insert missing rows with proper ID. The cost & maxcost for missing rows will be zero. Do i need to create a temp table that holds 1 to 10 numbers?
No need for temp table, simple tally derived table and LEFT OUTER JOIN are sufficient:
CREATE TABLE #tab(ID INT, Cost INT, MaxCost INT);
INSERT INTO #tab(ID, Cost, MaxCost)
VALUES (2, 200,300),(3, 400, 1000) ,(6, 20, 100);
DECLARE #range_start INT = 1
,#range_end INT = 10;
;WITH tally AS
(
SELECT TOP 1000 r = ROW_NUMBER() OVER (ORDER BY name)
FROM master..spt_values
)
INSERT INTO #tab(id, Cost, MaxCost)
SELECT t.r, 0, 0
FROM tally t
LEFT JOIN #tab c
ON t.r = c.ID
WHERE t.r BETWEEN #range_start AND #range_end
AND c.ID IS NULL;
SELECT *
FROM #tab
ORDER BY ID;
LiveDemo
EDIT:
Tally table is simply number table. There are many ways to achieve it with subquery:
recursive cte
ROW_NUMBER() from system table that holds many values (used here)
UNION ALL and CROSS JOIN
VALUES(...)
using OPENJSON (SQL Server 2016+)
...
The TOP 1000 will generate only 1000 records if you know that you need more you can use:
SELECT TOP 1000000 r = ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM master..spt_values c
CROSS JOIN master..spt_values c2;
Since your number of rows is low you could just define the data explicitly...
CREATE TABLE Data(ID INT, Cost INT, MaxCost INT);
INSERT INTO Data(ID, Cost, MaxCost) VALUES(2, 200, 300);
INSERT INTO Data(ID, Cost, MaxCost) VALUES(3, 400, 1000);
INSERT INTO Data(ID, Cost, MaxCost) VALUES(6, 20, 100);
and the query...
select *
from (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) RowNums (ID)
left outer join Data on RowNums.ID = Data.ID
The first part defines a column ID with rows 1-10, it then left outer joins to your data. The beauty of this is that it is very readable.
I like to google for new and better ways to do things.. so i stumbled over this post and...Well what worked good in SQL7 and works good in SQL2016 is to just use a outer join and look for NULL values(null is missing data) ....
insert into DestTable (keyCol1,col1,col2,col3...)
select keyCol1,col1,col2,col3,...)
from SouceTable as s
left outer join DestTable as d on d.KeyCol1=s.KeyCol1
where d.KeyCol1 is null
and ...
feel free to test it
wrap your statement in a transaction, delete a few rows and see them come back in the select statement that would normally insert the rows in the destination table...
BEGIN TRAN
--> delete a subset of the data, in this case 5 rows
set rowcount 5;
-->delete and show what is deleted
delete from DestTable;
OUTPUT deleted.*,'DELETD' as [Action]
--> Perform the select to see if the selected rows that are retured match the deleted rows
--insert into DestTable (keyCol1,col1,col2,col3...)
Select keyCol1,col1,col2,col3,...)
from SouceTable as s
left outer join DestTable as d on d.KeyCol1=s.KeyCol1
where d.KeyCol1 is null
and ...
ROLLBACK
another way would be a TSQL merge, google that if you need to also update and optionally delete...
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.
I have a list of items that is 860 items long. When i execute the query: select * from tableA where item in (... items ...) I get 858 items. I would like to know the 2 items in the list that are not in tableA.
NOT returns all of the items in the table that are not in the list, I want all the items in the list that are not in the table.
I would recommend that you convert your list into a temp table (there are a ton of udfs floating around that you can use ex: http://blog.sqlauthority.com/2007/05/06/sql-server-udf-function-to-convert-list-to-table/)
Once you have your temp table #List, you can do the following;
CREATE TABLE #List
(
[ListItem] INT
)
SELECT
*
FROM
#List AS l
LEFT OUTER JOIN
tableA AS t
ON
t.[Item] = l.[ListItem]
WHERE
t.[Item] IS NULL
See it in action: https://data.stackexchange.com/stackoverflow/query/61259/items-not-returned-from-a-list
Based on my original understanding of the question, I suggested to just add the keyword NOT
SELECT * FROM tableA WHERE item NOT IN (... items ...)
But as per the comment the above will not return what you want. The original question was edited to include this new infomration.
So, you need to get your data from your WHERE clause into a form that is queryable. Here is one way to do it where I create an additional table named "items" and have INSERT statements to place each item into this items table. Since I do not have access to your data, I am going to use integers for the items and set it up with a smaller amount of data.
--Set up some sample data
CREATE TABLE tableA(item INT PRIMARY KEY)
INSERT INTO tableA SELECT 1
INSERT INTO tableA SELECT 2
INSERT INTO tableA SELECT 3
INSERT INTO tableA SELECT 4
INSERT INTO tableA SELECT 9
INSERT INTO tableA SELECT 10
SELECT * FROM tableA WHERE item IN (0,1,2,3,4,5,6)
SELECT * FROM tableA WHERE item NOT IN (0,1,2,3,4,5,6)
-- Create a table and insert all the 860 items from your where clause
CREATE TABLE items(item INT)
INSERT INTO items SELECT 0
INSERT INTO items SELECT 1
INSERT INTO items SELECT 2
INSERT INTO items SELECT 3
INSERT INTO items SELECT 4
INSERT INTO items SELECT 5
INSERT INTO items SELECT 6
-- Want to find a query that returns all of the items in the newly created items table
-- that are not in the original tableA (in this example, the values returned are 0,5,6)
SELECT * FROM items WHERE item NOT IN (SELECT item FROM tableA)
I have two table "TempStore" and "Store" with the same column called "Items".
There is data in "TempStore" table which I need to move over to "Store" table which requires few modifications.
I need to iterate over "TempStore" data (i.e. items) and insert into Store...
More specifically: How can I iterate over TempStore (in sql) where "for each item in 'TempStore' I need to store 2 or maybe 3 items in 'Store' with little modification", how can I accomplish this?
What I want to do is take each rowdata from "[SELECT * FROM TempStore]" and insert three records in "Store" with being able to change "items"
try INSERT-SELECT:
INSERT INTO Store
(col1, col2, col3...)
SELECT
col1, col2, col3...
FROM TempStore
WHERE ...
just make the SELECT return one row for every insert, and produce the values in the Cols that you need. You might need CASE and a join to another table to make the extra rows.
EDIT based on comments, OP wanted to see the numbers table in action
Lets say TempStore table has {Items,
Cost, Price, ActualCost, ActualPrice}
But in the Store table I need to store
{Items, Cost, Price}. The ActualCost
and ActualPrice from TempStore datarow
would need to be added as another row
in Store....(I hope this makes
sense)....Anyways, is the solution
using "WHILE-BEGIN-END"??
CREATE TABLE Numbers (Number int NOT NULL PRIMARY KEY)
INSERT INTO Numbers VALUES(1)
INSERT INTO Numbers VALUES(2)
INSERT INTO Numbers VALUES(3)
INSERT INTO Store
(Items, Cost, Price)
SELECT
t.Items, t.Cost
,CASE
WHEN n.Number=1 THEN t.Price
WHEN n.Number=2 THEN t.ActualCost
ELSE t.ActualPrice
END
FROM TempStore t
INNER JOIN Numbers N ON n.Number<=3
WHERE ...
you could even use a UNION:
INSERT INTO Store
(Items, Cost, Price)
SELECT
t.Items, t.Cost, t.Price
FROM TempStore t
UNION ALL
SELECT
t.Items, t.Cost, t.ActualCost
FROM TempStore t
UNION ALL
SELECT
t.Items, t.Cost, t.ActualPrice
FROM TempStore t
either the Numbers table or the UNION will we WAY better than a loop!
OK, I think KM has proposed an excellent solution involving a "numbers table". However, VoodooChild has requested in a comment that I post example code for my suggestion of using WHILE-BEGIN-END around an INSERT-SELECT.
I have created two tables like VoodooChild's Store and TempStore.
Store has columns StoreID, StoreName, StoreState, StoreNumber.
TempStore has columns TempStoreID, TempStoreName.
I prepopulated TempStoreName with the values First, Second, Third and Fourth.
Now, my SQL will insert three records into the Store table for every record in the TempStore table that meets the condition in the WHERE clause. That condition is the length of the TempStoreName, obviously not a real-world example.
DECLARE #counter int
SET #counter = 0;
WHILE #counter < 3
BEGIN
INSERT INTO Store (StoreName, StoreState, StoreNumber)
SELECT TempStoreName, 'AZ', #counter FROM TempStore WHERE LEN(TempStoreName) = 5
SET #counter = #counter + 1
END
The result of this when applied to an empty Store table is:
StoreID StoreName StoreState StoreNumber
1 First AZ 0
2 First AZ 1
3 First AZ 2
4 Third AZ 0
5 Third AZ 1
6 Third AZ 2
So, this approach works. It appears to meet VoodooChild's needs. It may or may not be the very best choice, but there are other factors involved in the decision that we don't know, such as how many times this operation is going to be repeated.
INSERT INTO Store ( SELECT * FROM TempStore UNION ALL SELECT * FROM TempStore )
The above statement will insert two rows in the store for each row in the TempStore. You can change the SELECT * to whatever modification that you want to do to the item.
Given your latest comment, this should give you what you need. You should probably have some way of differentiating the values in your Stores table once they get there. Perhaps an "actual" BIT column or something similar:
INSERT INTO Stores (item, cost, price, actual)
SELECT item, cost, price, 0
FROM TempStores
UNION ALL
SELECT item, actual_cost, actual_price, 1
FROM TempStores
If you needed to adjust the columns (for example, increase actual_price by 10%) then you could do this:
INSERT INTO Stores (item, cost, price, actual)
SELECT item, cost, price, 0
FROM TempStores
UNION ALL
SELECT item, actual_cost, 1.1 * actual_price, 1
FROM TempStores
WHERE actual_cost IS NOT NULL
I also added a WHERE clause to the second SELECT statement to show that you can filter the rows. That WHERE clause will only affect the second SELECT. So, you could also do this:
INSERT INTO Stores (item, cost, price, actual)
SELECT item, cost, price, 0
FROM TempStores
WHERE cost IS NOT NULL
UNION ALL
SELECT item, actual_cost, 1.1 * actual_price, 1
FROM TempStores
WHERE actual_cost IS NOT NULL