Split Row and Paste to Different Tables Based on Column - sql

I have a table like this. Table is populated each time an order is complete. One order can have one or many compartments.
+---------+-------+-------------+------+
| OrderID | Plant | Compartment | Qty |
+---------+-------+-------------+------+
| 91 | 12 | 1 | 2000 |
| 91 | 12 | 2 | 2000 |
| 91 | 12 | 3 | 2000 |
| 90 | 12 | 1 | 3000 |
| 89 | 12 | 1 | 5000 |
+---------+-------+-------------+------+
Please help write an SQL script that takes the above and splits it into two new tables like so:
Table 1
+---------+-------+
| OrderID | Plant |
+---------+-------+
| 91 | 12 |
| 90 | 12 |
| 89 | 12 |
+---------+-------+
Table 2
+---------+-------------+------+
| OrderID | Compartment | Qty |
+---------+-------------+------+
| 91 | 1 | 2000 |
| 91 | 2 | 2000 |
| 91 | 3 | 2000 |
| 90 | 1 | 3000 |
| 89 | 1 | 5000 |
+---------+-------------+------+
I've tried using the DISTINCT command as suggested;
SELECT * FROM table
WHERE [OrderID] = (SELECT DISTINCT OrderID from table where (COMPARTMENT = '1'))
Which returns the error;
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
If the script can keep track of already processed rows so as to avoid duplication each time it runs, that would be the icing on the cake.

This is how I'd do:
-- just for debugging, your table is my #table variable table
set nocount on
declare #table table (OrderId int, Plant int, Compartment int, Qty int)
insert into #table values (89, 12, 1, 5000)
insert into #table values (90, 12, 1, 3000)
insert into #table values (91, 12, 3, 2000)
insert into #table values (91, 12, 2, 2000)
insert into #table values (91, 12, 1, 2000)
insert into #table values (91, 12, 2, 3000)
set nocount off
--select * from #table
-- now here it comes selections:
if (exists (select *
from INFORMATION_SCHEMA.TABLES
where TABLE_SCHEMA = 'dbo' -- or your schema
and TABLE_NAME = 'THeader' -- or your header's table))
insert into THeader
select distinct t1.OrderId, t1.Plant
from #table t1
left join THeader t2 on t1.OrderId = t2.OrderId and t1.Plant = t2.Plant
where t2.OrderId is null
else
select distinct t1.OrderId, t1.Plant
into THeader
from #table t1
left join THeader t2 on t1.OrderId = t2.OrderId and t1.Plant = t2.Plant
where t2.OrderId is null
if (exists (select *
from INFORMATION_SCHEMA.TABLES
where TABLE_SCHEMA = 'dbo' -- or your schema
and TABLE_NAME = 'TChilds' -- or your childs's table))
insert into TChilds
select distinct t1.OrderId, t1.Compartment, t1.Qty
from #table t1
left join TChilds t2 on t1.OrderId = t2.OrderId and t1.Compartment = t2.Compartment and t1.Qty = t2.Qty
where t2.OrderId is null
else
select distinct t1.OrderId, t1.Compartment, t1.Qty
into TChilds
from #table t1
left join TChilds t2 on t1.OrderId = t2.OrderId and t1.Compartment = t2.Compartment and t1.Qty = t2.Qty
where t2.OrderId is null
-- just for debugging
select * from THeader
select * from TChilds
Edit: Even so, I see your master table as a child table, from where you must create Header's table. It's enough. I mean your Table can be my TChilds table, and key can be OrderId + Plant. (I don't know what means plant in this table)

Related

How to add items from another table based on a string aggregated column

I have 2 tables like this
[Table 1]:
|cust_id| tran |item |
| ------| -----|-------
| id1 | 123 |a,b,c |
| id2 | 234 |b,b |
| id3 | 345 |c,d,a,b|
[Table 2]:
| item. | value |
| ----- | ----- |
| a | 1 |
| b | 2 |
| c | 3 |
| d | 4 |
I want to create a target value by doing a lookup from table 2 in table 1 using big query.
|cust_id| tran.|item |target|
| ------| -----|------|------|
| id1 | 123 |a,b,c | 6
| id2 | 234 |b,b | 4
| id3 | 345 |c,d,a,b| 10
What can I try next?
Consider below simple approach
select *,
( select sum(value)
from unnest(split(item)) item
join table2
using (item)
) target
from table1
if applied to sample data in your question - output is
Try the following:
select t1.cust_id
, t1.tran
, t1.item
, sum(t2.value) as target
from table_1 t1
, UNNEST(split(t1.item ,',')) as item_unnested
LEFT JOIN table_2 t2
on item_unnested=t2.item
group by t1.cust_id
, t1.tran
, t1.item
With your data it gives the following:
Create a center table that splits the item column values on rows and join that table with table2.
Try following
--Cursor is used to split the item data row by row
--#temp is a temporary table
create table #temp (id varchar(10), trans varchar(10), item varchar(10), item1 varchar(10));
DECLARE #item varchar(10);
DECLARE #id varchar(10);
DECLARE #trans varchar(10);
DECLARE item_cusor CURSOR FOR
SELECT *
FROM table1;
OPEN item_cusor
FETCH NEXT FROM item_cusor
INTO #id,#trans,#item
WHILE ##FETCH_STATUS = 0
BEGIN
insert into #temp
SELECT #id,#trans,#item,*
FROM STRING_SPLIT (#item, ',')
FETCH NEXT FROM item_cusor
INTO #id,#trans,#item
END
CLOSE item_cusor;
DEALLOCATE item_cusor;
--select * from temp
select t.id as cust_id, t.trans,t.item , sum(cast(t2.value as int)) as target
from #temp t
JOIN table2 t2
on t.item1=t2.item
group by t.id, t.trans,t.item;
Cursors: https://www.c-sharpcorner.com/article/cursors-in-sql-server/
Temporary tables: https://www.sqlservertutorial.net/sql-server-basics/sql-server-temporary-tables/
String split function: https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql

Inventory balance from two tables

I have two tables in SQL Server, the first one for inventory and second for inventory movement. I need a query to show the remaining raw material for each serial number and if there's a movement or not.
Table 1
+----------+------------+-----+
| CodeRaw | Serial_Raw | Qty |
+----------+------------+-----+
| 1 | 1 | 100 |
| 1 | 2 | 150 |
| 2 | 1 | 80 |
| 1 | 3 | 100 |
| 1 | 4 | 100 |
+----------+------------+-----+
And Table 2
+------------+----------+------------+----------+--+
| CodeBatch | CodeRaw | Serial_Raw | Qty_Added| |
+------------+----------+------------+----------+--+
| 1 | 1 | 1 | 80 | |
| 2 | 1 | 1 | 10 | |
| 3 | 1 | 2 | 150 | |
| 4 | 1 | 3 | 80 | |
+------------+----------+------------+----------+--+
I've already tried some code but I just got results where there is an inventory movenent, not showing all QT for a specific raw (Code_Raw).
Probably I'm missing something....
This is the query I have
declare #tbl1 table (CodeRaw INT, Serial_Raw INT, Qty INT)
declare #tbl2 table (CodeBatch INT, CodeRaw INT, Serial_Raw INT, QtyAdded INT)
insert into #tbl1 values (1,1,100), (1,2,150), (2,1,80), (1,3,100),(1,4,100)
insert into #tbl2 values (1,1,1,80), (2,1,1,10), (3,1,2,150), (4,1,3,80)
SELECT t2.Serial_Raw, t1.Qty - t2.QtyAdded AS Total_Remaining FROM #tbl1 t1
INNER JOIN (SELECT CodeRaw, Serial_Raw , SUM(QtyAdded) QtyAdded FROM #tbl2
GROUP BY CodeRaw, Serial_Raw) AS t2 ON t2.CodeRaw = t1.CodeRaw AND t1.Serial_Raw = t2.Serial_Raw
WHERE t1.CodeRaw = 1
I expected
Serial_Raw Total_Remaining
---------- ---------------
1 10
2 0
3 20
4 100
But the result is
Serial_Raw Total_Remaining
---------- ---------------
1 10
2 0
3 20
Thanks in advance
I think your issue is your INNER JOIN. It is only returning results for your inventory that's in both tables. Meaning, if an item has no inventory movement, the total count is not returned.
Step 1: Switch your INNER JOIN to a LEFT OUTER JOIN. This will return all results in the inventory table (tbl1) even if it has no movement (tbl2). Also, select Serial_Raw from tbl1 instead of tbl2 in case of NULL value returned from JOIN.
Step 2: Step 1 will return NULL for the tbl2.QtyAdded in your JOIN. To account for this, you can do a NULL check in your calculation by using ISNULL(tbl2.QtyAdded, 0). Then, if there is not QtyAdded, the tbl1.Qty will subtract 0 (stay the same).
Resulting Code:
declare #tbl1 table (CodeRaw INT, Serial_Raw INT, Qty INT)
declare #tbl2 table (CodeBatch INT, CodeRaw INT, Serial_Raw INT, QtyAdded INT)
insert into #tbl1 values (1,1,100), (1,2,150), (2,1,80), (1,3,100),(1,4,100)
insert into #tbl2 values (1,1,1,80), (2,1,1,10), (3,1,2,150), (4,1,3,80)
SELECT t1.Serial_Raw, t1.Qty - ISNULL(t2.QtyAdded, 0) AS Total_Remaining FROM #tbl1 t1
LEFT OUTER JOIN (SELECT CodeRaw, Serial_Raw , SUM(QtyAdded) QtyAdded FROM #tbl2
GROUP BY CodeRaw, Serial_Raw) AS t2 ON t2.CodeRaw = t1.CodeRaw
AND t1.Serial_Raw = t2.Serial_Raw
WHERE t1.CodeRaw = 1
Results:
Serial_Raw Total_Remaining
-----------------------------
1 10
2 0
3 20
4 100
Use LEFT OUTER JOIN instead INNER JOIN, also ISNULL and serial_Raw from left table.
declare #tbl1 table (CodeRaw INT, Serial_Raw INT, Qty INT)
declare #tbl2 table (CodeBatch INT, CodeRaw INT, Serial_Raw INT, QtyAdded INT)
insert into #tbl1 values (1,1,100), (1,2,150), (2,1,80), (1,3,100),(1,4,100)
insert into #tbl2 values (1,1,1,80), (2,1,1,10), (3,1,2,150), (4,1,3,80)
SELECT t1.Serial_Raw, t1.Qty - ISNULL(t2.QtyAdded,0) AS Total_Remaining FROM #tbl1 t1
LEFT OUTER JOIN (SELECT CodeRaw, Serial_Raw , SUM(QtyAdded) QtyAdded FROM #tbl2
GROUP BY CodeRaw, Serial_Raw) AS t2
ON t2.CodeRaw = t1.CodeRaw AND t1.Serial_Raw = t2.Serial_Raw
WHERE t1.CodeRaw = 1
Result:
Serial_Raw Total_Remaining
----------- ---------------
1 10
2 0
3 20
4 100
You need to do 2 changes
Left join to the second table.
Check the second table's column is null, then set to 0 for minus
SELECT t1.Serial_Raw, t1.Qty - isnull(t2.QtyAdded, 0) AS Total_Remaining
FROM #tbl1 t1
Left Join
(SELECT CodeRaw, Serial_Raw, SUM(QtyAdded) QtyAdded FROM #tbl2 GROUP BY CodeRaw, Serial_Raw)
AS t2 ON t2.CodeRaw = t1.CodeRaw AND t1.Serial_Raw = t2.Serial_Raw
Where t1.CodeRaw = 1

Get records having the same value in 2 columns but a different value in a 3rd column

I am having trouble writing a query that will return all records where 2 columns have the same value but a different value in a 3rd column. I am looking for the records where the Item_Type and Location_ID are the same, but the Sub_Location_ID is different.
The table looks like this:
+---------+-----------+-------------+-----------------+
| Item_ID | Item_Type | Location_ID | Sub_Location_ID |
+---------+-----------+-------------+-----------------+
| 1 | 00001 | 20 | 78 |
| 2 | 00001 | 110 | 124 |
| 3 | 00001 | 110 | 124 |
| 4 | 00002 | 3 | 18 |
| 5 | 00002 | 3 | 25 |
+---------+-----------+-------------+-----------------+
The result I am trying to get would look like this:
+---------+-----------+-------------+-----------------+
| Item_ID | Item_Type | Location_ID | Sub_Location_ID |
+---------+-----------+-------------+-----------------+
| 4 | 00002 | 3 | 18 |
| 5 | 00002 | 3 | 25 |
+---------+-----------+-------------+-----------------+
I have been trying to use the following query:
SELECT *
FROM Table1
WHERE Item_Type IN (
SELECT Item_Type
FROM Table1
GROUP BY Item_Type
HAVING COUNT (DISTINCT Sub_Location_ID) > 1
)
But it returns all records with the same Item_Type and a different Sub_Location_ID, not all records with the same Item_Type AND Location_ID but a different Sub_Location_ID.
This should do the trick...
-- some test data...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
BEGIN DROP TABLE #TestData; END;
CREATE TABLE #TestData (
Item_ID INT NOT NULL PRIMARY KEY,
Item_Type CHAR(5) NOT NULL,
Location_ID INT NOT NULL,
Sub_Location_ID INT NOT NULL
);
INSERT #TestData (Item_ID, Item_Type, Location_ID, Sub_Location_ID) VALUES
(1, '00001', 20, 78),
(2, '00001', 110, 124),
(3, '00001', 110, 124),
(4, '00002', 3, 18),
(5, '00002', 3, 25);
-- adding a covering index will eliminate the sort operation...
CREATE NONCLUSTERED INDEX ix_indexname ON #TestData (Item_Type, Location_ID, Sub_Location_ID, Item_ID);
-- the actual solution...
WITH
cte_count_group AS (
SELECT
td.Item_ID,
td.Item_Type,
td.Location_ID,
td.Sub_Location_ID,
cnt_grp_2 = COUNT(1) OVER (PARTITION BY td.Item_Type, td.Location_ID),
cnt_grp_3 = COUNT(1) OVER (PARTITION BY td.Item_Type, td.Location_ID, td.Sub_Location_ID)
FROM
#TestData td
)
SELECT
cg.Item_ID,
cg.Item_Type,
cg.Location_ID,
cg.Sub_Location_ID
FROM
cte_count_group cg
WHERE
cg.cnt_grp_2 > 1
AND cg.cnt_grp_3 < cg.cnt_grp_2;
You can use exists :
select t.*
from table t
where exists (select 1
from table t1
where t.Item_Type = t1.Item_Type and
t.Location_ID = t1.Location_ID and
t.Sub_Location_ID <> t1.Sub_Location_ID
);
Sql server has no vector IN so you can emulate it with a little trick. Assuming '#' is illegal char for Item_Type
SELECT *
FROM Table1
WHERE Item_Type+'#'+Cast(Location_ID as varchar(20)) IN (
SELECT Item_Type+'#'+Cast(Location_ID as varchar(20))
FROM Table1
GROUP BY Item_Type, Location_ID
HAVING COUNT (DISTINCT Sub_Location_ID) > 1
);
The downsize is the expression in WHERE is non-sargable
I think you can use exists:
select t1.*
from table1 t1
where exists (select 1
from table1 tt1
where tt1.Item_Type = t1.Item_Type and
tt1.Location_ID = t1.Location_ID and
tt1.Sub_Location_ID <> t1.Sub_Location_ID
);

Comparing rows in a sql table

I have a table which tracks changes to an entity, and I'm trying to extract the changes. The structure is more or less this:
| RowNumber | Value | SourceID| TargetID |
| 1 | A | 100 | 50 |
| 2 | B | 100 | 100 |
| 3 | C | 200 | 100 |
My select is
select t1.Value as Old, t2.Value as New from MyTable t1
inner join MyTable t2 on t1.SourceID = t2.TargetID
where t1.value != t2.value
Which gives me :
|Old|New|
|A | B |
|A | C |
|B | C |
The problem is, the data was changed from A->B, then from B->C. It never actually changed from A->C and I can't for the life of me find a way of doing this in one query, I realise that a cursor could achieve this going through the rows in order.
Is this possible in one query?
You can use the ROW_NUMBER window function to find the first next one.
Example:
declare #MyTable table (RowNumber int primary key identity(1,1), [Value] varchar(30), SourceID int, TargetID int);
insert into #MyTable ([Value], SourceID, TargetID) values
('A', 100, 50),
('B', 100, 100),
('C', 200, 100);
SELECT Old, New
FROM
(
select
t1.[Value] as Old,
t2.[Value] as New,
row_number() over (partition by t1.RowNumber order by t2.RowNumber) as RN
from #MyTable t1
join #MyTable t2
on t2.TargetID = t1.SourceID AND t2.RowNumber > t1.RowNumber
) q
WHERE RN = 1;
Returns:
Old New
A B
B C

Join returns too much info

I have 2 tables
Table1
+---------+--------+-------+----+
| CALDATE | GROOMS | ROOMS | fn |
+---------+--------+-------+----+
| 1/5/18 | 15 | 17 | A12|
| 1/5/18 | 0 | 0 | A12|
| 1/6/18 | 0 | 0 | B34|
| 1/6/18 | 75 | 77 | B34|
| 1/7/18 | 123 | 125 | C56|
| 1/7/18 | 0 | 0 | C56|
+---------+--------+-------+----+
-
Table2
+----------+--------+----+
| ROOMDATE | pickup | FN |
+----------+--------+----+
| 1/5/18 | 0 | A12|
| 1/5/18 | 2 | A12|
| 1/5/18 | 1 | A12|
| 1/5/18 | 7 | A12|
| 1/6/18 | 2 | B34|
| 1/6/18 | 1 | B34|
| 1/6/18 | 13 | B34|
| 1/7/18 | 3 | C56|
| 1/7/18 | 0 | C56|
| 1/7/18 | 12 | C56|
+----------+--------+----+
Querying each I use
Select caldate as date, sum(grooms) as g, sum (rooms) as r
from Table1
and
Select roomdate as date, sum(pickup) as p
from Table2
These each give me the info I'm expecting, however when I try and join them things get wonky. I was hoping for something like
Select caldate as date,
sum(grooms) as g,
sum(rooms) as r,
sum(pickup) as p
from Table1
inner join table2 on table1.fn = table2.fn
But that returns way too high of each.
How do I join these queries so that I get my expected output of
+--------+-----+-----+----+----+
| Date | g | r | p | fn |
+--------+-----+-----+----+----+
| 1/5/18 | 15 | 17 | 10 | A12|
| 1/6/18 | 75 | 77 | 16 | B34|
| 1/7/18 | 123 | 125 | 15 | C56|
+--------+-----+-----+----+----+
Each row in your first table will match with each available row in the other table based on your join predicate. Take fn = A12 for example: since you have 2 rows in table1 and 4 rows in table2, you will end up with (4x2) 8 rows in your result set. That will cause your sums to be higher than they should be.
One way to fix this is to use derived tables to get your sums, then join them together:
SELECT t1.date, g, r, p, t1.fn
FROM (SELECT fn, caldate as date, sum(grooms) as g, sum (rooms) as r
FROM Table1
GROUP BY fn, caldate) t1
JOIN (SELECT fn, roomdate as date, sum(pickup) as p
FROM Table2
GROUP BY fn, roomdate) t2 on t1.fn = t2.fn
This makes sure there is one row returned from each table before the join.
You should be Grouping by 'caldate'. This way you will only be getting the sums per date.
Select caldate as date,
sum(grooms) as g,
sum(rooms) as r,
sum(pickup) as p
from Table1
inner join table2 on table1.fn = table2.fn
group by caldate
The reason that you get more rows than expected is because of join condition. if you want to find grooms and rooms for each fn on each day you have add date to join condition as well:
Select table1.caldate as date,
sum(grooms) as g,
sum(rooms) as r,
sum(pickup) as p
from table1
inner join table2 on table1.fn = table2.fn
and table1.caldate = table2.roomdate
Here's an answer that allows you to group by FN and date:
select format(caldate, 'M/d/yyyy') as date, sum(grooms) as g, sum(rooms) as r, t2.p, t1.fn
from Table1 t1
inner join (
select fn, roomdate, sum(pickup) as p from Table2 group by fn, roomdate
)t2 on t1.fn = t2.fn and t1.caldate = t2.roomdate
group by t1.caldate, t1.fn, t2.p
I created some sample data with DML statements so you can test grouping by different combinations of FN and date, and the output:
declare #t1 table (caldate datetime, grooms int, rooms int, fn varchar(3))
declare #t2 table (roomdate datetime, pickup int, fn varchar(3))
insert into #t1 select '1/5/18', 15, 17,'A12'
insert into #t1 select '1/5/18', 0, 0,'A12'
insert into #t1 select '1/6/18', 0, 0,'B34'
insert into #t1 select '1/6/18', 75, 77,'B34'
insert into #t1 select '1/7/18',123,125,'C56'
insert into #t1 select '1/8/18',100,200,'C56' -- changed to 1/8/18, changed vals
insert into #t2 select '1/5/18', 0 ,'A12'
insert into #t2 select '1/5/18', 2 ,'A12'
insert into #t2 select '1/5/18', 1 ,'A12'
insert into #t2 select '1/5/18', 7 ,'A12'
insert into #t2 select '1/6/18', 2 ,'B34'
insert into #t2 select '1/6/18', 1 ,'B34'
insert into #t2 select '1/6/18',13 ,'B34'
insert into #t2 select '1/7/18', 3 ,'C56'
insert into #t2 select '1/7/18', 0 ,'C56'
insert into #t2 select '1/8/18',12 ,'C56' -- changed to 1/8/18
select format(caldate, 'M/d/yyyy') as date, sum(grooms) as g, sum(rooms) as r, t2.p, t1.fn
from #t1 t1
inner join (
select fn, roomdate, sum(pickup) as p from #t2 group by fn, roomdate
)t2 on t1.fn = t2.fn and t1.caldate = t2.roomdate
group by t1.caldate, t1.fn, t2.p
Output:
date g r p fn
1/5/2018 15 17 10 A12
1/6/2018 75 77 16 B34
1/7/2018 123 125 3 C56
1/8/2018 100 200 12 C56
If you don't also join by date, then adding different combinations of caldate/fn give you duplicates. You must mean to join by the date as well, right?