I have table structure like tblCustData
ID UserID Fee FeePaid
1 12 150 0
2 12 100 0
3 12 50 0
And value to be update in FeePaid Column such that if i have value in #Amt variable in 200 Then it should update any two rows
Output should be like
ID UserID Fee FeePaid
1 12 150 150
2 12 100 50
3 12 50 0
FeePaid should not be grater than Fee Column But if i pass 350 in #Amt variable it should produce output like
ID UserID Fee FeePaid
1 12 150 200
2 12 100 100
3 12 50 50
Only if #Amt is exceeding the total value in Fee column
I can not think beyond this query
Update tblCustData
Set FeePaid=#Amt
Where UserID=12
First with CTE syntax we prepare a table with sums distribution and then using unique field Code update the main table using CASE to handle all possible ways (including first row with remainder).
Declare #Amt int;
SET #Amt=250;
with T as
(
SELECT ROW_NUMBER() OVER (ORDER BY Fee desc) as rn, *
FROM tblCustData WHERE UserId=12
)
,T2 as
(
SELECT *,
ISNULL((SELECT SUM(Fee-FeePaid) FROM T WHERE T1.RN<RN),0) as PrevSum
FROM T as T1
)
UPDATE
A
SET A.FeePaid = A.FeePaid+ CASE WHEN (B.PrevSum+B.Fee-B.FeePaid<=#Amt)
AND (B.RN<>1)
THEN B.Fee-B.FeePaid
WHEN (B.PrevSum+B.Fee-B.FeePaid<=#Amt) AND (B.RN=1)
THEN #Amt-B.PrevSum
WHEN B.PrevSum>=#Amt
THEN 0
WHEN B.PrevSum+B.Fee-B.FeePaid>#Amt
THEN #Amt-B.PrevSum
END
FROM
tblCustData A
JOIN T2 B ON A.Code = B.Code
GO
SQLFiddle demo
Try ..
declare #t table (id int identity, UserId int, Fee money, FeePaid money)
insert into #t (UserID, Fee, FeePaid)
values
(12, 150, 0)
,(12, 100, 0)
,(12, 50 , 0)
declare #amt money = 200; -- change to 400 to test over paid
declare #Fees money;
select #Fees = sum(Fee) from #t;
declare #derivedt table (deid int, id int, UserId int, Fee money, FeePaid money)
insert into #derivedt (deid, id, UserId, Fee, FeePaid)
select row_number() over (order by case when #amt <= #Fees then id else -id end asc), id, UserId, Fee, FeePaid
from #t
-- order by case when #amt <= #Fees then id else -id end asc
; with cte(deid, id, UserId, Fee, FeePaid, Remainder)
as
(
select 0 as deid, 0 as id, 0 as UserId, cast(0.00 as money) as Fee, cast(0.00 as money) as FeePaid , #Amt as Remainder
from #derivedt
where id = 1
union all
select t.deid, t.id, t.UserId, t.Fee, case when cte.Remainder > t.Fee then t.Fee else cte.Remainder end as FeePaid
, case when cte.Remainder > t.Fee then cte.Remainder - t.Fee else 0 end as Remainder
from #derivedt t inner join cte cte on t.deid = (cte.deid + 1)
)
update origt
set FeePaid = det.FeePaid
from #t origt
inner join
(
select cte1.deid, cte1.id, cte1.UserId, cte1.Fee, cte1.FeePaid + ISNULL(cte2.Remainder, 0) as FeePaid
from cte cte1
left outer join (select top 1 deid, Remainder from cte order by deid desc) cte2
on cte1.deid = cte2.deid
where cte1.deid > 0
) det
on origt.id = det.id
select *
from #t
Modified to continuous update of value..
-- Create table once and insert into table once
create table #t (id int identity, UserId int, Fee money, FeePaid money)
insert into #t (UserID, Fee, FeePaid)
values
(12, 150, 0)
,(12, 100, 0)
,(12, 50 , 0)
-- ===============================
-- Run multiple times to populate #t table
declare #amt money = 100; -- change to 400 to test over paid
declare #Fees money;
select #Fees = sum(Fee - FeePaid) from #t;
declare #derivedt table (deid int, id int, UserId int, Fee money, FeePaid money)
insert into #derivedt (deid, id, UserId, Fee, FeePaid)
select row_number() over (order by case when #amt <= #Fees then id else -id end asc), id, UserId, (Fee - FeePaid) as Fee, FeePaid
from #t
-- order by case when #amt <= #Fees then id else -id end asc
; with cte(deid, id, UserId, Fee, FeePaid, Remainder)
as
(
select 0 as deid, 0 as id, 0 as UserId, cast(0.00 as money) as Fee, cast(0.00 as money) as FeePaid , #Amt as Remainder
from #derivedt
where id = 1
union all
select t.deid, t.id, t.UserId, t.Fee, case when cte.Remainder >= t.Fee then t.Fee else cte.Remainder end as FeePaid
, case when cte.Remainder >= t.Fee then cte.Remainder - t.Fee else 0 end as Remainder
from #derivedt t inner join cte cte on t.deid = (cte.deid + 1)
)
update origt
set FeePaid = origt.FeePaid + det.FeePaid
from #t origt
inner join
(
select cte1.deid, cte1.id, cte1.UserId, cte1.Fee, cte1.FeePaid + ISNULL(cte2.Remainder, 0) as FeePaid, cte1.Remainder
from cte cte1
left outer join (select top 1 deid, Remainder from cte order by deid desc) cte2
on cte1.deid = cte2.deid
where cte1.deid > 0
) det
on origt.id = det.id
select *
from #t
-- Drop temp table after
-- drop table #t
Apart from your code, I added an identity column to your table. See the code.
DECLARE #TAB TABLE(ID INT IDENTITY(1,1),USERID INT, FEE INT, FEEPAID INT)
INSERT INTO #TAB VALUES (12,150,0),(12,100,0),(12,50,0)
DECLARE #AMOUNT INT = 230,
#AMOUNTNEW INT = 0,
#B INT = 1,
#S INT = 1,#E INT = (SELECT COUNT(*) FROM #TAB)
WHILE #S <= #E
BEGIN
UPDATE LU
SET LU.FEEPAID = CASE WHEN #AMOUNT >= FEE THEN FEE ELSE #AMOUNT END
FROM #TAB LU
WHERE LU.ID = #S
SET #AMOUNT = #AMOUNT - (SELECT FEE FROM #TAB WHERE ID = #S)
IF #AMOUNT <= 0
SET #S = #E
SET #S = #S + 1
END
SELECT * FROM #TAB
Result:
I hope the idea is clear, we can work from here.
Related
I have orders and users. I distribute evenly for each user's orders.
I need to redistribute when new users are added. And it is necessary to take into account the difference between the fulfilled and the limit in the algorithm. If users have made a limit on the first distribution, they do not include in the new distribution and expose the column to the limit value from the column made.
declare #orderCount int
set #orderCount = 50 --Orders for Distribution
--Result table
declare #t table (
users char(3),
limit int,
Made int
)
--Add users
insert into #t (users, limit, Made) values
('us1',0,0),
('us2',0,0),
('us3',0,0)
--Table for the Distribution algorithm
declare #c table (
users char(3),
limit int,
Made int,
Cnt int,
Rn int
)
--Count users and row_number
insert into #c
select
*,
COUNT(*) OVER () as Cnt,
ROW_NUMBER() OVER (ORDER BY users) as Rn
from
#t
--var for check new distribution>made
DECLARE #check int = 0
--Distribution
update t
set #check = (#orderCount/Cnt) + CASE WHEN #orderCount % Cnt >= Rn THEN 1 ELSE 0 END,
limit=
CASE WHEN
(#check >= t.Made)
THEN
#check
ELSE
t.Made
END
FROM #t t
INNER JOIN #c cn ON cn.users=t.users
--Test data
UPDATE #t SET Made=12 WHERE users='us1'
UPDATE #t SET Made=10 WHERE users='us2'
UPDATE #t SET Made=5 WHERE users='us3'
--Check result
SELECT * FROM #t
--add to distribution new users
INSERT INTO #t (users, limit, Made) values ('us4',0,0)
INSERT INTO #t (users, limit, Made) values ('us5',0,0)
--Clear table
DELETE FROM #c
--Check new data
insert into #c
select
*,
COUNT(*) OVER () as Cnt,
ROW_NUMBER() OVER (ORDER BY users) as Rn
from
#t
--Distribution
update t
set #check = (#orderCount/Cnt) + CASE WHEN #orderCount % Cnt >= Rn THEN 1 ELSE 0 END,
limit=
CASE WHEN
(#check >= t.Made)
THEN
#check
ELSE
t.Made
END
FROM #t t
INNER JOIN #c cn ON cn.users=t.users
--Check result
SELECT * FROM #t
It divides the number of orders for users. 50/3 ~16 orders for 1 users. If I add 2 new users then 50/5 ~ 10. But 1 users do 12 orders. 12 more new distribution orders (10). then him past into column limit 12. And 50-12=38. Then 38/4 ~ 9 orders the rest uesrs.
SELECT x.users,
x.work,
x.Made,
sum(CASE WHEN Made>NewLimit THEN Made ELSE 0 END) OVER() as Dif,
count(CASE WHEN Made<NewLimit THEN NewLimit END) OVER() as Cnt,
CASE WHEN Made>NewLimit THEN 0 ELSE 1 END as IsUsed,
(CASE WHEN Made<NewLimit THEN
ROW_NUMBER() OVER (PARTITION BY x.work, (case when Made<NewLimit then 1 else 0 end) ORDER BY work) ELSE 0 END) as Rn
FROM (
SELECT t.users ,(#orderCount/Cnt) + CASE WHEN #orderCount % Cnt >= Rn THEN 1 ELSE 0 END as NewLimit,
t.Made,
t.work
FROM #t t
INNER JOIN #c cn ON cn.users=t.users
) x
I consider the amount where the old distribution is more. I'm setting the isUsed flag for the new distribution. I consider the number. I deduct from all orders the amount where the new lehmite is accustomed and divide the amount received for a quantity that is suitable for a new distribution.
My problem is auto populating Table.
I have table with 1000 record in it, but for testing purpose, i need to insert more data.
ID | PersonID | Date | Time | Sum | TypeID | PlaceID | StatusID
So i need to populate the database with 10000 records where the date is between 1/3/2015 and 1/5/2015, Time is Random, SUM Between 100 and 1000, TypeID between 1 and 2, PlaceID between 1-10, StatusID between 1-3
I would a appreciate any kind of help or suggestion.
Thanks in advance.
Here is some brutal solution but completely randomized:
with rows as(select row_number() over(order by(select null)) as dummy from
(values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t1(n)
cross join (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t2(n)
cross join (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t3(n)
cross join (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t4(n))
select *,
cast(dateadd(ms, cast(cast(newid() as varbinary(30)) as int), getdate()) as time) as time
from rows r
cross apply(select top 1 p as place
from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10))p(p)
where r.dummy = r.dummy order by newid()) cap
cross apply(select top 1 s as status
from (values(1),(2),(3))s(s)
where r.dummy = r.dummy order by newid()) cas
cross apply(select top 1 t as time
from (values(1),(2))t(t)
where r.dummy = r.dummy order by newid()) cat
cross apply(select top 1 sum from(select 100 + row_number() over(order by(select null)) as sum
from (values(1),(1),(1),(1),(1),(1),(1),(1),(1))t1(n)
cross join (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t2(n)
cross join (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t3(n)) t
where r.dummy = r.dummy order by newid()) casu
cross apply(select top 1 dateadd(dd, s -1, '20150103') as date
from (values(1),(2),(3))s(s)
where r.dummy = r.dummy order by newid()) cad
Fiddle http://sqlfiddle.com/#!6/9eecb7db59d16c80417c72d1/892
You need a small t-sql to do it:
--CREATE TABLE TEST (CID INT, PERSONID INT, TEST_DATE DATE, TEST_TIME TIME, TEST_SUM INT, TYPEID INT, PLACEID INT, STATUSID INT);
--TRUNCATE TABLE TEST;
SET NOCOUNT ON;
DECLARE #X INT, #PERSONID INT, #DATE DATE, #TIME TIME, #SUM INT, #TYPEID INT, #PLACEID INT, #STATUSID INT,#R INT;
SELECT #X=0;
WHILE #X < 10000 BEGIN
SELECT #X=#X +1;
SELECT #DATE = DATEADD(DAY, #X / 4000, '2015-1-3');
SELECT #R=CONVERT(INT, RAND() * 3600 * 24);
SELECT #TIME = DATEADD(SECOND, #R , '00:00:01');
SELECT #SUM = 100 + #R % 900;
SELECT #TYPEID = #R % 2 + 1 ;
SELECT #PLACEID = #R % 10 +1 ;
SELECT #STATUSID = #R % 3 +1 ;
SELECT #PERSONID = #R % 500 +1 ;
INSERT INTO TEST (CID, PERSONID, TEST_DATE, TEST_TIME, TEST_SUM, TYPEID, PLACEID, STATUSID)
VALUES(#X, #PERSONID, #DATE, #TIME, #SUM, #TYPEID, #PLACEID, #STATUSID);
END;
SET NOCOUNT OFF;
Also, please try not to use column names like "ID","Date","Time" and etc which have special meanings in SQL Server.
One is to use the pseudo random values derived from NEWID. You didn't mention how ID and PersonID should be assigned but the ROW_NUMBER value returned by the CTE could be used for that if you need incremental values.
WITH
t4 AS (SELECT n FROM (VALUES(0),(0),(0),(0)) t(n))
,t256 AS (SELECT 0 AS n FROM t4 AS a CROSS JOIN t4 AS b CROSS JOIN t4 AS c CROSS JOIN t4 AS d)
,t16M AS (SELECT ROW_NUMBER() OVER (ORDER BY (a.n)) AS num FROM t256 AS a CROSS JOIN t256 AS b CROSS JOIN t256 AS c)
SELECT
DATEADD(day, CAST(CAST(NEWID() AS varbinary(1)) AS int) % 3, '20150103') AS Date
,DATEADD(millisecond, CAST(CAST(NEWID() AS varbinary(4)) AS int), CAST('' AS time)) AS Time
,(CAST(CAST(NEWID() AS varbinary(3)) AS int) % 900) + 100 AS [Sum]
,(CAST(CAST(NEWID() AS varbinary(3)) AS int) % 2) + 1 AS TypeID
,(CAST(CAST(NEWID() AS varbinary(3)) AS int) % 10) + 1 AS PlaceID
,(CAST(CAST(NEWID() AS varbinary(3)) AS int) % 3) + 1 AS StatisID
FROM t16M
WHERE num <= 10000;
I working on FIFO implementation in sql.
I have Batch number concept in my application.
If suppose I am selling on inventory then my application should tell me that which inventory is the first come.
Lets. Say I purchased Inventory 'A' on 4th-Aug, 5th-Aug & 6th-Aug
On 4th Aug - A Inventory has batch number BT002 - 10 (Qty)
On 5th Aug - A's Inventory has batch number BT003 - 15 (Qty)
On 6th Aug - A's Inventory has batch number BT001 - 10 (Qty)
So, Now I am having stock Now in my hand as following :
A Inventory
BT002 - 10 - 4-Aug
BT003 - 15 - 5-Aug
BT001 - 10 - 6-Aug
Now If I want to sell that Inventory to anybody then my application should tell me that I should sell
BT002 (Batch number) inventory first beacause it came first.
That was the concept I am using in my application.
Now I want to sell 15 Qty from 'A' (Inventory).
Then O/p Should be like this :
BT002 - 10
BT003 - 5
Here's My Query :
SELECT ISNULL(SUM(qty),0) AS Qty,batch_no,accept_date FROM RS_GIN_Master
GROUP BY batch_no,accept_date
HAVING ISNULL(SUM(qty),0) <= 15
ORDER BY accept_date asc
O/p Of Given Query :
How can I get O/P like this :
BT002 - 10
BT003 - 5
Any Help will be appreciated.
Thank you in Advance.
This should work for you:
Working sample on Fiddle
CREATE FUNCTION [dbo].[GetBatchAmounts]
(
#requestedAmount int
)
RETURNS
#tBatchResults TABLE
(
Batch nvarchar(50),
Amount int
)
AS
BEGIN
/*This is just a mock of ersults of your query*/
DECLARE #RS_GIN_Master TABLE(
Qty int,
batch_no NVARCHAR(max),
accept_date DATETIME
)
insert into #RS_GIN_Master(Qty,batch_no,accept_date)
SELECT 10,'BT002', CAST(CAST(2014 AS varchar) + '-' + CAST(8 AS varchar) + '-' + CAST(4 AS varchar) AS DATETIME)
insert into #RS_GIN_Master(Qty,batch_no,accept_date)
SELECT 10,'BT003', CAST(CAST(2014 AS varchar) + '-' + CAST(8 AS varchar) + '-' + CAST(5 AS varchar) AS DATETIME)
insert into #RS_GIN_Master(Qty,batch_no,accept_date)
SELECT 10,'BT001', CAST(CAST(2014 AS varchar) + '-' + CAST(8 AS varchar) + '-' + CAST(6 AS varchar) AS DATETIME)
/*---------------------------*/
DECLARE #Qty int
DECLARE #batch_no NVARCHAR(max)
DECLARE #accept_date DATETIME
DECLARE myCursor CURSOR FOR
SELECT Qty, batch_no, accept_date FROM #RS_GIN_Master ORDER BY accept_date ASC
OPEN myCursor
FETCH NEXT FROM myCursor INTO #Qty, #batch_no,#accept_date
WHILE (##FETCH_STATUS = 0 AND #requestedAmount > 0 )
BEGIN
Declare #actualQty int
IF #requestedAmount > #Qty
SET #actualQty = #Qty
ELSE
SET #actualQty = #requestedAmount
INSERT INTO #tBatchResults (batch, Amount)
SELECT #batch_no, #actualQty
set #requestedAmount = #requestedAmount - #actualQty
FETCH NEXT FROM myCursor INTO #Qty, #batch_no,#accept_date
END /*WHILE*/
CLOSE myCursor
DEALLOCATE myCursor
RETURN
END
Just make sure to replace the marked part of the function with your query...
You need to create a stored procedure in your database and taking the quantity from your stock table. And you should also have the id of each record to update that records from where u have taken that qty.
Alter PROCEDURE sp_UpdateStockForSale
#batchNO varchar(10),
#qty decimal(9,3)
AS
BEGIN
Create Table #tmpOutput(ID int identity(1,1), StockID int, batchNo varchar(10), qty decimal(9,3));
SET NOCOUNT ON;
DECLARE #ID int;
DECLARE #Stock Decimal(9,3);
DECLARE #TEMPID int;
Select #TEMPID=(Max(ID)+1) From RS_GIN_Master Where qty > 0 And batch_no = #batchNO;
While (#qty > 0) BEGIN
Select #ID=ID, #Stock=qty From RS_GIN_Master Where qty > 0 And batch_no = #batchNO AND ID < #TEMPID Order By accept_date Desc;
--If Outward Qty is more than Stock
IF (#Stock < #qty) BEGIN
SET #qty = #qty - #Stock;
SET #Stock = 0;
END
--If Outward Qty is less than Stock
ELSE BEGIN
SET #Stock = #Stock - #qty;
SET #qty = 0;
END
Insert Into #tmpOutput(StockID,batchNo,qty)Values(#ID,#batchNO,#Stock);
SET #TEMPID = #ID;
--This will update that record don't need it now.
--Update RS_GIN_Master Set qty = #Stock Where ID=#ID
END
Select StockID, batchNo, qty From #tmpOutput;
END
GO
The above example is not compiled but, you can get the logic how you can retrieve the records from your stock table according to FIFO method. You can use accept_date instead of ID in RS_GIN_Master table. but, i would prefer to make it unique so, if i want to get a specific record then it can be possible.
One query .. like this
This should be tweaked for you case as you have groups and other stuff, is only for example purposes.
;with qty as (
select 15 as value
)
,l as (
select
ROW_NUMBER () over (order by accept_date desc) rn
,*
from xxx
)
,q as (
select
batch_no
,accept_date
,case when value>qty then value-qty else 0 end as remainder
,case when value>qty then qty else value end as used
,rn
from l
cross join qty
where rn=1
union all
select
r.batch_no
,r.accept_date
,case when q.remainder>r.qty then q.remainder-r.qty else 0 end as remainder
,case when q.remainder>r.qty then r.qty else q.remainder end as used
,r.rn
from q
join l r
on q.rn+1 = r.rn
where q.remainder!=0
)
select *
from q
where used != 0
and the fiffle for it http://sqlfiddle.com/#!6/9b063/34/0
Below should work for you
Create table RS_GIN_Master
(id int,
qty int,
batch_no varchar(5),
accept_date Datetime
)
GO
Insert into RS_GIN_Master (id, qty, batch_no, accept_date)
values(1,10,'BT001','2018-04-06')
Insert into RS_GIN_Master (id, qty, batch_no, accept_date)
values(2,10,'BT002','2018-04-04')
Insert into RS_GIN_Master (id, qty, batch_no, accept_date)
values(3,15,'BT003','2018-05-06')
GO
----------------------------
CREATE PROC FIFO
#TakenQty int
AS
BEGIN
WITH cte AS (SELECT *, SUM(qty) OVER (ORDER BY accept_date, id ASC) as CumQty FROM RS_GIN_Master WHERE qty>0)
SELECT TOP ((SELECT COUNT(*) FROM cte WHERE CumQty <#TakenQty)+1) batch_no, accept_date,
CASE
WHEN CumQty<#TakenQty THEN qty
ELSE #TakenQty -(CumQty-Qty)
END AS TakenOut
FROM cte
END
Result
| batch_no | accept_date | TakenOut |
|----------|----------------------|----------|
| BT002 | 2018-04-04T00:00:00Z | 10 |
| BT001 | 2018-04-06T00:00:00Z | 5 |
http://www.sqlfiddle.com/#!18/f7ee7/1
Let say I have a table:
ColumnA ColumnB
---------------------------------
1 10.75
4 1234.30
6 2000.99
How can I write a SELECT query that will result in the following:
ColumnA ColumnB
---------------------------------
1 10.75
2 0.00
3 0.00
4 1234.30
5 0.00
6 2000.99
You can use a CTE to create a list of numbers from 1 to the maximum value in your table:
; with numbers as
(
select max(ColumnA) as nr
from YourTable
union all
select nr - 1
from numbers
where nr > 1
)
select nr.nr as ColumnA
, yt.ColumnB
from numbers nr
left join
YourTable yt
on nr.nr = yt.ColumnA
order by
nr.nr
option (maxrecursion 0)
See it working at SQL Fiddle.
Please try:
declare #min int, #max int
select #min=MIN(ColumnA), #max=MAX(ColumnA) from tbl
select
distinct number ColumnA,
isnull(b.ColumnB, 0) ColumnB
from
master.dbo.spt_values a left join tbl b on a.number=b.ColumnA
where number between #min and #max
Create a TallyTable (or NumbersTable) - see this question: What is the best way to create and populate a numbers table?
With that table create an insert statement:
INSERT INTO YourTable (ColumnA, ColumnB)
SELECT Number FROM NumberTable
WHERE
NOT EXISTS (SELECT 1 FROM YourTable WHERE NumberTable.Number = YourTable.ColumnA)
-- Adjust this value or calculate it with a query to the maximum of the source table
AND NumberTable.Number < 230130
DECLARE #t TABLE (ID INT,Val DECIMAL(10,2))
INSERT INTO #t (ID,Val) VALUES (1,10.75)
INSERT INTO #t (ID,Val) VALUES (4,6.75)
INSERT INTO #t (ID,Val) VALUES (7,4.75)
declare #MinNo int
declare #MaxNo int
declare #IncrementStep int
set #MinNo = 1
set #MaxNo = 10
set #IncrementStep = 1
;with C as
(
select #MinNo as Num
union all
select Num + #IncrementStep
from C
where Num < #MaxNo
)
select Num,
CASE WHEN Val IS NOT NULL THEN Val ELSE 0.00 END AS NUMBER
from C
LEFT JOIN #t t
ON t.ID = c.Num
You could use a number-table or following trick to generate a sequence which you can LEFT OUTER JOIN with your table. I assume you want to determine the boundaries dynamically:
WITH Seq AS
(
SELECT TOP ((SELECT Max(ColumnA)FROM Table1) - (SELECT Min(ColumnA) FROM Table1) + 1)
Num = (SELECT Min(ColumnA) FROM Table1)+ Row_number() OVER (ORDER BY [object_id]) -1
FROM sys.all_objects)
SELECT ColumnA = Seq.Num,
ColumnB = COALESCE(t.ColumnB ,0.00)
FROM Seq
LEFT OUTER JOIN Table1 t
ON Seq.Num = t.ColumnA
Demo with your sample.
Worth reading: http://www.sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
I have my collect of table functions like these.
create function dbo.GetNumbers(#Start int, #End int)
returns #Items table
(
Item int
)
as
begin
while (#Start <= #End)
begin
insert into #Items
values (#Start)
set #Start = #Start + 1
end
return
end
Then I can use it to left join to my data table and every value will be there.
declare #min int, #max int
set #min = 10
set #max = 20
select gn.Item
from dbo.GetNumbers(#min, #max) gn
I have similar table functions for date ranges, times, timezones, etc.
Arrrgh, I am not getting this.
I have a table of accounts from Dynamics GP that has 7 columns. I need to fill in the blank months for accounts that didn't have any activity for a given month.
I have created an in memory table #MONTHS that has lines like so:
Account, Description, Year, Month, Month Name, Netchange, PeriodBal
1110000, NULL, 2006, 1, NULL, 0, NULL
This should match up with the same information coming from Dynamics GP. A similar line from GP would look like this:
1110000, Petty Cash, 2006, 1, January, 15.00, 343.97
If we did not spend any petty cash in February, then there would be no line for that account in 2/2006, I want to make the #MONTHS table RIGHT JOIN with the DynamicsGP table so that empty months are filled in.
Here's the abbreviated SQL shortened for readability:
SELECT Z.GPACCOUNTNO,
Z.DESCRIPTION,
Z.FISCALYEAR,
Z.FISCALPERIOD,
Z.FISCALPERIODNAME,
Z.NETCHANGE,
Z.PERIODBALANCE
FROM Z
RIGHT JOIN #MONTHS M
ON Z.GPACCOUNTNO = M.GPACCOUNTNO
AND Z.FISCALPERIOD = M.FISCALPERIOD
AND Z.FISCALYEAR = M.FISCALYEAR
The SQL just runs forever. (i.e. 5 minutes before I lose my patience)
I have verified that my #MONTHS table looks like I intend. I have tried doing a "UNION ALL" with the two tables and it gives me duplicates.
If Table Z does not have a current line for a given account/year/month, I want my #MONTHS table to add that line with a Netchange balance of 0.
Thank you for your help. The full SQL is below.
/* Create in memory table to hold account numbers */
DECLARE #i int
DECLARE #c int
DECLARE #ACCT char(129)
DECLARE #numrows int
DECLARE #numyears int
DECLARE #y int
DECLARE #m int
DECLARE #ACCT_TABLE TABLE (
idx smallint Primary Key IDENTITY(1,1),
account char(129)
)
/* Populate account number table */
INSERT #ACCT_TABLE
select distinct ACTNUMST from SBM01.[dbo].[GL00105]
/* Year table reads available years in the DB */
DECLARE #YEAR_TABLE TABLE (
idx smallint Primary Key IDENTITY(1,1),
YEAR1 smallint
)
/* Populate year table */
INSERT #YEAR_TABLE
SELECT distinct YEAR1 FROM SBM01.dbo.SY40101 ORDER BY YEAR1
/* Create our table of months to UNION to the main accounts */
DECLARE #MONTHS table (
GPACCOUNTNO char(129),
DESCRIPTION char(51),
FISCALYEAR smallint ,
FISCALPERIOD smallint,
FISCALPERIODNAME char(21),
NETCHANGE numeric(19, 5),
PERIODBALANCE numeric(19, 5)
)
/* Here comes the heavy lifting.
We loop over the account numbers and add year and month values.
*/
SET #i = 1
SET #numrows = (SELECT COUNT(*) FROM #ACCT_TABLE)
IF #numrows > 0
WHILE(#i <= (SELECT MAX(idx) FROM #ACCT_TABLE))
BEGIN
/* Get the next account number */
SET #ACCT = (SELECT account FROM #ACCT_TABLE WHERE idx = #i)
SET #c = 1
SET #numyears = (SELECT COUNT(*) FROM #YEAR_TABLE)
WHILE(#c <= (SELECT MAX(idx) FROM #YEAR_TABLE))
BEGIN
SET #y = (SELECT YEAR1 FROM #YEAR_TABLE WHERE idx = #c)
SET #m = '0'
WHILE(#m < '13')
BEGIN
INSERT INTO #MONTHS (GPACCOUNTNO, DESCRIPTION, FISCALPERIOD, FISCALYEAR, FISCALPERIODNAME, NETCHANGE, PERIODBALANCE)
VALUES (#ACCT, NULL, #m, #y, NULL, '0', NULL)
SET #m = #m + 1
END
SET #c = #c + 1
END
SET #i = #i + 1
END
/* We should now have a populated Database */
SELECT Z.GPACCOUNTNO, Z.DESCRIPTION, Z.FISCALYEAR, Z.FISCALPERIOD, Z.FISCALPERIODNAME, Z.NETCHANGE, Z.PERIODBALANCE
FROM ( SELECT RTRIM(B.[ACTNUMST]) AS GPACCOUNTNO,
RTRIM(C.[ACTDESCR]) AS DESCRIPTION,
A.[YEAR1] AS FISCALYEAR,
A.[PERIODID] AS FISCALPERIOD,
E.[PERNAME] AS FISCALPERIODNAME,
ISNULL(A.[PERDBLNC], 0) AS NETCHANGE,
( SELECT ISNULL(SUM(D.[PERDBLNC]), 0)
FROM SBM01.[dbo].[GL10110] D
WHERE D.[ACTINDX] = A.[ACTINDX]
AND D.[YEAR1] = A.[YEAR1]
AND D.[PERIODID] <= A.[PERIODID]
) AS PERIODBALANCE
FROM SBM01.[dbo].[GL10110] A
INNER JOIN SBM01.[dbo].[GL00105] B ON B.[ACTINDX] = A.[ACTINDX]
INNER JOIN SBM01.[dbo].[GL00100] C ON C.[ACTINDX] = A.[ACTINDX]
INNER JOIN SBM01.[dbo].[SY40100] E ON E.[YEAR1] = A.[YEAR1]
AND E.[PERIODID] = A.[PERIODID]
AND E.[SERIES] = 0
UNION ALL
SELECT RTRIM(B.[ACTNUMST]) AS GPACCOUNTNO,
RTRIM(C.[ACTDESCR]) AS DESCRIPTION,
A.[YEAR1] AS FISCALYEAR,
A.[PERIODID] AS FISCALPERIOD,
E.[PERNAME] AS FISCALPERIODNAME,
ISNULL(A.[PERDBLNC], 0) AS NETCHANGE,
( SELECT ISNULL(SUM(D.[PERDBLNC]), 0)
FROM SBM01.[dbo].[GL10111] D
WHERE D.[ACTINDX] = A.[ACTINDX]
AND D.[YEAR1] = A.[YEAR1]
AND D.[PERIODID] <= A.[PERIODID]
) AS PERIODBALANCE
FROM SBM01.[dbo].[GL10111] A
INNER JOIN SBM01.[dbo].[GL00105] B ON B.[ACTINDX] = A.[ACTINDX]
INNER JOIN SBM01.[dbo].[GL00100] C ON C.[ACTINDX] = A.[ACTINDX]
INNER JOIN SBM01.[dbo].[SY40100] E ON E.[YEAR1] = A.[YEAR1]
AND E.[PERIODID] = A.[PERIODID]
AND E.[SERIES] = 0
) Z
RIGHT JOIN #MONTHS M
ON Z.GPACCOUNTNO = M.GPACCOUNTNO
AND Z.FISCALPERIOD = M.FISCALPERIOD
AND Z.FISCALYEAR = M.FISCALYEAR
ORDER BY Z.[GPACCOUNTNO],
M.[FISCALYEAR],
M.[FISCALPERIOD]
Why don't you use the #Months table as the starting point (since it already gives you all the months you need) and fill-in the values from Z if they are available?
SELECT
M.GPACCOUNTNO,
M.DESCRIPTION,
M.FISCALYEAR,
M.FISCALPERIOD,
M.FISCALPERIODNAME,
ISNULL(Z.NETCHANGE, 0) as NETCHANGE
ISNULL(Z.PERIODBALANCE, 0) as PERIODBALANCE
FROM #MONTHS M
LEFT JOIN Z
ON Z.GPACCOUNTNO = M.GPACCOUNTNO
AND Z.FISCALPERIOD = M.FISCALPERIOD
AND Z.FISCALYEAR = M.FISCALYEAR
You can use a SQL case statement to join when null
CREATE TABLE #TMP
(
id int,
[month] datetime
)
INSERT INTO #TMP(id,[month])values(1,GETDATE())
INSERT INTO #TMP(id,[month])values(2,null)
INSERT INTO #TMP(id,[month])values(3,GETDATE())
INSERT INTO #TMP(id,[month])values(4,GETDATE())
CREATE TABLE #TMP2
(
id int,
[month] datetime
)
INSERT INTO #TMP2(id,[month])values(1,GETDATE())
INSERT INTO #TMP2(id,[month])values(2,GETDATE())
INSERT INTO #TMP2(id,[month])values(3,GETDATE())
INSERT INTO #TMP2(id,[month])values(4,GETDATE())
select * from #TMP
select * from #TMP2
SELECT #TMP.[id], case when #TMP.[month] is null then #TMP2.[month] else #TMP.month end
from #tmp
inner join #tmp2 on #tmp.id= #tmp2.id
drop table #tmp,#tmp2