SQL Server : split row into multiple rows based on a column value - sql

I have a question regarding splitting rows based on column value
My example data set is :
id ExpenseType Price
------------------------
1 Car 100
2 Hotel 50
I want to split rows those have some Expense Types such as Car into two rows . Others should remain as one row.
First row Price *70
Second Row Price *30
Returned dataset should be
id ExpenseType Price
-----------------------
1 Car 70
1 Car 30
2 Hotel 50
Thanks for your answers in advance

If you want to split more expense types than car you could use:
WITH r AS (
SELECT 'Car' AS ExpenseType, 0.7 AS Ratio
UNION SELECT 'Car' AS ExpenseType, 0.3 AS Ratio
-- add more ExpenseTypes/Ratios here
)
SELECT
t.id,
t.ExpenseType,
t.Price * ISNULL(r.Ratio, 1.0) AS Price
FROM
your_table t
LEFT OUTER JOIN r ON t.ExpenseType = r.ExpenseType

A simple way uses union all:
select id, expensetype, price
from t
where expensetype <> 'Car'
union all
select id, expensetype, price * 0.7
from t
where expensetype = 'Car'
union all
select id, expensetype, price * 0.3
from t
where expensetype = 'Car';
This is not the most efficient method. For that, a cross apply with filtering logic is better:
select t.id, v.*
from t cross apply
(values (NULL, price), ('Car', price * 0.3), ('Car', price * 0.7)
) v(expensetype, price)
where v.expensetype = t.expense_type or
v.expensetype <> 'Car' and t.expense_type is null;

A less simple way is to use an OUTER APPLY
CREATE TABLE YourSampleData
(
Id INT IDENTITY(1,1) PRIMARY KEY,
ExpenseType VARCHAR(30) NOT NULL,
Price INT NOT NULL DEFAULT 0
);
INSERT INTO YourSampleData
(ExpenseType, Price) VALUES
('Car', 100)
,('Hotel', 50)
,('Gold', 1)
;
SELECT Id, ExpenseType
, COALESCE(a.Price, t.Price) AS Price
FROM YourSampleData t
OUTER APPLY
(
SELECT Price * Perc AS Price
FROM (VALUES
('Car',0.3E0), ('Car',0.7E0)
,('Gold',1.618E0)
) AS v(ExpType, Perc)
WHERE t.ExpenseType = v.ExpType
) a
GO
Id | ExpenseType | Price
-: | :---------- | ----:
1 | Car | 30
1 | Car | 70
2 | Hotel | 50
3 | Gold | 1.618
db<>fiddle here

I ran into a similar need, here is my solution.
Problem statement:
My organization is switching from an in-house build system to a third-party system. Numerical values in the original system surpassed the value size that the destination system could handle. The third-party system will not allow us to increase the field size, as a result we need to split the data up into values that do not surpass the field size limit.
Details:
Destination system can only support values under 1 billion (can include a negative sign)
Example:
DROP TABLE IF EXISTS #MyDemoData /* Generate some fake data for the demo */
SELECT item_no = 1, item_description = 'zero asset', amount = 0 INTO #MyDemoData
UNION SELECT item_no = 2, item_description = 'small asset', amount = 5100000
UNION SELECT item_no = 3, item_description = 'mid asset', amount = 510000000
UNION SELECT item_no = 4, item_description = 'large asset', amount = 5100000000
UNION SELECT item_no = 5, item_description = 'large debt', amount = -2999999999.99
SELECT * FROM #MyDemoData
DECLARE #limit_size INT = 1000000000
DROP TABLE IF EXISTS #groupings;
WITH
max_groups AS
(
SELECT max_groups=100
)
,groups AS
(
SELECT 1 AS [group]
UNION ALL
SELECT [group]+1
FROM groups
JOIN max_groups ON 1=1
WHERE [group]+1<=max_groups
)
,group_rows AS
(
SELECT 0 AS [row]
UNION ALL
SELECT [row]+1
FROM group_rows
JOIN max_groups ON 1=1
WHERE [row]+1<=max_groups
)
,groupings AS
(
SELECT [group],[row]
FROM group_rows
CROSS JOIN groups
WHERE [row] <= [group]
)
SELECT * INTO #groupings FROM groupings;
WITH /* Split out items that are under the limit and over the limit */
t1 AS /* Identify rows that are over the limit and by how many multiples over it is */
(
SELECT
item_no
, item_description
, amount
, over_limit = FLOOR(ABS(amount/#limit_size))
FROM #MyDemoData
)
SELECT /* select the items that are under the limit and do not need manipulated */
item_no
, item_description
, amount = CAST(amount AS DECIMAL(16,2))
FROM t1
WHERE ABS([amount]) < #limit_size
UNION ALL /* select the items that are over the limit, join on the groupings cte and calculate the split amounts */
SELECT
item_no
, item_description
, [Amount] = CAST(
CASE
WHEN row != 0 THEN (#limit_size-1) * ([amount]/ABS([amount]))
ELSE (ABS([amount]) - (t1.over_limit * #limit_size) + t1.over_limit) * ([amount]/ABS([amount]))
END AS DECIMAL(16,2))
FROM t1
JOIN #groupings bg ON t1.over_limit = bg.[group]
WHERE ABS([amount]) >= #limit_size
ORDER BY item_no

Related

Value need from range, based on Priority in SQL ,

There is 3 cases: (In all cases value needs to be picked up based on priority)
case 1 : zip exist between range
case 2: zip does not exist between range
case 3 : overlap range
Table
Temp1
state
zip_start
zip_end
Priority
Value
NY
100
200
1
A
NY
150
250
3
c
NY
null
null
2
B
Data
state
zip
NY
201
NY
400
OUTPUT :
state
zip_start
zip_end
Priority
Value
zip
NY
null
null
2
B
201
NY
null
null
2
B
400
I am trying with below code , but It's not picking the data based on priority:
SELECT ZIP,ZIP_START,ZIP_END,VALUE,PRIORITY,STATE,IX FROM
(
SELECT TMP1.*,
ROW_NUMBER () OVER (PARTITION BY STATE,ZIP ORDER BY PRIORITY ) IX
FROM
(
WITH CASE_1 AS
( SELECT
temp1.*
,DATA.ZIP
FROM TEMP1
LEFT JOIN
"DATA" ON DATA.STATE = temp1.STATE
WHERE DATA.ZIP BETWEEN TEMP1.ZIP_START AND TEMP1 .ZIP_END
),
CASE_2 AS
(
SELECT
temp1.*
,DATA.ZIP
FROM "DATA"
LEFT JOIN
TEMP1 ON DATA.STATE = temp1.STATE
WHERE (ZIP_START IS NULL OR ZIP_START = '')
AND (ZIP_END IS NULL OR ZIP_END = '')
AND Not EXISTS
(SELECT 1 FROM CASE_1 WHERE CASE_1.zip=DATA.zip
AND CASE_1.STATE=DATA.STATE)
)
SELECT * FROM CASE_1
UNION
SELECT * FROM CASE_2
)TMP1
) TMP2
WHERE TMP2.IX = 1;
From Oracle 12, you can use a LATERAL join and filter when the zip is within range or when one-or-other end of the range is NULL the ORDER BY priority and FETCH the FIRST matched ROW ONLY:
SELECT t.*, d.zip
FROM data d
CROSS JOIN LATERAL (
SELECT *
FROM temp1 t
WHERE d.state = t.state
AND (t.zip_start <= d.zip OR t.zip_start IS NULL)
AND (t.zip_end >= d.zip OR t.zip_end IS NULL)
ORDER BY priority
FETCH FIRST ROW ONLY
) t
In earlier versions, you can join the two tables and then use the ROW_NUMBER analytic function to find the best match:
SELECT state, zip_start, zip_end, priority, value, zip
FROM (
SELECT t.*,
d.zip,
ROW_NUMBER() OVER (PARTITION BY d.ROWID ORDER BY t.priority) AS rn
FROM data d
INNER JOIN temp1 t
ON ( d.state = t.state
AND (t.zip_start <= d.zip OR t.zip_start IS NULL)
AND (t.zip_end >= d.zip OR t.zip_end IS NULL))
)
WHERE rn = 1;
Which, for the sample data:
CREATE TABLE Temp1 (state, zip_start, zip_end, Priority, Value) AS
SELECT 'NY', 100, 200, 1, 'A' FROM DUAL UNION ALL
SELECT 'NY', 150, 250, 3, 'c' FROM DUAL UNION ALL
SELECT 'NY', null, null, 2, 'B' FROM DUAL;
CREATE TABLE Data (state, zip) AS
SELECT 'NY', 201 FROM DUAL UNION ALL
SELECT 'NY', 400 FROM DUAL;
Both output:
STATE
ZIP_START
ZIP_END
PRIORITY
VALUE
ZIP
NY
null
null
2
B
201
NY
null
null
2
B
400
db<>fiddle here
CREATE TABLE TEMP1 (
STATE VARCHAR(10),
ZIP_START NUMBER,
ZIP_END NUMBER,
PRIORITY NUMBER,
VAL VARCHAR(10));
INSERT INTO TEMP1 VALUES ('NY', 100,200,1,'A');
INSERT INTO TEMP1 VALUES ('NY', 150,250,3,'C');
INSERT INTO TEMP1 VALUES ('NY', null,null,2,'B');
CREATE TABLE DATATABLE (
STATE VARCHAR(10),
ZIP NUMBER
);
INSERT INTO DATATABLE VALUES ('NY', 201);
INSERT INTO DATATABLE VALUES ('NY', 400);
The main idea is to figure out first how many times your condition (zip in range between zip_start and end) is met. This is why we use count_match variable.
Once you get if your data is priority 1,2 or 3, you match your data table with the temp table to get the value associated with that priority.
SELECT
t0.STATE,
t0.ZIP_START,
t0.ZIP_END,
CASE WHEN t0.COUNT_MATCH > 1 THEN 3
WHEN t0.COUNT_MATCH = 1 THEN 1
WHEN t0.COUNT_MATCH = 0 THEN 2 END AS PRIORITY,
t.VAL,
t0.ZIP
FROM
(
SELECT
t1.STATE,
MIN(t2.ZIP_START) AS ZIP_START,
MAX(t2.ZIP_END) AS ZIP_END,
COUNT(t2.STATE) AS COUNT_MATCH,
t1.ZIP
FROM DATATABLE t1
LEFT JOIN TEMP1 t2 ON (t1.STATE = t2.STATE AND t1.ZIP>=t2.ZIP_START AND t1.ZIP <= t2.ZIP_END)
GROUP BY
t1.STATE, t1.ZIP) t0
LEFT JOIN TEMP1 t ON (t0.STATE = t.STATE AND CASE WHEN t0.COUNT_MATCH > 1 THEN 3
WHEN t0.COUNT_MATCH = 1 THEN 1
WHEN t0.COUNT_MATCH = 0 THEN 2 END = t.PRIORITY)
;

How do I partially share param values across union selects using different constraints?

I have a view
create or replace view v_collected as
select car.id_car
,car.state_car
,[..]
from cars car
,garages garage
,[..]
where [..]
Which is at least unique for car.id
Now I want to union 3 different queries into a second view, which takes user set parameters:
select 0 as score
,p0.*
from v_collected p0
where 1 = 1
-- User IO Binding
and p0.car = 'Audi'
and p0.garage = 'P01'
and p0.state_car = 'Ok'
union
select 1 as score
,p1.*
from v_collected p1
where 1 = 1
-- Should access the identical binding
and p1.car <> p0.car
and p1.state_car = p0.state_car
union
select 2 as score
,p2.*
from v_collected p2
where 1 = 1
-- Should access the identical binding
and p2.state_car = p0.state_car
The above does not work as the 2nd query has no access to the the 1st one. As the constraints change, I believe I cannot use a CTE. What are my options?
Since the requirements are vague, I can't say for sure that the following solution is complete, however, I would look into breaking the p0, p1, p2 into with clause sub queries that way you can use p0 in p1 and p2. For example:
with cars as
(
select 2 id_car, 'Ford' car, 'Ok' state_car from dual union
select 1 id_car, 'Audi' car, 'Ok' state_car from dual
)
, garages as
(
select 2 id_car, 'P02' garage from dual union
select 1 id_car, 'P01' garage from dual
)
, v_collected as
(
select car.id_car
,car.car
,car.state_car
,garage.garage
from cars car
,garages garage
where 1=1
and car.id_car = garage.id_car
)
-- select * from v_collected;
, p0_subquery as
(
select 0 as score
,p0.*
from v_collected p0
where 1 = 1
-- User IO Binding
and p0.car = 'Audi'
and p0.garage = 'P01'
and p0.state_car = 'Ok'
)
--select * from p0_subquery;
, p1_subquery as
(
select 1 as score
,p1.*
from v_collected p1
, p0_subquery p0
where 1 = 1
-- Should access the identical binding
and p1.car <> p0.car
and p1.state_car = p0.state_car
)
, p2_subquery as
(
select 2 as score
,p2.*
from v_collected p2
, p0_subquery p0
where 1 = 1
-- Should access the identical binding
and p2.state_car = p0.state_car
)
select * from p0_subquery
union
select * from p1_subquery
union
select * from p2_subquery
;

SQL reporting query

I have a database with following structure.
CREATE TABLE Party
(
PartyID INT IDENTITY
PRIMARY KEY ,
StatusID INT ,
Weigth INT ,
OldWeigth INT
);
GO
CREATE TABLE PartyLocation
(
PartyLocationID INT IDENTITY
PRIMARY KEY ,
PartyID INT FOREIGN KEY REFERENCES dbo.Party ( PartyID ) ,
LocationID INT ,
Distance INT
);
GO
CREATE TABLE PartyRole
(
PartyRoleID INT IDENTITY
PRIMARY KEY ,
PartyID INT FOREIGN KEY REFERENCES dbo.Party ( PartyID ) ,
RoleID INT
);
with some simple data.
INSERT INTO dbo.Party
( StatusID, Weigth, OldWeigth )
VALUES ( 1, -- StatusID - int
10, -- Age - int
20 -- OldAge - int
),
( 1, 15, 25 ),
( 2, 20, 30 );
INSERT INTO dbo.PartyLocation
( PartyID, LocationID, Distance )
VALUES ( 1, -- PartyID - int
1, -- LocationID - int
100 -- Distance - int
),
( 1, 2, 200 ),
( 1, 3, 300 ),
( 2, 1, 1000 ),
( 2, 2, 2000 ),
( 3, 1, 10000 );
INSERT INTO dbo.PartyRole
( PartyID, RoleID )
VALUES ( 1, -- PartyID - int
1 -- RoleID - int
),
( 1, 2 ),
( 1, 3 ),
( 2, 1 ),
( 2, 2 ),
( 3, 1 );
I want to query the following information
Return sum of Weigth of all parties that has roleID = 1 in PartyRole table
Return sum of OldWeigth of all parties that has statusID = 2
Return sum of distances of all parties that has locationID = 3
Return sum of distances of all parties that has roleID = 2
So the expected results are
FilteredWeigth FilteredOldWeigth FilteredDistance AnotherFilteredDistance
-------------- ----------------- ---------------- -----------------------
45 30 600 3600
Can we write a query that will query each table just once? If no what will be the most optimal way to query the data?
You can try this.
SELECT
FilteredWeigth = SUM(CASE WHEN RoleID = 1 AND RN_P = 1 THEN Weigth END) ,
FilteredOldWeigth = SUM(CASE WHEN StatusID = 2 AND RN_P = 1 THEN OldWeigth END),
FilteredDistance = SUM(CASE WHEN LocationID = 3 AND RN_L = 1 THEN Distance END),
AnotherFilteredDistance = SUM(CASE WHEN RoleID = 2 THEN Distance END)
FROM (
SELECT P.Weigth, P.StatusID, P.OldWeigth, PL.LocationID, PL.Distance, PR.RoleID,
RN_P = ROW_NUMBER() OVER (PARTITION BY P.PartyID ORDER BY PL.PartyLocationID),
RN_L = ROW_NUMBER() OVER (PARTITION BY PL.LocationID ORDER BY PR.PartyRoleID)
FROM Party P
INNER JOIN PartyLocation PL ON P.PartyID = PL.PartyID
INNER JOIN PartyRole PR ON P.PartyID = PR.PartyID
) AS T
the below gives
45 20 300 3600
the third column gives 300 which does not correspond to your expected result.
with q1
as
(
select sum(weigth) FilteredWeigth
from party join partyrole on party.partyid = partyrole.partyid
where partyrole.RoleID = '1'
),
q2 as
(
select sum(weigth) OldWeigth from party where StatusID = '2'
),
q3 as (
select sum(Distance) FilteredDistance
from party join PartyLocation on party.partyid = PartyLocation.partyid
where PartyLocation.locationID = '3'
),
q4 as
(
select sum(Distance) AnotherFilteredDistance
from party join partyrole on party.partyid = partyrole.partyid
join PartyLocation on party.partyid = PartyLocation.partyid
where partyrole.RoleID = '2'
)
select FilteredWeigth,OldWeigth,FilteredDistance,AnotherFilteredDistance
from q1,q2,q3,q4
When Using Individual Queries, you can achieve this using the following
Return sum of Weight of all parties that has roleID = 1 in PartyRole table
SELECT
SUM(Weight) FilteredWeigth
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 1
)
Return sum of OldWeigth of all parties that has statusID = 2
SELECT
SUM(OldWeigth) FilteredOldWeigth
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 2
)
Return sum of distances of all parties that has locationID = 3
SELECT
SUM(Distance) FilteredDistance
FROM dbo.PartyLocation
WHERE LocationID = 3
Return sum of distances of all parties that has roleID = 2
SELECT SUM(Distance) FROM PartyLocation PL
WHERE EXISTS
(
SELECT 1 FROM PartyRole PR
WHERE PR.PartyID = PL.PartyID
AND PR.Roleid = 2
)
If you want to get the result of all these in a single result set. then maybe you can try a pivot query. Like this
WITH CTE
AS
(
SELECT
'FilteredWeigth' ColNm,
SUM(Weigth) Val
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 1
)
UNION
SELECT
'FilteredOldWeigth' ColNm,
SUM(OldWeigth) Val
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 2
)
UNION
SELECT
'FilteredDistance' ColNm,
SUM(Distance) Val
FROM dbo.PartyLocation
WHERE LocationID = 3
UNION
SELECT
'AnotherFilteredDistance' ColNm,
SUM(Distance) Val FROM PartyLocation PL
WHERE EXISTS
(
SELECT 1 FROM PartyRole PR
WHERE PR.PartyID = PL.PartyID
AND PR.Roleid = 2
)
)
SELECT
*
FROM CTE
PIVOT
(
SUM(Val)
FOR ColNm IN
(
[FilteredWeigth],[FilteredOldWeigth],[FilteredDistance],[AnotherFilteredDistance]
)
)Pvt
The Result Will be
I could think of only three possible options:
Union query with four different select statements as answered by #ab-bennett
Join all tables then use select statements as answered by sarslan
Mix of 1 and 2, based on experiments
Coming to the question you asked:
Can we write a query that will query each table just once?
Assuming best performance is the goal, following could happen in each of the above cases:
All select statements would have their own where clause. This would perform best when where produces few rows compared to the count(*). Note that Joins are terrible for very large tables.
A join is made once, and the desired output is obtained from the same Joined table. This would perform optimal when where produces significant number of rows and the table is not too big to join.
You can mix JOIN / IN / EXISTS / WHERE to optimize your queries based on number of rows you are having in table. This approach could be used when your dataset cardinality might not vary a lot.

SQL Running Subtraction

I have a result set as below:
Item ExpectedQty ReceivedQty Short
Item01 30 45 5
Item01 20 45 5
Item02 40 38 2
item03 50 90 10
item03 30 90 10
item03 20 90 10
query is:
select a.Item, a.ExpectedQty,b.ReceivedQty, b.Short
from a join b on a.Item = b.Item
I need to get result as in second chart. Basically I have a total of received quantity in each line and I need to show received quantity against Expected Quantity, if there is any shortage I need to show in last line.
Expected:
Item ExpectedQty ReceivedQty Short
item01 30 30 0
item01 20 15 5
item02 40 38 2
item03 50 50 0
item03 30 30 0
item03 20 10 10
Thanks in advance.
Edited,
Vession 02 ;
-- Just a brief of business scenario is table has been created for a good receipt.
-- So here we have good expected line with PurchaseOrder(PO) in first few line.
-- And then we receive each expected line physically and that time these
-- quantity may be different
-- due to business case like quantity may damage and short quantity like that.
-- So we maintain a status for that eg: OK, Damage, also we have to calculate
-- short quantity
-- based on total of expected quantity of each item and total of received line.
if object_id('DEV..Temp','U') is not null
drop table Temp
CREATE TABLE Temp
(
ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
Item VARCHAR(32),
PO VARCHAR(32) NULL,
ExpectedQty INT NULL,
ReceivedQty INT NULL,
[STATUS] VARCHAR(32) NULL,
BoxName VARCHAR(32) NULL
)
-- Please see first few line with PO data will be the expected lines,
-- and then rest line will be received line
INSERT INTO TEMP (Item,PO,ExpectedQty,ReceivedQty,[STATUS],BoxName)
SELECT 'ITEM01','PO-01','30',NULL,NULL,NULL UNION ALL
SELECT 'ITEM01','PO-02','20',NULL,NULL,NULL UNION ALL
SELECT 'ITEM02','PO-01','40',NULL,NULL,NULL UNION ALL
SELECT 'ITEM03','PO-01','50',NULL,NULL,NULL UNION ALL
SELECT 'ITEM03','PO-02','30',NULL,NULL,NULL UNION ALL
SELECT 'ITEM03','PO-03','20',NULL,NULL,NULL UNION ALL
SELECT 'ITEM04','PO-01','30',NULL,NULL,NULL UNION ALL
SELECT 'ITEM01',NULL,NULL,'20','OK','box01' UNION ALL
SELECT 'ITEM01',NULL,NULL,'25','OK','box02' UNION ALL
SELECT 'ITEM01',NULL,NULL,'5','DAMAGE','box03' UNION ALL
SELECT 'ITEM02',NULL,NULL,'38','OK','box04' UNION ALL
SELECT 'ITEM02',NULL,NULL,'2','DAMAGE','box05' UNION ALL
SELECT 'ITEM03',NULL,NULL,'30','OK','box06' UNION ALL
SELECT 'ITEM03',NULL,NULL,'30','OK','box07' UNION ALL
SELECT 'ITEM03',NULL,NULL,'30','OK','box08' UNION ALL
SELECT 'ITEM03',NULL,NULL,'10','DAMAGE','box09' UNION ALL
SELECT 'ITEM04',NULL,NULL,'25','OK','box10'
-- Below Table is my expected result based on above data.
-- I need to show those data following way.
-- So I appreciate if you can give me an appropriate query for it.
-- Note: first row is blank and it is actually my table header. :)
SELECT ''as'ITEM', ''as'PO#', ''as'ExpectedQty',''as'ReceivedQty',
''as'DamageQty' ,''as'ShortQty' UNION ALL
SELECT 'ITEM01','PO-01','30','30','0' ,'0' UNION ALL
SELECT 'ITEM01','PO-02','20','15','5' ,'0' UNION ALL
SELECT 'ITEM02','PO-01','40','38','2' ,'0' UNION ALL
SELECT 'ITEM03','PO-01','50','50','0' ,'0' UNION ALL
SELECT 'ITEM03','PO-02','30','30','0' ,'0' UNION ALL
SELECT 'ITEM03','PO-03','20','10','10','0' UNION ALL
SELECT 'ITEM04','PO-01','30','25','0' ,'5'
One part of the problem is to get the running totals of expected item qunatities. For that you'd need a way to distinguish rows with same items from each other and a rule for the order of discharging same item quantities.
For the purpose of my attempt at solving your problem I'm going to assume there's a timestamp column whose values provide the order of discharge and are unique within same item groups.
Here's the sample data definition I was testing my solution on:
CREATE TABLE TableA (Item varchar(50), ExpectedQty int, Timestamp int);
INSERT INTO TableA
SELECT 'Item01', 30, 1 UNION ALL
SELECT 'Item01', 20, 2 UNION ALL
SELECT 'Item02', 40, 1 UNION ALL
SELECT 'item03', 50, 1 UNION ALL
SELECT 'item03', 30, 2 UNION ALL
SELECT 'item03', 20, 3;
CREATE TABLE TableB (Item varchar(50), ReceivedQty int);
INSERT INTO TableB
SELECT 'Item01', 45 UNION ALL
SELECT 'Item02', 38 UNION ALL
SELECT 'item03', 90;
And here's my solution:
SELECT
Item,
ExpectedQty,
ReceivedQty = CASE
WHEN RemainderQty >= 0 THEN ExpectedQty
WHEN RemainderQty < -ExpectedQty THEN 0
ELSE RemainderQty + ExpectedQty
END,
Short = CASE
WHEN RemainderQty >= 0 THEN 0
WHEN RemainderQty < -ExpectedQty THEN ExpectedQty
ELSE ABS(RemainderQty)
END
FROM (
SELECT
a.Item,
a.ExpectedQty,
RemainderQty = b.ReceivedQty - a.RunningTotalQty
FROM (
SELECT
a.Item,
a.Timestamp,
a.ExpectedQty,
RunningTotalQty = SUM(a2.ExpectedQty)
FROM TableA a
INNER JOIN TableA a AS a2 ON a.Item = a2.Item AND a.Timestamp >= a2.Timestamp
GROUP BY
a.Item,
a.Timestamp,
a.ExpectedQty
) a
INNER JOIN TableB b ON a.Item = b.Item
) s
select a.Item, a.ExpectedQty,b.ReceivedQty, (a.ExpectedQty - b.ReceivedQty) as 'Short' from a join b on a.Item = b.Item
SELECT a.ExpectedQty,
b.ReceivedQty,
CASE WHEN b.ReceivedQty < a.ExpectedQty
THEN b.ReceivedQty - a.ExpectedQty
ELSE 0
END Short
FROM dbo.a a
INNER JOIN dbo.b b
ON a.ItemId = b.ItemId

how do i get multiple records from 1 record

I have a product table with 15 fields like ItemID (primary),Name ,UPC,Price,Cost, etc.
Now I need to print labels the user can say
from Item "ABC" I need 15 labels
from item 'XYZ" I need 10 labels
I need a SQL statement which I will send the ItemID and the label Qty for Each record and it should give me back for each label a record for example 15 records for item "ABC" and 10 records for Item "XYZ" and so on
SELECT <fields>
FROM Mytable
Where Item = 'ABC'
GO 10
Will select those fields from that table 10 times in a row in 10 result sets.
Really though it sounds like you need to do what you are trying to do not in SQL, but in your calling application.
I agree this should be done on the client but if you insist, following duplicates each record 100 times and selects the amount you need from it.
;WITH ATable AS (
SELECT Item = 'ABC'
UNION ALL SELECT Item = 'XYZ'
)
, Temp (Item, Amount) AS (
SELECT 'ABC', 15
UNION ALL SELECT 'XYZ', 10
)
, q AS (
SELECT ID = 1
, Item
FROM ATable
UNION ALL
SELECT ID = q.ID +1
, q.Item
FROM q
WHERE ID < 100
)
SELECT q.*
FROM q
INNER JOIN Temp t ON t.Item = q.Item
AND t.Amount >= q.ID
You create the dynamic table aliased as r below. Works for amounts up to 2047.
select t.*
from
(select label='ABC', required=15 union all
select label='XYZ', required=10) r
inner join tbl t
on t.ItemID = r.label
inner join master..spt_values v
on v.type=Number and v.number between 1 and r.required
order by t.ItemID