Need help understanding why this query is producing these results - sql

Consider the database entries below. The distinguishing identifier is the "short" item number, 89721.
Data:
F3003
IRKIT IRKITL IRMMCU IRMCU IREFFF IREFFT IRAN8 IRTRT IRUOM
89721 7N74N050046 B20 NXT 12/06/2015 12/31/2040 200038 M SY
89721 7N74N050046 B70 NXT 07/28/2021 12/31/2040 200038 M SY
F0101
ABAN8 ABALPH ABAC01
200038 Company XYZ CON
F4101
IMITM IMDSC1
89721 TWIN RIB N05 ONYX HS-SY 46.5"
F41021
LIITM LIPBIN LIPQOH
89721 S 256
...
[a total of 99 "S" entries where LIPQOH sums to 16554]
F4211
SDITM SDNXTR SDDCTO SDUORG
89721 540 SO 4700.00
SQL:
SELECT F3003.IRAN8 AS CUST_NO,
F3003.IRKIT AS SHORT_ITEM,
F3003.IRKITL AS ITEM_NO,
F3003.IRUOM AS UOM,
F3003.IRMCU AS WC,
F0101.ABALPH AS CUST_NAME,
F4101.IMDSC1 AS ITEM_DESC,
SUM(F41021.LIPQOH / 100) AS ON_HAND
FROM PROD2DTA.F3003 AS F3003
INNER JOIN PROD2DTA.F0101 AS F0101
ON F3003.IRAN8 = F0101.ABAN8
INNER JOIN PROD2DTA.F4101 AS F4101
ON F3003.IRKIT = F4101.IMITM
INNER JOIN PROD2DTA.F41021 AS F41021
ON F3003.IRKIT = F41021.LIITM
WHERE F3003.IRMCU LIKE '%NXT'
AND F3003.IRTRT = 'M'
AND F0101.ABAC01 = 'CON'
AND F41021.LIPBIN = 'S'
AND CURRENT_DATE BETWEEN DATE(CONCAT(CAST(F3003.IREFFF / 1000 AS INT) + 1900, RIGHT(MOD(F3003.IREFFF, 1000) + 1000, 3))) AND DATE(CONCAT(CAST(F3003.IREFFT / 1000 AS INT) + 1900, RIGHT(MOD(F3003.IREFFT, 1000) + 1000, 3)))
GROUP BY F3003.IRAN8,
F3003.IRKIT,
F3003.IRKITL,
F3003.IRUOM,
F3003.IRMCU,
F3003.IRDSC1,
F0101.ABALPH,
F4101.IMDSC1
HAVING SUM(F41021.LIPQOH / 100) > 0
When I run the query above, I get the following results:
CUST NO CUST NAME ITEM NO ITEM DESC UOM ON HAND
200038 Company XYZ 7N74N050046 TWIN RIB N05 ONYX HS-SY 46.5" SY 16,554.00
This is correct based on comparison with JD Edwards applications.
Note that in the F3003 table above, there are two entries that meet the filtering criteria in the query. I have provided the IRMMCU column to show a source of multiple results, even though I'm not selecting it in the SQL. This represents a plant location where this item may be manufactured. The dates also may contribute to multiple results, because they both meet the filter critera, but are not selected in the SQL.
Now, consider the following query, which uses the one above as a subquery:
SELECT CUST_SPEC_ON_HAND.CUST_NO AS CUST_NO,
CUST_SPEC_ON_HAND.SHORT_ITEM AS SHORT_ITEM,
CUST_SPEC_ON_HAND.ITEM_NO AS ITEM_NO,
CUST_SPEC_ON_HAND.UOM AS UOM,
CUST_SPEC_ON_HAND.WC AS WC,
CUST_SPEC_ON_HAND.CUST_NAME AS CUST_NAME,
CUST_SPEC_ON_HAND.ITEM_DESC AS ITEM_DESC,
CUST_SPEC_ON_HAND.ON_HAND AS ON_HAND,
SUM(F4211.SDUORG / 100) AS OPEN_ORDER
FROM (SELECT F3003.IRAN8 AS CUST_NO,
F3003.IRKIT AS SHORT_ITEM,
F3003.IRKITL AS ITEM_NO,
F3003.IRUOM AS UOM,
F3003.IRMCU AS WC,
F0101.ABALPH AS CUST_NAME,
F4101.IMDSC1 AS ITEM_DESC,
SUM(F41021.LIPQOH / 100) AS ON_HAND
FROM PROD2DTA.F3003 AS F3003
INNER JOIN PROD2DTA.F0101 AS F0101
ON F3003.IRAN8 = F0101.ABAN8
INNER JOIN PROD2DTA.F4101 AS F4101
ON F3003.IRKIT = F4101.IMITM
INNER JOIN PROD2DTA.F41021 AS F41021
ON F3003.IRKIT = F41021.LIITM
WHERE F3003.IRMCU LIKE '%NXT'
AND F3003.IRTRT = 'M'
AND F0101.ABAC01 = 'CON'
AND F41021.LIPBIN = 'S'
AND CURRENT_DATE BETWEEN DATE(CONCAT(CAST(F3003.IREFFF / 1000 AS INT) + 1900, RIGHT(MOD(F3003.IREFFF, 1000) + 1000, 3))) AND DATE(CONCAT(CAST(F3003.IREFFT / 1000 AS INT) + 1900, RIGHT(MOD(F3003.IREFFT, 1000) + 1000, 3)))
GROUP BY F3003.IRAN8,
F3003.IRKIT,
F3003.IRKITL,
F3003.IRUOM,
F3003.IRMCU,
F0101.ABALPH,
F4101.IMDSC1
HAVING SUM(F41021.LIPQOH / 100) > 0) CUST_SPEC_ON_HAND
LEFT JOIN PROD2DTA.F4211 AS F4211
ON CUST_SPEC_ON_HAND.SHORT_ITEM = F4211.SDITM
AND F4211.SDDCTO IN ( 'SO', 'SM', 'S2' )
AND F4211.SDNXTR < 999
GROUP BY CUST_SPEC_ON_HAND.CUST_NO,
CUST_SPEC_ON_HAND.SHORT_ITEM,
CUST_SPEC_ON_HAND.ITEM_NO,
CUST_SPEC_ON_HAND.UOM,
CUST_SPEC_ON_HAND.WC,
CUST_SPEC_ON_HAND.CUST_NAME,
CUST_SPEC_ON_HAND.ITEM_DESC,
CUST_SPEC_ON_HAND.ON_HAND
When I run this query for the same "short" item number 89721, I get the following:
CUST # CUSTOMER NAME ITEM # ITEM DESCRIPTION UOM ON HAND OPEN ORDERS
200038 Company XYZ 7N74N050046 TWIN RIB N05 ONYX HS-SY 46.5" SY 33,108.00 4,700.00
Notice that the on-hand quantity is now twice what it was from the first query.
I can't figure out why this is happening. It does not happen for all data in the result set. From what I can tell, the items with an inflated on-hand quantity are those items in the F3003 table with entries for multiple manufacturing plants (F3003.IRMMCU). But why would it work correctly for the simpler query, and not for the second query that is using the first query?
Below are CREATE TABLE statements to create the tables shown above. Note: The actual JDE tables contain many more columns than what is shown here.
You should also note that JDE stores dates as an integer in a JDE "Julian" date format. The format is as follows: CYYDDD, where C is the century, YY is the year and DDD is the day of the year. For example, April 28, 2022 is represented as 122118.
CREATE TABLE F3003
(
IRTRT NCHAR(3) NULL, -- Type of Routing
IRKIT INT NULL, -- Parent (short) Item Number
IRKITL NCHAR(25) NULL, -- Kit - 2nd Item Number
IRMMCU NCHAR(12) NULL, -- Branch
IRMCU NCHAR(12) NULL, -- Business Unit
IREFFF NUMERIC(6) NULL, -- Effective - From Date
IREFFT NUMERIC(6) NULL, -- Effective - Thru Date
IRUOM NCHAR(2) NULL, -- Unit of Measure as Input
IRAN8 INT NULL, -- Address Number
);
CREATE TABLE F0101
(
ABAN8 INT NULL, -- Address Number
ABALPH NCHAR(40) NULL, -- Name - Alpha
ABAC01 NCHAR(3) NULL, -- Category Code - Address Book 01
);
CREATE TABLE F4101
(
IMITM INT NULL, -- Item Number - Short
IMDSC1 NCHAR(30) NULL, -- Description
);
CREATE TABLE F41021
(
LIITM INT NULL, -- Item Number - Short
LIPBIN NCHAR(1) NULL, -- Primary Location (P/S)
LIPQOH INT NULL, -- Quantity on Hand - Primary units
);
CREATE TABLE F4211
(
SDDCTO NCHAR(2) NULL, -- Order Type
SDITM INT NULL, -- Item Number - Short
SDNXTR NCHAR(3) NULL, -- Status Code - Next
SDUORG INT NULL, -- Units - Order/Transaction Quantity
);
If you really want some information overload, you can find all sorts of information about the JDE data ecosystem by going to http://www.jdetables.com/.
INSERT statements:
Note that the dates are in the JDE Julian date format.
INSERT INTO F3003 (IRKIT,IRKITL,IRMMCU,IRMCU,IREFFF,IREFFT,IRAN8,IRTRT,IRUOM) VALUES (89721,'7N74N050046','B20','NXT',115340,140366,200038,'M','SY');
INSERT INTO F3003 (IRKIT,IRKITL,IRMMCU,IRMCU,IREFFF,IREFFT,IRAN8,IRTRT,IRUOM) VALUES (89721,'7N74N050046','B70','NXT',121209,140366,200038,'M','SY');
INSERT INTO F0101 (ABAN8,ABALPH,ABAC01) VALUES (200038,'Company XYZ','CON');
INSERT INTO F4101 (IMITM,IMDSC1) VALUES (89721,'TWIN RIB N05 ONYX HS-SY 46.5"');
Note that the on-hand quantity (LIPQOH) is stored with 2 places after the decimal point. To get the actual value, it must be divided by 100. Also, I have just one insert statement to replicate the on-hand quantity, rather than inserting 99 lines.
INSERT INTO F41021 (LIITM,LIPBIN,LIPQOH) VALUES (89721,'S',1655400);
Note that the ordered quantity (SDUORG) is stored with 2 places after the decimal point. To get the actual value, it must be divided by 100.
INSERT INTO F4211 (SDITM,SDNXTR,SDDCTO,SDUORG) VALUES (89721,540,'SO',470000);

Related

SQL Server Stored Procedure for Menu Performance Report

I have four tables in my SQL database i.e MenuItems, Categories, Invoices and InvoiceDetails. Now what I want is to show the menu performance report for a certain date i.e total Qty and total Amount for
each menu item for a specific date. It shows the desired result without the date in the where clause but excludes menu items with null values.
Here is my stored procedure:
CREATE PROCEDURE spGetMenuPerformanceByDay
#Date date,
#Terminal int
AS
BEGIN
SELECT
M.Name,
ISNULL(SUM(D.Amount), 0) AS Amount,
ISNULL(SUM(D.Qty), 0) AS Qty
FROM
MenuItems AS M
JOIN
Categories AS C ON C.Id = M.CategoryId
LEFT JOIN
InvoiceDetails AS D ON M.Id = D.ItemId
LEFT JOIN
Invoices I ON I.Id = d.InvoiceId
WHERE
#Terminal IN (I.TerminalId, C.TerminalId)
AND CONVERT(date, I.Time) = #Date
OR NULL IN (Amount, Qty)
GROUP BY
M.Name, M.Id, D.ItemId
ORDER BY
(Qty) DESC
END
The result this stored procedure returns on adding Date in where clause:
Item
Amount
Qty
KOFTA ANDA
1950
3
HOT N SOUR SOUP
550
1
CHICKEN CHOWMEIN
250
1
CHICKEN KORMA
850
1
And the result I want is but don't get it on adding Date in where clause :
Item
Amount
Qty
KOFTA ANDA
1950
3
HOT N SOUR SOUP
550
1
CHICKEN CHOWMEIN
250
1
CHICKEN KORMA
850
1
CRISPY CHICKEN
0
0
MEXICAN BURGER
0
0
What if you don't put criteria for Invoices in the WHERE clause?
Sample data
create table Categories (
Id int primary key,
Name varchar(30) not null,
TerminalId int not null
);
create table MenuItems (
Id int identity(21,1) primary key,
Name varchar(30) not null,
CategoryId int not null,
foreign key (CategoryId) references Categories(Id)
);
create table Invoices (
Id int identity(31,1) primary key,
TerminalId int not null,
ItemId int not null,
Time datetime,
foreign key (ItemId) references MenuItems(Id)
);
create table InvoiceDetails (
InvoiceDetailId int identity(41,1) primary key,
InvoiceId int,
Amount decimal(10,2),
Qty int,
foreign key (InvoiceId) references Invoices(Id)
);
insert into Categories (Id, Name, TerminalId) values
(1,'KOFTA', 1),
(2,'SOUP', 1),
(3,'CHICKEN', 1),
(4,'BURGER', 1);
insert into MenuItems (CategoryId, Name) values
(1,'KOFTA ANDA'),
(2,'HOT N SOUR SOUP'),
(3,'CHICKEN CHOWMEIN'),
(3,'CHICKEN KORMA'),
(3,'CRISPY CHICKEN'),
(4,'MEXICAN BURGER');
insert into Invoices (ItemId, TerminalId, Time)
select itm.Id, cat.TerminalId, GetDate() as Time
from MenuItems itm
join Categories cat on cat.Id = itm.CategoryId
where itm.Name in (
'KOFTA ANDA',
'HOT N SOUR SOUP',
'CHICKEN CHOWMEIN',
'CHICKEN KORMA'
);
insert into InvoiceDetails (InvoiceId, Amount, Qty) values
(31, 1950, 3),
(32, 550, 1),
(33, 250, 1),
(34, 850, 1);
Query
DECLARE #TerminalId INT = 1;
DECLARE #Date DATE = GetDate();
SELECT
V.[Date],
C.Name AS Category,
M.Name AS MenuItemName,
ISNULL(SUM(D.Amount), 0) AS Amount,
ISNULL(SUM(D.Qty), 0) AS Qty
FROM Categories AS C
CROSS JOIN (SELECT #Date AS [Date], #TerminalId AS TerminalId) V
JOIN MenuItems AS M
ON M.CategoryId = C.Id
LEFT JOIN Invoices I
ON I.ItemId = M.Id
AND I.TerminalId = V.TerminalId
AND CAST(I.Time AS DATE) = V.[Date]
LEFT JOIN InvoiceDetails AS D
ON D.InvoiceId = I.Id
WHERE C.TerminalId = V.TerminalId
GROUP BY V.[Date], C.Id, M.Id, C.Name, M.Name
ORDER BY SUM(D.Qty) DESC
Date
Category
MenuItemName
Amount
Qty
2021-12-18
KOFTA
KOFTA ANDA
1950.00
3
2021-12-18
SOUP
HOT N SOUR SOUP
550.00
1
2021-12-18
CHICKEN
CHICKEN CHOWMEIN
250.00
1
2021-12-18
CHICKEN
CHICKEN KORMA
850.00
1
2021-12-18
CHICKEN
CRISPY CHICKEN
0.00
0
2021-12-18
BURGER
MEXICAN BURGER
0.00
0
Demo on db<>fiddle here
Here's my crack at your goal. Notice the changes. I found the reference to TerminalId in Category table highly suspicious - so much that I suspect it is a model flaw. Along those lines I note that TerminalId should likely have a foreign key to a missing table for Terminals. So I ignore that.
With that out, references to Category are now irrelevant. So that was removed as well. I also changed the procedure name since I find the reference to "day" misleading. It is highly likely "menu performance" would be evaluated on a "day" basis since retail (especially food service) sales vary by day of week consistently. So let's not mislead anyone thinking that is what this procedure does.
For simplicity and clarity, I removed the ISNULL usage. Add it back if desired but such things are usually better handled by the consumer of the resultset. I left the ORDER BY clause as a stub for you to re-evaluate (and you need to).
So how does this work? Simply calculate the sums directly in the CTE and then outer join from the menu items to the CTE sums to get all menu items along with the relevant performance information for the date specified.
CREATE PROCEDURE dbo.GetMenuPerformanceByDate
#Date date,
#Terminal int
AS
BEGIN
with sales as (
select det.ItemId, SUM(det.Amount) as amt, SUM(det.Qty) as qty
from dbo.Invoices as inv
inner join dbo.InvoiceDetails as det
on inv.Id = det.InvoiceId
where cast(inv.Time as date) = #Date
and inv.TerminalId = #Terminal
group by det.ItemId
)
select menu.name, sales.amt, sales.qty
from dbo.MenuItems as menu
left join sales
on menu.Id = sles.ItemId
order by ...
;
END;
One last note. This filter:
cast(inv.Time as date) = #Date
is generally not a good method of filtering a datetime column. Far better to use inclusive lower and exclusive upper boundaries like:
inv.Time >= #date and inv.Time < dateadd(day, 1, #date)
for this reason.
My last note - there is a potential flaw regarding MenuItems. Presumably "name" is unique. It is highly unlikely that multiple rows would have the same name, but "unlikely" is not a restriction. If you generate rows based on name and name turns out to NOT be unique, your results are difficult to interpret.

Group by count multiple tables

Need to find out why my group by count query is not working. I am using Microsoft SQL Server and there are 2 tables I am trying to join.
My query needs to bring up the number of transactions made for each type of vehicle. The output of the query needs to have a separate row for each type of vehicle such as ute, hatch, sedan, etc.
CREATE TABLE vehicle
(
vid INT PRIMARY KEY,
type VARCHAR(30) NOT NULL,
year SMALLINT NOT NULL,
price DECIMAL(10, 2) NOT NULL,
);
INSERT INTO vehicle
VALUES (1, 'Sedan', 2020, 240)
CREATE TABLE purchase
(
pid INT PRIMARY KEY,
vid INT REFERENCES vehicle(vid),
pdate DATE NOT NULL,
datepickup DATE NOT NULL,
datereturn DATE NOT NULL,
);
INSERT INTO purchase
VALUES (1, 1, '2020-07-12', '2020-08-21', '2020-08-23')
I have about 10 rows on information in each table I just haven't written it out.
This is what I wrote but it doesn't return the correct number of transactions for each type of car.
SELECT
vehicle.vid,
COUNT(purchase.pid) AS NumberOfTransactions
FROM
purchase
JOIN
vehicle ON vehicle.vid = purchase.pid
GROUP BY
vehicle.type;
Any help would be appreciated. Thanks.
Your GROUP BY and SELECT columns are inconsistent. You should write the query like this:
SELECT v.Type, COUNT(*) AS NumPurchases
FROM Purchase p JOIN
Vehicle v
ON v.vID = p.pID
GROUP BY v.Type;
Note the use of table aliases so the query is easier to write and read.
If this doesn't produce the expected values, you will need to provide sample data and desired results to make it clear what the data really looks like and what you expect.

Join Sales table with Sales Region table based on Sales person and the date of the sale

I have a table of sales, but it does not include the region of the sale. I also have a table of the assignment of our sales people based on the region and dates they were assigned. I want to join the tables so I can grab the region and include it into my sales table.
I join on the sales person's initial (key), but I also want to compare the date of the sale to the region start and region stop to join the correct region. I tried using the sale date BETWEEN the start and stop, but that did not work because if they are still currently in the region, it provides a NULL value.
Thanks for any help, Brent
IF NOT EXISTS (
select * from sysobjects where name='sales' and xtype='U'
)CREATE TABLE sales (
[Sale_Date] DATETIME,
[Sales_Person] NVARCHAR(3),
[Sales_Amount] INT,
[Region] INT
);
INSERT INTO sales VALUES
('2016-07-01 00:00:00',N'MDD',152,NULL),
('2016-09-21 00:00:00',N'MDD',278,NULL),
('2018-03-01 00:00:00',N'STE',385,NULL),
('2018-04-01 00:00:00',N'MDD',426,NULL),
('2019-02-25 00:00:00',N'MDD',224,NULL),
('2020-02-15 00:00:00',N'STE',261,NULL),
('2020-03-01 00:00:00',N'STE',480,NULL),
('2020-06-05 00:00:00',N'BBB',245,NULL),
('2020-07-05 00:00:00',N'BBB',178,NULL);
IF NOT EXISTS (
select * from sysobjects where name='SalesPersonAssignment' and xtype='U'
) CREATE TABLE SalesPersonAssignment (
[sales_person] NVARCHAR(4),
[Region_ID] INT,
[Region_Name] NVARCHAR(6),
[Region_Start_Date] DATETIME,
[Region_Stop_Date] NVARCHAR(10)
);
INSERT INTO SalesPersonAssignment VALUES
(N'MDD',2,N'North ','2015-01-05 00:00:00',N'12/31/2017'),
(N'MDD',6,N'West','2018-01-01 00:00:00',N'NULL'),
(N'STE ',6,N'West','2018-10-02 00:00:00',N'12/31/2019'),
(N'STE',2,N'North ','2020-01-01 00:00:00',N'NULL'),
(N'BBB',1,N'South','2019-01-01 00:00:00',N'NULL');
Select s.Sale_Date, s.Sales_Amount, s.Sales_Person, spa.Region_Name
FROM sales s LEFT OUTER JOIN SalesPersonAssignment spa ON s.Sales_Person = spa.sales_person
--join based on the sales date and region's start/stop date of the sales person
You are storing a date as a string. And the time component is unnecessary. The better approach is:
CREATE TABLE SalesPersonAssignment (
[sales_person] NVARCHAR(4),
[Region_ID] INT,
[Region_Name] NVARCHAR(6),
[Region_Start_Date] DATE,
[Region_Stop_Date] DATE
);
 
INSERT INTO SalesPersonAssignment VALUES
(N'MDD',2,N'North ','2015-01-05','2017-12-31'),
(N'MDD',6,N'West','2018-01-01', NULL),
(N'STE ',6,N'West','2018-10-02', '2019-12-31'),
(N'STE',2,N'North ','2020-01-01', NULL),
(N'BBB',1,N'South','2019-01-01', NULL);
Just include those conditions in the ON clause:
SELECT s.Sale_Date, s.Sales_Amount, s.Sales_Person, spa.Region_Name
FROM sales s LEFT OUTER JOIN
SalesPersonAssignment spa
ON s.Sales_Person = spa.sales_person AND
s.Sale_Date >= spa.Region_Start_Date AND
(s.Sale_Date <= spa.Region_End_Date OR spa.Region_End_Date IS NULL);

MS SQL How to avoid loops when allocating amounts

I'm building a system that needs to automatically allocate amounts to a Charge_Detail table.
I have the following tables:
-- a list of charges that are outstanding
DECLARE Charge_Master TABLE
( ID INT NOT NULL,
CompanyID VARCHAR(6) NOT NULL,
EntryDate SMALLDATETIME,
Ref VARCHAR(30) NOT NULL,
Amount DECIMAL(12,2) NOT NULL
)
-- directly linked to master table to represent charges paid in detail
DECLARE Charge_Detail TABLE
( ID INT NOT NULL,
Charge_MasterID INT NOT NULL, --Foreign Key
EntryDate SMALLDATETIME,
Ref VARCHAR(30) NOT NULL,
Amount DECIMAL(12,2) NOT NULL
)
INSERT Charge_Master
VALUES ('ABC123', '01/01/2018', 'INV-111', 25),
('ABC123', '21/03/2018', 'INV-222', 30),
('ABC123', '11/05/2018', 'INV-333', 15)
The objective is to have a query take the following parameters:
CompanyId e.g. 'ABC123'
Amount e.g. 45
Ref e.g. 'REF-142'
Based on the parameters it should work out the records that need to be INSERTED into the Charge_Detail table and the associated Charge_MasterID.
Example:
If the total amount to allocate is 45 against CompanyId: ABC123
then this is the Expected output inserted into the Charge_Detail table.
/* Charge_Master Table
*
* ID CompanyID EntryDate Ref Amount
* 1 ABC123 01/01/2018 INV-111 25
* 2 ABC123 21/03/2018 INV-222 30
* 3 ABC123 11/05/2018 INV-333 15
*/
/* Charge_Detail Table
*
* ID Charge_MasterID EntryDate Ref Amount
* 1 1 12/08/2018 REF-142 25
* 2 2 12/08/2018 REF-142 20 -- cannot fully allocate therefore 10 still remaining to be allocated for next time
*/
What's the best approach with this? Would a CTE help? I'm not too familiar with them but I have tried a SELECT query with subqueries and case statement but I can't get it to decrement the amount remaining without using a loop.
Any advice would be much appreciated!
It should be something like this:
DECLARE #CompanyID VARCHAR(6) = 'ABC123',
#Ref VARCHAR(30) = 'REF-142',
#Amount DECIMAL(12,2) = 45
;WITH cte as (
SELECT *, SUM (Amount) OVER (ORDER BY Id) AS RunningAmount
FROM Charge_Master
), Limits as (
SELECT TopRow = (SELECT MIN(ID) FROM CTE WHERE RunningAmount > #Amount),
LastRow = (SELECT MAX(ID) FROM CTE WHERE RunningAmount < #Amount))
SELECT c.ID, c.EntryDate, #Ref, c.Amount
FROM Limits as l INNER JOIN cte as c ON c.ID < l.TopRow
UNION ALL
SELECT c2.ID, c2.EntryDate, #Ref, #Amount-c1.RunningAmount
FROM Limits as l
INNER JOIN cte as c1 ON c1.ID = l.LastRow
INNER JOIN cte as c2 ON c2.ID = l.TopRow
ORDER BY 1

How to distribute budget value to actual rows in Postgresql

Budget table contains jobs with loads:
create temp table budget (
job char(20) primary key,
load numeric(4,1) not null check (load>0 )
);
insert into budget values ( 'programmer', 3 );
insert into budget values ( 'analyst', 1.5 );
Actual table contains actual loads by employees:
create temp table actual (
job char(20),
employee char(20),
load numeric(4,1) not null check (load>0 ),
contractdate date,
primary key (job, employee)
);
insert into actual values ( 'programmer', 'John', 1, '2014-01-01' );
-- half time programmer:
insert into actual values ( 'programmer', 'Bill', 0.5, '2014-01-02' );
insert into actual values ( 'analyst', 'Aldo', 1, '2014-01-03' );
insert into actual values ( 'analyst', 'Margaret', 1, '2014-01-04' );
Result table should show difference between budget and actual jobs so that budget load is
distributed to employees in contract date order.
If budget load is greater than sum of job loads, separate budget line with empty employee
should appear.
In data above, 1.5 programmers are missing and 0.5 analysts are more.
Result should be
Job Employee Budget Actual Difference
programmer John 1 1 0
programmer Bill 0.5 0.5 0
programmer 1.5 0 1.5
analyst Aldo 1 1 0
analyst Margaret 0.5 1 -0.5
How to create such table in modern Postgresql ?
Can rank function with full join used or other idea ?
I tried
select
coalesce(budget.job, actual.job ) as job,
employee,
budget.load as budget,
coalesce(actual.load,0) as actual,
coalesce(budget.load,0)-coalesce( actual.load,0) as difference
from budget full join actual using (job)
order by 1, contractdate
but this does not distribute budget load to employee rows.
I posted this also in pgsql-general mailing list.
The following query gets what you want:
select job, employee, budget, actual,
(budget - cumload) as diff, contractdate
from (select coalesce(b.job, a.job ) as job, a.contractdate,
a.employee,
b.load as budget,
coalesce(a.load,0) as actual,
sum(a.load) over (partition by a.job order by a.contractdate NULLS last) as cumload
from budget b join
(select a.*
from actual a
union all
select b.job, NULL, NULL, NULL
from budget b
) a
on b.job = a.job
) ab
where contractdate is not null or budget > cumload
order by job, contractdate
The SQL Fiddle is here.
Note that this uses union all to bring in the extra rows needed for the query. You wanted to do this with a full outer join, but that doesn't generate extra rows when the join conditions are met.
Also, the logic that you are looking for requires a cumulative sum, which Postgres happily provides.