Showing posts with label SQL. Show all posts
Showing posts with label SQL. Show all posts

4/10/13

Computing the Trimmed Mean Average in SQL

This article by Bob Newstadt presents code to compute a trimmed mean in SQL. The trimmed mean is a more robust version of the simple mean (SQL AVG() aggregate function). It is a useful tool for summarizing ill-behaved real world data.

We all use statistics to help us understand the world. Think of batting averages, grade point averages, or the oft-quoted median price of a single family home. However, averages of real word data can be misleading. Two common problems are having too few samples or having wild values known as outliers.

A widely applicable technique to deal with these issues is the trimmed mean. The trimmed mean computation discards extreme values and averages the remaining samples. The amount of trimming can be tuned to fit the problem. Ideally, this avoids the outliers which can plague the mean while otherwise using as much of the data as possible.

This article presents several ways to compute a trimmed mean in SQL. Among the solutions is code which yields the mean, the median, or something in between depending on the amount of trimming you specify. These queries have been tested using SQL Server 2000 and may use some non-standard extensions.

Sample data

Let’s create a table variable containing a set of scores.
Declare @TestScores table (StudentID int, Score int)
insert @TestScores (StudentID, Score) Values (1,  20)
insert @TestScores (StudentID, Score) Values (2,  03)
insert @TestScores (StudentID, Score) Values (3,  40)
insert @TestScores (StudentID, Score) Values (4,  45)
insert @TestScores (StudentID, Score) Values (5,  50)
insert @TestScores (StudentID, Score) Values (6,  20)
insert @TestScores (StudentID, Score) Values (7,  90)
insert @TestScores (StudentID, Score) Values (8,  20)
insert @TestScores (StudentID, Score) Values (9,  11)
insert @TestScores (StudentID, Score) Values (10, 30)

The average is thrown off by the “curve-wrecking” student 7 who earned a score of 90.
select avg(cast(score as float)) as mean from @testscores

32.899999999999999
If that high score were 900 the mean would be really out of whack, all due to one far out value.

Trimming the smallest and largest values

This trick will compute the mean excluding the smallest and largest values.
select (sum(score)-min(score)-max(score)) / cast(count(*)-2 as float) 
  as meantrimmedby1 
from @testscores

29.5
Of course there must be more than 2 scores for this to work.

Trimming the N smallest and largest values

This code removes the smallest N and largest N scores before computing the average. N is a variable set at run time.
declare @N int
set @N = 3

select @N as N, avg(cast(score as float)) as TrimmedMeanN
from @TestScores a
where
 (select count(*) from @TestScores aa
  where aa.Score <= a.Score) > @N
 and
 (select count(*) from @TestScores bb
  where bb.Score >= a.Score) > @N

3, 26.0
The where clause keeps only the scores which fall between the N largest and the N smallest values. The correlated subqueries in the where clause rank each a.score compared to all scores in @TestScores. Duplicate values are either all removed or all retained. For example, if all scores are the same then none of them will be trimmed no matter what N is. Apply this algorithm only when there are at least 2N scores.

Trimming the smallest and largest percentile

A more general approach is to trim by a fixed percentage instead of a fixed number. Here we trim by a factor between 0.0 and 0.5. Trimming by 0.0 trims nothing yielding the mean. Trimming by .25 discards the scores in the top and bottom quartiles and averages what’s left. Trimming by .5 yields the weighted median. The median is weighted when there are duplicate values. In this example the central values are 20,20,20,30 which average out to 22.5. Compare this to the non-weighted median 25.0 ((20+30)/2).
declare @pp float
set @pp = .5

select @pp as factor, avg(cast(score as float)) as TrimmedMeanP
from @TestScores a
where
 (select count(*) from @TestScores aa
  where aa.Score <= a.Score) >= 
   (select @pp*count(*) from @TestScores)
 and
 (select count(*) from @TestScores bb
  where bb.Score >= a.Score) >= 
   (select @pp*count(*) from @TestScores)

.5, 22.5
This code is similar to the previous query except @N is replaced by @pp*count(*). The relation > was changed to >= so that a factor of .5 generates the weighted median instead of trimming all samples.
We can rewrite this solution to improve performance. The following code cuts the number of table scans in half.
declare @pp float
set @pp = .5

select @pp as factor, sum(cast(score as float)*weight)/sum(weight) as TrimmedMeanP2
from
 (
 select
  a.score,
  count(*) as weight
 from @TestScores a
 cross join @TestScores b
 group by a.score
 having
  sum(case when b.Score <= a.Score
   then 1 else 0 end) >= @pp*count(*)
  and 
  sum(case when b.Score >= a.Score
   then 1 else 0 end) >= @pp*count(*)
 ) as x1

.5, 22.5
The @TestScores table is cross joined with itself to permit comparisons of every score in the table ‘a’ with every score in table ‘b’. The results are grouped by a.score. Thus there will be at most one row in the derived table for every distinct value of a.score. In this example there are 3 scores with the value 20 causing the join to evaluate 30 rows (3*10) for that group. The having clause retains those a.score groups near the center of the distribution. The derived table generates a weight with each score proportional to the number of duplicate values there are for that score in the original table. Finally, the outer select calculates the weighted average of the retained grouped scores.

Trimming using TOP

You may be thinking: Why go to all this trouble when TOP and ORDER BY can easily filter rows from a table? The TOP operator has some limitations which are inconvenient to work around.
TOP’s argument N can not be a variable. Until you upgrade to Yukon, the next version of SQL SERVER 2000, you will need to resort to dynamic SQL if N is variable. Many DBAs try to avoid dynamic SQL for security and performance reasons.
TOP applies to the whole result set. This makes it hard to compose some complex queries which depend on TOP. Consider computing the trimmed mean of each student’s scores. Using TOP you would need to have a cursor to process each student’s scores separately. The query from the previous section can be extended to handle this problem without using cursors.
If just a single result is required and if the amount of trimming is not variable then using TOP may work. Here’s an example of computing the left median using TOP.
select top 1 Score as medianByTOP
from (select top 50 percent Score
 from @TestScores 
 order by Score) as x
order by Score desc

20
This code takes the max value in the bottom half of the distribution. A 25% trimmed mean using TOP can be coded as:
select avg(cast(score as float)) as TrimmedMean25pByTOP
from (select top 66.666 percent Score
 from (select top 75 percent Score
  from @TestScores 
  order by Score desc) as x
 order by Score) as y

29.166666666666668
The inner derived table trims the lowest 25%. The outer derived table trims the highest 25% of the original. The select clause averages the middle 50% of the distribution (66.666% of 75%=50%).

http://www.sqlteam.com/article/computing-the-trimmed-mean-in-sql

3/15/12

Change Table Structure in SQl Server 2008

We recently upgraded to SQL 2008R2 and everything has gone well.  Until I tried to create a new field in a table and also when I tried to modify a field type.  I kept getting the following error:

Saving Changes is Not Permitted.The Changes you Have Made require the following tables to be dropped and re-created.you have either made changes to a table that can't be re-created or enabled the option prevent saving changes that require the table to be re-created.
Rather quickly I found the following solution while searching the internet.  Therefore I wanted to share the solution.

An option in SSMS "prevent saving changes that require the table to be recreated" is the culprit

Therefore, go to the following to correct.
Tools - Options - Designers - Table and Database Designers and de-select "prevent saving changes that require the table to be recreated"

4/4/11

SQL - Function - Month Name

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Fn_MonthName]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[Fn_MonthName]
go
Create function Fn_MonthName (@MonthCode varchar(2))
Returns Varchar (15)
As
/*------------------------------------------------------------------------------------------------------------------------------------------
** SQLVersion : SQL 2000
** Function    : Fn_FinMonth
** Author     : Kartik M Kumar
** DateTime    : 29 January 2011 21:27
** Version    : 1.1
** Purpose    : To get the Month Name from the respective calander month
** ToCheck    :
** Changes    :
------------------------------------------------------------------------------------------------------------------------------------------*/
/*
-- Start of Debugging Stuff
    Declare @MonthCode varchar(2)
    Set @MonthCode = '03' -- Pass the Month Number as Input
-- End of Debugging Stuff
*/
Begin
    Declare @MonthName varchar(15)
    DECLARE @IMonthCd as int
    set @IMonthCd = cast(@MonthCode as Int)
    Select @MonthName = case @IMonthCd
        when 1 then 'January'
        when 2 then 'Feburary'
        when 3 then 'March'
        when 4 then 'April'
        when 5 then 'May'
        when 6 then 'June'
        when 7 then 'July'
        when 8 then 'August'
        when 9 then 'September'
        when 10 then 'October'
        when 11 then 'November'
        when 12 then 'December'
                end
Return Isnull(@MonthName, 'InvalidMonth')
end
go

http://www.sqlservercentral.com/scripts/Month+Name/72408/

3/4/11

Making a Trigger Fire On Column Change

I was working with some SQL Server triggers today at work, the triggers were used to track changes to a Price column on a table. Whenever the Price column changed, we wanted to track that in a separate table so we could have a price history. So I setup the trigger on the Price column and set it to fire on INSERTs and UPDATEs. Here was the trigger at this point:

CREATE TRIGGER trg_SavePriceHistory ON myTable
FOR INSERT, UPDATE
AS
IF UPDATE(Price)
BEGIN
DECLARE @newPrice decimal(18,2)
DECLARE @itemId int
SET @newPrice = (SELECT Price FROM Inserted)
SET @itemId = (SELECT ItemID FROM Inserted)
INSERT INTO PriceHistory (NewPrice, ItemID) VALUES (@newPrice, @itemId)
END

Its pretty straightforward, check to see if the Price column was updated and if it was then make a new entry in the PriceHistory table with the new price and the item�s id. After a little while, I realized that every update statement that included the Price column was setting off the trigger. It made sense, I guess I assumed that it would only fire when the value of the Price column actually changed, not if it simply got written with the same value. For instance, if I ran the following INSERT statement:

INSERT INTO myTable (Price) VALUES (10);

Then the trigger would fire and the price would get logged. Now suppose I update the record that I just inserted with the same value for price (assume the id = 1):

UPDATE myTable SET Price = 10 WHERE ItemID = 1;

Now the trigger will fire again, which is what I don�t want. I only wanted the trigger to fire if the value had changed. I was kind of scratching my head, being new to triggers and all, about how I could get that to work. I started writing an email to an internal mailing list, when it hit me in one of those �Aha!� moments. Inside each trigger are 2 special tables called �Inserted� and �Deleted�. The Deleted table holds the values of the record before its state was changed by my UPDATE statement and the Inserted table holds the values of record after my UPDATE statement. All I had to do was compare the Price columns from each table and see if they were different; if they were the same, then I could just exit my trigger. So heres what the trigger looked like after I modified it (modifications in red):

CREATE TRIGGER trg_SavePriceHistory ON myTable
FOR INSERT, UPDATE
AS
IF UPDATE(Price)
BEGIN
DECLARE @newPrice decimal(18,2)
DECLARE @oldPrice decimal(18,2)
DEClARE @itemId int
SET @newPrice = (SELECT Price FROM Inserted)
SET @oldPrice = (SELECT Price FROM Deleted)
IF @newPrice != @oldPrice
BEGIN
SET @itemId = (SELECT ItemID FROM Inserted)
INSERT INTO PriceHistory (NewPrice, ItemID) VALUES (@newPrice, @itemId)
END
END

This is all probably very obvious to someone familiar with triggers, just thought I would help someone else out if they were looking for this.
-----------------------------------------------------------------------
COMMENT:
Hi Ben,

I may be wrong but I don’t think your code will work correctly when updating sets: the inserted and deleted tables are what thet are: tables and not rows.
You are logging the changes to one row, not a set.

The following code works for an update trigger.
You can add similar code for delete and insert statements (or you can change this one to cover the three possibilities)

INSERT INTO PriceHistory(ItemId, OldPrice, NewPrice)
SELECT I.ItemId, D.Price, I.Price
FROM INSERTED I INNER JOIN DELETED D ON I.ItemId = D.ItemId
WHERE I.Price != D.Price

Kind regards,

Karel Vandenhove

http://benreichelt.net/blog/2005/12/13/making-a-trigger-fire-on-column-change

1/25/11

Excellent SQL Server Backup and Maintenance Solution

One of the first things I have to do after I’ve installed Dynamics GP and created the DYNAMICS, TWO and company databases, is setup jobs to backup and optimize those databases. The best method I’ve found to do that is a free solution developed by Ola Hallengren that can be found here. This solution received the 2010 Editor’s Choice Bronze Award for best Free SQL Tool, while the SQL Server Community gave it the Gold. After a previous write-up of this solution Ola contacted me directly asking for my feedback, and he has been in touch several times since – he clearly cares about his solution and makes regular improvements as SQL Server features are added or changed.

The solution can be installed by downloading and running a single sql script – direct link here. Upon opening the script in SQL Management Studio, I typically make two changes. First I change the value of the @BackupDirectory parameter, highlighted below, to point to the location I want the backups stored. The backup job will create a folder with the server name in this folder, with folders for each database below that, followed by folders for each of the backup types – full, differential, and log.

ScreenClip(9)

The second change I make is that I like to keep 3 days worth of backups on disk if possible, and the scripts defaults to cleaning up backups older than 24 hours, so I like to change that to 72 hours. This can certainly be done afterwards by editing the job step but I like to do it up front. I usually do a quick find and replace operation on “24, ” replacing with 72.

ScreenClip(10)

After the replace is completed, I run the script and the highlighted jobs below are created. From there I open each job, set the desired schedule for each one, and I’m done.

ScreenClip(11)

I use this script because I want consistently configured backup and database maintenance jobs on all of the SQL Servers I manage for my customers. It takes 10-15 minutes to configure and I love that it’s smart enough to not throw an error on the whole transaction log job if someone creates a new company and the recovery is still set to Simple – it skips that database and moves on to the next one. It is also much smarter then the SQL Server Maintenance Plans about whether it rebuilds or reorganizes the databases indexes. Visit Ola’s site, http://ola.hallengren.com/ to read more about the solution or view the documentation.



http://www.andynifong.com/blog/2011/1/24/excellent-sql-server-backup-and-maintenance-solution.html

1/24/11

Amazon's "Customers who bought this item also bought..."

This set of views is designed to show related items purchased with an inventory item in Dynamics GP.
This is similar in concept to Amazon's "Customers who bought this item also bought..."
The final view should ideally be restricted to a single item and will return items that have been bought (invoiced) with the
restricted item and the % of times they were bought together.

Using Fabrikam as a example, if you run this for item '5-Fee', you'll see that item '5-TVLLABOR' was on the same invoice as '5-Fee' 56%
of the time. The goal is to find related products for suggestive selling or product positioning.

These were built as views to allow them to be built into a Smartlist Builder based Smartlist. The use of views means that performance
suffers because the view has to hold all of the combinations and it may slow down with a large number of items and transactions.

I also have a Stored Procedure at http://www.dynamicaccounting.net that does the same thing and is more efficient.
That is a better option for custom programming, Crystal Reports or SSRS.

*/


--Create view to build denominator for % calc. This is the number of invoices per item.
Create View RelatedItems_InvoiceCount as
Select ItemNmbr as ItemNumber, COUNT(SOPNumbe) as InvoiceCount
FROM sop30300
WHERE sopnumbe IN (SELECT sopnumbe FROM SOP30300) and SOPTYPE=3
Group by ITEMNMBR

--Create view to hold the the relationships. This holds other items on the same invoice as an item.
Create View RelatedItems_Relationship as
SELECT SOP30300.SOPTYPE, SOP30300.SOPNUMBE, SOP30300.ITEMNMBR, SOP30300_2.ITEMNMBR AS RelatedItem
FROM SOP30300 INNER JOIN
SOP30300 AS SOP30300_2 ON SOP30300.SOPTYPE = SOP30300_2.SOPTYPE AND SOP30300.SOPNUMBE = SOP30300_2.SOPNUMBE
WHERE (SOP30300.SOPNUMBE IN
(SELECT SOPNUMBE
FROM SOP30300 AS SOP30300_1)) AND (SOP30300.SOPTYPE = 3) and SOP30300.ITEMNMBR <>SOP30300_2.ITEMNMBR

GROUP BY SOP30300.SOPTYPE, SOP30300.SOPNUMBE, SOP30300.ITEMNMBR, SOP30300_2.SOPNUMBE, SOP30300_2.SOPTYPE, SOP30300_2.ITEMNMBR
order by itemnmbr


--Create view to hold both related items and the % of common invoices they appeared on
Create View RelatedItems_PcntAlsoBought as
SELECT RelatedItems_Relationship.itemnmbr as ItemNumber, RelatedItems_Relationship.RelatedItem AS RelatedItem, IV00101.ITEMDESC AS Description,
Cast(cast(COUNT(SOPNUMBE) as decimal(8,2))/ CAST( RelatedItems_InvoiceCount.InvoiceCount as decimal(8,2)) *100 as decimal(8,2)) AS PcntAlsoBought
FROM RelatedItems_Relationship INNER JOIN
IV00101 ON RelatedItems_Relationship.RelatedItem = IV00101.ITEMNMBR
INNER JOIN
RelatedItems_InvoiceCount ON RelatedItems_Relationship.ItemNmbr = RelatedItems_InvoiceCount.ItemNumber
Where RelatedItems_Relationship.ITEMNMBR in (Select ITEMNMBR from IV00101)
GROUP BY RelatedItems_Relationship.itemnmbr,RelatedItems_Relationship.Relateditem, IV00101.ITEMDESC, RelatedItems_InvoiceCount.InvoiceCount

--Execute the view limiting it to one item and sorting by highest %
Select * from RelatedItems_PcntAlsoBought where ItemNumber='5-Fee'
Order by 4 desc

11/11/10

Wrong PO Number

Welcome to another edition of my blog! This time around, I want to talk -- not literally -- about a common occurrence experimented in many Dynamics GP environments. When the pressure amounts, some company buyers may find themselves accidentally overriding the PO number field, a common misshap that may cause wasted time or the need to void and re-enter the document. Now, think for an instance, if you are using the Manufacturing module or any third-party product how cumbersome the task can become.

Fortunately, there is help on the way! I have developed a script based on a previous post, that will scan for the PONUMBER field in all tables in the company database. The script will automatically produce another script in the Results pane that can be copied and pasted into a new Query window and be executed against the company database.

The following example shows the script with the new PO number (@newponumber) and the old PO number (@oldponumber) variables being used to facilitate the interfacing with the person executing the change.

DECLARE @newponumber char(25), @oldponumber char(25)
SET @newponumber = 'PO1023'
SET @oldponumber = 'PO1001'

SELECT DISTINCT 'UPDATE ' + RTRIM(objs.name) + ' SET PONUMBER = ''' + RTRIM(@newponumber) + ''' WHERE PONUMBER = ''' + RTRIM(@oldponumber) + ''''
FROM syscolumns cols
INNER JOIN sysobjects objs ON (cols.id = objs.id)
INNER JOIN sysindexes indx on (cols.id = indx.id)
WHERE (cols.name = 'PONUMBER') and (objs.xtype = 'U') and (indx.rowcnt <> 0)


When this script is executed against plain vanilla GP v10, it produces the following results:
UPDATE POP10100 SET PONUMBER = 'PO1023' WHERE PONUMBER = 'PO1001'
UPDATE POP10110 SET PONUMBER = 'PO1023' WHERE PONUMBER = 'PO1001'
UPDATE POP10310 SET PONUMBER = 'PO1023' WHERE PONUMBER = 'PO1001'
UPDATE POP10500 SET PONUMBER = 'PO1023' WHERE PONUMBER = 'PO1001'
UPDATE POP30100 SET PONUMBER = 'PO1023' WHERE PONUMBER = 'PO1001'
UPDATE POP30110 SET PONUMBER = 'PO1023' WHERE PONUMBER = 'PO1001'
UPDATE POP30310 SET PONUMBER = 'PO1023' WHERE PONUMBER = 'PO1001'
UPDATE POP40100 SET PONUMBER = 'PO1023' WHERE PONUMBER = 'PO1001'
UPDATE SOP60100 SET PONUMBER = 'PO1023' WHERE PONUMBER = 'PO1001'
(9 row(s) affected)

http://dynamicsgpblogster.blogspot.com/2008/04/wrong-po-number.html

11/9/10

SQL Code for GL Trial Balance

Today, I just decided to post a simple code snippet to generate a detailed GL Trial Balance with reference to the various sub ledgers, including the multi-dimensional information.

IF EXISTS ( SELECT * FROM dbo.sysobjects
WHERE id = OBJECT_ID(N'[dbo].[vw_GLTrialBalance]') AND OBJECTPROPERTY(id, N'IsView') = 1 )
DROP VIEW [dbo].[vw_GLTrialBalance]
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

CREATE VIEW dbo.vw_GLTrialBalance
AS SELECT dbo.GL00105.ACTNUMST AS [ACCOUNTNUMBER],
dbo.GL00100.ACTDESCR AS ACCOUNTDESCRIPTION,
dbo.GL00102.ACCATDSC AS CATEGORY,
dbo.GL00100.MNACSGMT AS [MAINACCOUNT],
CASE dbo.GL00100.ACCTTYPE
WHEN 1 THEN 'POSTING ACCOUNT'
WHEN 2 THEN 'UNIT ACCOUNT'
WHEN 3 THEN 'POSTING ALLOCATION ACCOUNT'
WHEN 4 THEN 'UNIT ALLOCATION ACCOUNT'
END AS [ACCOUNTTYPE],
CASE dbo.GL00100.ACTIVE
WHEN 1 THEN 'ACTIVE'
WHEN 0 THEN 'INACTIVE'
END AS [STATUS],
CASE dbo.GL00100.PSTNGTYP
WHEN 0 THEN 'BALANCE SHEET'
WHEN 1 THEN 'PROFT AND LOSS'
END AS [POSTINGTYPE],
CASE dbo.GL00100.TPCLBLNC
WHEN 0 THEN 'DEBIT'
WHEN 1 THEN 'CREDIT'
END AS [TYPICALBALANCE],
dbo.GL00100.ACTNUMBR_1 AS SEGMENT1,
dbo.GL00100.ACTNUMBR_2 AS SEGMENT2,
dbo.GL00100.ACTNUMBR_3 AS SEGMENT3,
dbo.GL00100.ACTNUMBR_4 AS SEGMENT4,
dbo.GL00100.ACTNUMBR_5 AS SEGMENT5,
dbo.GL00100.ACTNUMBR_6 AS SEGMENT6,
dbo.GL00100.ACTNUMBR_7 AS SEGMENT7,
dbo.GL00100.ACTNUMBR_8 AS SEGMENT8,
dbo.GL00100.ACTNUMBR_9 AS SEGMENT9,
dbo.GL00100.ACTNUMBR_10 AS SEGMENT10,
dbo.GL20000.OPENYEAR AS [FISCALYEAR],
dbo.GL20000.JRNENTRY AS [JVNUMBER],
dbo.GL20000.REFRENCE AS REFERENCE,
dbo.GL20000.DSCRIPTN AS [ADDLDESCRIPTION],
dbo.GL20000.TRXDATE AS [JVDATE],
dbo.GL20000.TRXSORCE AS [AUDITTRAIL],
dbo.GL20000.LASTUSER AS [USERID],
CASE dbo.GL20000.SERIES
WHEN 1 THEN 'ALL'
WHEN 2 THEN 'FINANCIAL'
WHEN 3 THEN 'SALES'
WHEN 4 THEN 'PURCHASING'
WHEN 5 THEN 'INVENTORY'
WHEN 6 THEN 'PAYROLL'
WHEN 7 THEN 'PROJECT'
END AS SERIES,
dbo.GL20000.ORDOCNUM AS [ORIGDOCNUMBER],
dbo.GL20000.ORMSTRNM AS [ORIGMASTERNAME],
dbo.GL20000.ORTRXSRC AS [ORIGAUDITTRAIL],
dbo.GL20000.CURNCYID AS [CURRENCYID],
dbo.GL20000.CRDTAMNT AS [CREDITAMOUNT],
dbo.GL20000.DEBITAMT AS [DEBITAMOUNT],
( dbo.GL20000.DEBITAMT - dbo.GL20000.CRDTAMNT ) NETAMOUNT,
dbo.GL20000.VOIDED AS [VOIDSTATUS],
dbo.GL20000.Back_Out_JE AS [BACKOUTJV],
dbo.GL20000.Back_Out_JE_Year AS [BACKOUTJVYEAR],
dbo.GL20000.Correcting_JE AS [CORRECTINGJV],
dbo.GL20000.Correcting_JE_Year AS [CORRECTINGJVYEAR],
dbo.GL20000.Original_JE AS [ORIGINALJV],
dbo.GL20000.Original_JE_Year AS [ORIGINALJVYEAR],
CASE MONTH(dbo.GL20000.TRXDATE)
WHEN 1 THEN 'JAN - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 2 THEN 'FEB - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 3 THEN 'MAR - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 4 THEN 'APR - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 5 THEN 'MAY - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 6 THEN 'JUN - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 7 THEN 'JUL - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 8 THEN 'AUG - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 9 THEN 'SEP - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 10 THEN 'OCT - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 11 THEN 'NOV - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
WHEN 12 THEN 'DEC - ' + ltrim(rtrim(str(dbo.GL20000.OPENYEAR)))
END AS [JVMONTH],
dbo.GL20000.PERIODID,
dbo.GL20000.SOURCDOC,
dbo.DTA10100.GROUPID ANALYSISGROUPID,
dbo.DTA10100.GROUPAMT ANALYSISGROUPAMOUNT,
dbo.DTA10200.CODEID ANALYSISCODEID,
dbo.DTA10200.POSTDESC ANALYSISPOSTINGDESC,
dbo.DTA10200.CODEAMT ANALYSISCODEAMOUNT
FROM dbo.GL20000
INNER JOIN dbo.GL00100 ON dbo.GL20000.ACTINDX = dbo.GL00100.ACTINDX
INNER JOIN dbo.GL00102 ON dbo.GL00100.ACCATNUM = dbo.GL00102.ACCATNUM
INNER JOIN dbo.GL00105 ON dbo.GL00100.ACTINDX = dbo.GL00105.ACTINDX
LEFT OUTER JOIN dbo.DTA10100 ON dbo.DTA10100.JRNENTRY = dbo.GL20000.JRNENTRY
AND dbo.DTA10100.ACTINDX = dbo.GL20000.ACTINDX
LEFT OUTER JOIN dbo.DTA10200 ON dbo.DTA10200.DTAREF = dbo.DTA10100.DTAREF
UNION
SELECT dbo.GL00105.ACTNUMST AS [ACCOUNTNUMBER],
dbo.GL00100.ACTDESCR AS ACCOUNTDESCRIPTION,
dbo.GL00102.ACCATDSC AS CATEGORY,
dbo.GL00100.MNACSGMT AS [MAINACCOUNT],
CASE dbo.GL00100.ACCTTYPE
WHEN 1 THEN 'POSTING ACCOUNT'
WHEN 2 THEN 'UNIT ACCOUNT'
WHEN 3 THEN 'POSTING ALLOCATION ACCOUNT'
WHEN 4 THEN 'UNIT ALLOCATION ACCOUNT'
END AS [ACCOUNTTYPE],
CASE dbo.GL00100.ACTIVE
WHEN 1 THEN 'ACTIVE'
WHEN 0 THEN 'INACTIVE'
END AS [STATUS],
CASE dbo.GL00100.PSTNGTYP
WHEN 0 THEN 'BALANCE SHEET'
WHEN 1 THEN 'PROFT AND LOSS'
END AS [POSTINGTYPE],
CASE dbo.GL00100.TPCLBLNC
WHEN 0 THEN 'DEBIT'
WHEN 1 THEN 'CREDIT'
END AS [TYPICALBALANCE],
dbo.GL00100.ACTNUMBR_1 AS SEGMENT1,
dbo.GL00100.ACTNUMBR_2 AS SEGMENT2,
dbo.GL00100.ACTNUMBR_3 AS SEGMENT3,
dbo.GL00100.ACTNUMBR_4 AS SEGMENT4,
dbo.GL00100.ACTNUMBR_5 AS SEGMENT5,
dbo.GL00100.ACTNUMBR_6 AS SEGMENT6,
dbo.GL00100.ACTNUMBR_7 AS SEGMENT7,
dbo.GL00100.ACTNUMBR_8 AS SEGMENT8,
dbo.GL00100.ACTNUMBR_9 AS SEGMENT9,
dbo.GL00100.ACTNUMBR_10 AS SEGMENT10,
dbo.GL30000.HSTYEAR AS [FISCALYEAR],
dbo.GL30000.JRNENTRY AS [JVNUMBER],
dbo.GL30000.REFRENCE AS REFERENCE,
dbo.GL30000.DSCRIPTN AS [ADDLDESCRIPTION],
dbo.GL30000.TRXDATE AS [JVDATE],
dbo.GL30000.TRXSORCE AS [AUDITTRAIL],
dbo.GL30000.LASTUSER AS [USERID],
CASE dbo.GL30000.SERIES
WHEN 1 THEN 'ALL'
WHEN 2 THEN 'FINANCIAL'
WHEN 3 THEN 'SALES'
WHEN 4 THEN 'PURCHASING'
WHEN 5 THEN 'INVENTORY'
WHEN 6 THEN 'PAYROLL'
WHEN 7 THEN 'PROJECT'
END AS SERIES,
dbo.GL30000.ORDOCNUM AS [ORIGDOCNUMBER],
dbo.GL30000.ORMSTRNM AS [ORIGMASTERNAME],
dbo.GL30000.ORTRXSRC AS [ORIGAUDITTRAIL],
dbo.GL30000.CURNCYID AS [CURRENCYID],
dbo.GL30000.CRDTAMNT AS [CREDITAMOUNT],
dbo.GL30000.DEBITAMT AS [DEBITAMOUNT],
( dbo.GL30000.DEBITAMT - dbo.GL30000.CRDTAMNT ) NETAMOUNT,
dbo.GL30000.VOIDED AS [VOIDSTATUS],
dbo.GL30000.Back_Out_JE AS [BACKOUTJV],
dbo.GL30000.Back_Out_JE_Year AS [BACKOUTJVYEAR],
dbo.GL30000.Correcting_JE AS [CORRECTINGJV],
dbo.GL30000.Correcting_JE_Year AS [CORRECTINGJVYEAR],
dbo.GL30000.Original_JE AS [ORIGINALJV],
dbo.GL30000.Original_JE_Year AS [ORIGINALJVYEAR],
CASE MONTH(dbo.GL30000.TRXDATE)
WHEN 1 THEN 'JAN - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 2 THEN 'FEB - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 3 THEN 'MAR - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 4 THEN 'APR - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 5 THEN 'MAY - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 6 THEN 'JUN - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 7 THEN 'JUL - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 8 THEN 'AUG - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 9 THEN 'SEP - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 10 THEN 'OCT - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 11 THEN 'NOV - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
WHEN 12 THEN 'DEC - ' + ltrim(rtrim(str(dbo.GL30000.HSTYEAR)))
END AS [JVMONTH],
dbo.GL30000.PERIODID,
dbo.GL30000.SOURCDOC,
dbo.DTA10100.GROUPID ANALYSISGROUPID,
dbo.DTA10100.GROUPAMT ANALYSISGROUPAMOUNT,
dbo.DTA10200.CODEID ANALYSISCODEID,
dbo.DTA10200.POSTDESC ANALYSISPOSTINGDESC,
dbo.DTA10200.CODEAMT ANALYSISCODEAMOUNT
FROM dbo.GL30000
INNER JOIN dbo.GL00100 ON dbo.GL30000.ACTINDX = dbo.GL00100.ACTINDX
INNER JOIN dbo.GL00102 ON dbo.GL00100.ACCATNUM = dbo.GL00102.ACCATNUM
INNER JOIN dbo.GL00105 ON dbo.GL00100.ACTINDX = dbo.GL00105.ACTINDX
LEFT OUTER JOIN dbo.DTA10100 ON dbo.DTA10100.JRNENTRY = dbo.GL30000.JRNENTRY
AND dbo.DTA10100.ACTINDX = dbo.GL30000.ACTINDX
LEFT OUTER JOIN dbo.DTA10200 ON dbo.DTA10200.DTAREF = dbo.DTA10100.DTAREF

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

GRANT SELECT ON vw_GLTrialBalance TO DYNGRP

http://cvakumar.com/msdynamics/2009/04/26/sql-code-for-gl-trial-balance/

Receivables HATB Report (using Document Date)

Today, I have decided to post a script for the Receivables Historical Aged Trial Balance (HATB), which is generated by picking transactions based on the GL posting date for the receivables transactions.

This script can also be used as a base to reconcile the sub-ledger balances as of a certain date with the corresponding GL transactions. The script for generating general ledger balances is available here.

DECLARE @ASOFDATE DATETIME
SET @ASOFDATE = '2017-04-12'
SELECT X.CUSTOMERNUMBER,
X.CORPORATECUSTNUMBER,
X.DOCUMENTNO,
X.DOCUMENTTYPE,
X.DOCUMENTDATE,
X.GLPOSTINGDATE,
X.DOCUMENTAMT,
X.APPLIEDAMT,
X.WRITEOFFAMT,
X.DISCTAKENAMT,
X.REALGAINLOSSAMT,
( X.DOCUMENTAMT - X.APPLIEDAMT - X.WRITEOFFAMT - X.DISCTAKENAMT
+ X.REALGAINLOSSAMT ) AS CURRENTAMTFROM ( SELECT Z.CUSTNMBR AS CUSTOMERNUMBER,
Z.CPRCSTNM AS CORPORATECUSTNUMBER,
Z.DOCNUMBR AS DOCUMENTNO,
Z.DOCTYPE AS DOCUMENTTYPE,
Z.DOCDATE AS DOCUMENTDATE,
Z.GLPOSTINGDATE AS GLPOSTINGDATE,
Z.ORTRXAMT AS DOCUMENTAMT,
CASE WHEN Z.[RMDTYPAL] <= 6
THEN ISNULL(( SELECT SUM(Y.[ActualApplyToAmount])
FROM ( SELECT [CUSTNMBR],
[DATE1],
[APFRDCDT],
[APTODCNM],
[APTODCTY],
[ActualApplyToAmount]
FROM dbo.[RM20201]
UNION
SELECT [CUSTNMBR],
[DATE1],
[APFRDCDT],
[APTODCNM],
[APTODCTY],
[ActualApplyToAmount]
FROM dbo.[RM30201]
) Y
WHERE Y.[DATE1] <= @ASOFDATE
AND Y.[APFRDCDT] <= @ASOFDATE
AND Y.[CUSTNMBR] = Z.CUSTNMBR
AND Y.[APTODCNM] = Z.DOCNUMBR
AND Y.[APTODCTY] = Z.RMDTYPAL
), 0)
WHEN Z.[RMDTYPAL] > 7
AND Z.[RMDTYPAL] <= 9
THEN ISNULL(( SELECT SUM(Y.[ActualApplyToAmount])
FROM ( SELECT [CUSTNMBR],
[DATE1],
[APTODCDT],
[APFRDCNM],
[APFRDCTY],
[ActualApplyToAmount]
FROM dbo.[RM20201]
UNION
SELECT [CUSTNMBR],
[DATE1],
[APTODCDT],
[APFRDCNM],
[APFRDCTY],
[ActualApplyToAmount]
FROM dbo.[RM30201]
) Y
WHERE Y.[DATE1] <= @ASOFDATE
AND Y.[APTODCDT] <= @ASOFDATE
AND Y.[CUSTNMBR] = Z.CUSTNMBR
AND Y.[APFRDCNM] = Z.DOCNUMBR
AND Y.[APFRDCTY] = Z.RMDTYPAL
), 0)
ELSE 0
END AS APPLIEDAMT,
CASE WHEN Z.[RMDTYPAL] <= 6
THEN ISNULL(( SELECT SUM(Y.[WROFAMNT])
FROM ( SELECT [CUSTNMBR],
[DATE1],
[APFRDCDT],
[APTODCNM],
[APTODCTY],
[WROFAMNT]
FROM dbo.[RM20201]
UNION
SELECT [CUSTNMBR],
[DATE1],
[APFRDCDT],
[APTODCNM],
[APTODCTY],
[WROFAMNT]
FROM dbo.[RM30201]
) Y
WHERE Y.[DATE1] <= @ASOFDATE
AND Y.[APFRDCDT] <= @ASOFDATE
AND Y.[CUSTNMBR] = Z.CUSTNMBR
AND Y.[APTODCNM] = Z.DOCNUMBR
AND Y.[APTODCTY] = Z.RMDTYPAL
), 0)
ELSE 0
END AS WRITEOFFAMT,
CASE WHEN Z.[RMDTYPAL] <= 6
THEN ISNULL(( SELECT SUM(Y.[DISTKNAM])
FROM ( SELECT [CUSTNMBR],
[DATE1],
[APFRDCDT],
[APTODCNM],
[APTODCTY],
[DISTKNAM]
FROM dbo.[RM20201]
UNION
SELECT [CUSTNMBR],
[DATE1],
[APFRDCDT],
[APTODCNM],
[APTODCTY],
[DISTKNAM]
FROM dbo.[RM30201]
) Y
WHERE Y.[DATE1] <= @ASOFDATE
AND Y.[APFRDCDT] <= @ASOFDATE
AND Y.[CUSTNMBR] = Z.CUSTNMBR
AND Y.[APTODCNM] = Z.DOCNUMBR
AND Y.[APTODCTY] = Z.RMDTYPAL
), 0)
ELSE 0
END AS DISCTAKENAMT,
CASE WHEN Z.[RMDTYPAL] > 7
AND Z.[RMDTYPAL] <= 9
THEN ISNULL(( SELECT SUM(Y.[RLGANLOS])
FROM ( SELECT [CUSTNMBR],
[DATE1],
[APTODCDT],
[APFRDCNM],
[APFRDCTY],
[RLGANLOS]
FROM dbo.[RM20201]
UNION
SELECT [CUSTNMBR],
[DATE1],
[APTODCDT],
[APFRDCNM],
[APFRDCTY],
[RLGANLOS]
FROM dbo.[RM30201]
) Y
WHERE Y.[DATE1] <= @ASOFDATE
AND Y.[APTODCDT] <= @ASOFDATE
AND Y.[CUSTNMBR] = Z.CUSTNMBR
AND Y.[APFRDCNM] = Z.DOCNUMBR
AND Y.[APFRDCTY] = Z.RMDTYPAL
), 0)
ELSE 0
END AS REALGAINLOSSAMT
FROM ( SELECT A.[CUSTNMBR],
A.[CPRCSTNM],
A.[DOCNUMBR],
A.[RMDTYPAL],
B.[DOCDESCR] AS DOCTYPE,
A.[DOCDATE],
A.[GLPOSTDT] AS GLPOSTINGDATE,
A.[ORTRXAMT]
FROM [dbo].[RM20101] A
INNER JOIN RM40401 B ON A.[RMDTYPAL] = B.[RMDTYPAL]
WHERE [VOIDSTTS] = 0
UNION
SELECT A.[CUSTNMBR],
A.[CPRCSTNM],
A.[DOCNUMBR],
A.[RMDTYPAL],
B.[DOCDESCR] AS DOCTYPE,
A.[DOCDATE],
A.[VOIDDATE] AS GLPOSTINGDATE,
A.[ORTRXAMT] * -1
FROM [dbo].[RM20101] A
INNER JOIN [dbo].[RM40401] B ON A.[RMDTYPAL] = B.[RMDTYPAL]
WHERE [VOIDSTTS] = 1
UNION
SELECT A.[CUSTNMBR],
A.[CPRCSTNM],
A.[DOCNUMBR],
A.[RMDTYPAL],
B.[DOCDESCR] AS DOCTYPE,
A.[DOCDATE],
A.[GLPOSTDT] AS GLPOSTINGDATE,
A.[ORTRXAMT]
FROM [dbo].[RM30101] A
INNER JOIN [dbo].[RM40401] B ON A.[RMDTYPAL] = B.[RMDTYPAL]
WHERE [VOIDSTTS] = 0
UNION
SELECT A.[CUSTNMBR],
A.[CPRCSTNM],
A.[DOCNUMBR],
A.[RMDTYPAL],
B.[DOCDESCR] AS DOCTYPE,
A.[DOCDATE],
A.[VOIDDATE] AS GLPOSTINGDATE,
A.[ORTRXAMT] * -1
FROM [dbo].[RM30101] A
INNER JOIN [dbo].[RM40401] B ON A.[RMDTYPAL] = B.[RMDTYPAL]
WHERE [VOIDSTTS] = 1
) Z
WHERE Z.DOCDATE <= @ASOFDATE
) X
http://cvakumar.com/msdynamics/2010/11/07/receivables-hatb-report-using-gl-posting-date/

Receivables HATB Report (using GL Posting Date)

Today, I have decided to post this script which is generated by picking transactions based on the GL posting date.

This script can also be used as a base to reconcile the sub-ledger balances as of a certain date with the corresponding GL transactions. The script for generating general ledger balances for reconciliation is available here.

DECLARE @ASOFDATE DATETIME
SET @ASOFDATE = '2017-04-12'
SELECT X.CUSTOMERNUMBER,
X.CORPORATECUSTNUMBER,
X.DOCUMENTNO,
X.DOCUMENTTYPE,
X.DOCUMENTDATE,
X.GLPOSTINGDATE,
X.DOCUMENTAMT,
X.APPLIEDAMT,
X.WRITEOFFAMT,
X.DISCTAKENAMT,
X.REALGAINLOSSAMT,
( X.DOCUMENTAMT - X.APPLIEDAMT - X.WRITEOFFAMT - X.DISCTAKENAMT
+ X.REALGAINLOSSAMT ) AS CURRENTAMTFROM ( SELECT Z.CUSTNMBR AS CUSTOMERNUMBER,
Z.CPRCSTNM AS CORPORATECUSTNUMBER,
Z.DOCNUMBR AS DOCUMENTNO,
Z.DOCTYPE AS DOCUMENTTYPE,
Z.DOCDATE AS DOCUMENTDATE,
Z.GLPOSTINGDATE AS GLPOSTINGDATE,
Z.ORTRXAMT AS DOCUMENTAMT,
CASE WHEN Z.[RMDTYPAL] <= 6
THEN ISNULL(( SELECT SUM(Y.[ActualApplyToAmount])
FROM ( SELECT [CUSTNMBR],
[GLPOSTDT],
[ApplyFromGLPostDate],
[APTODCNM],
[APTODCTY],
[ActualApplyToAmount]
FROM dbo.[RM20201]
UNION
SELECT [CUSTNMBR],
[GLPOSTDT],
[ApplyFromGLPostDate],
[APTODCNM],
[APTODCTY],
[ActualApplyToAmount]
FROM dbo.[RM30201]
) Y
WHERE Y.[GLPOSTDT] <= @ASOFDATE
AND Y.[ApplyFromGLPostDate] <= @ASOFDATE
AND Y.[CUSTNMBR] = Z.CUSTNMBR
AND Y.[APTODCNM] = Z.DOCNUMBR
AND Y.[APTODCTY] = Z.RMDTYPAL
), 0)
WHEN Z.[RMDTYPAL] > 7
AND Z.[RMDTYPAL] <= 9
THEN ISNULL(( SELECT SUM(Y.[ActualApplyToAmount])
FROM ( SELECT [CUSTNMBR],
[GLPOSTDT],
[ApplyToGLPostDate],
[APFRDCNM],
[APFRDCTY],
[ActualApplyToAmount]
FROM dbo.[RM20201]
UNION
SELECT [CUSTNMBR],
[GLPOSTDT],
[ApplyToGLPostDate],
[APFRDCNM],
[APFRDCTY],
[ActualApplyToAmount]
FROM dbo.[RM30201]
) Y
WHERE Y.[GLPOSTDT] <= @ASOFDATE
AND Y.[ApplyToGLPostDate] <= @ASOFDATE
AND Y.[CUSTNMBR] = Z.CUSTNMBR
AND Y.[APFRDCNM] = Z.DOCNUMBR
AND Y.[APFRDCTY] = Z.RMDTYPAL
), 0)
ELSE 0
END AS APPLIEDAMT,
CASE WHEN Z.[RMDTYPAL] <= 6
THEN ISNULL(( SELECT SUM(Y.[WROFAMNT])
FROM ( SELECT [CUSTNMBR],
[GLPOSTDT],
[ApplyFromGLPostDate],
[APTODCNM],
[APTODCTY],
[WROFAMNT]
FROM dbo.[RM20201]
UNION
SELECT [CUSTNMBR],
[GLPOSTDT],
[ApplyFromGLPostDate],
[APTODCNM],
[APTODCTY],
[WROFAMNT]
FROM dbo.[RM30201]
) Y
WHERE Y.[GLPOSTDT] <= @ASOFDATE
AND Y.[ApplyFromGLPostDate] <= @ASOFDATE
AND Y.[CUSTNMBR] = Z.CUSTNMBR
AND Y.[APTODCNM] = Z.DOCNUMBR
AND Y.[APTODCTY] = Z.RMDTYPAL
), 0)
ELSE 0
END AS WRITEOFFAMT,
CASE WHEN Z.[RMDTYPAL] <= 6
THEN ISNULL(( SELECT SUM(Y.[DISTKNAM])
FROM ( SELECT [CUSTNMBR],
[GLPOSTDT],
[ApplyFromGLPostDate],
[APTODCNM],
[APTODCTY],
[DISTKNAM]
FROM dbo.[RM20201]
UNION
SELECT [CUSTNMBR],
[GLPOSTDT],
[ApplyFromGLPostDate],
[APTODCNM],
[APTODCTY],
[DISTKNAM]
FROM dbo.[RM30201]
) Y
WHERE Y.[GLPOSTDT] <= @ASOFDATE
AND Y.[ApplyFromGLPostDate] <= @ASOFDATE
AND Y.[CUSTNMBR] = Z.CUSTNMBR
AND Y.[APTODCNM] = Z.DOCNUMBR
AND Y.[APTODCTY] = Z.RMDTYPAL
), 0)
ELSE 0
END AS DISCTAKENAMT,
CASE WHEN Z.[RMDTYPAL] > 7
AND Z.[RMDTYPAL] <= 9
THEN ISNULL(( SELECT SUM(Y.[RLGANLOS])
FROM ( SELECT [CUSTNMBR],
[GLPOSTDT],
[ApplyToGLPostDate],
[APFRDCNM],
[APFRDCTY],
[RLGANLOS]
FROM dbo.[RM20201]
UNION
SELECT [CUSTNMBR],
[GLPOSTDT],
[ApplyToGLPostDate],
[APFRDCNM],
[APFRDCTY],
[RLGANLOS]
FROM dbo.[RM30201]
) Y
WHERE Y.[GLPOSTDT] <= @ASOFDATE
AND Y.[ApplyToGLPostDate] <= @ASOFDATE
AND Y.[CUSTNMBR] = Z.CUSTNMBR
AND Y.[APFRDCNM] = Z.DOCNUMBR
AND Y.[APFRDCTY] = Z.RMDTYPAL
), 0)
ELSE 0
END AS REALGAINLOSSAMT
FROM ( SELECT A.[CUSTNMBR],
A.[CPRCSTNM],
A.[DOCNUMBR],
A.[RMDTYPAL],
B.[DOCDESCR] AS DOCTYPE,
A.[DOCDATE],
A.[GLPOSTDT] AS GLPOSTINGDATE,
A.[ORTRXAMT]
FROM [dbo].[RM20101] A
INNER JOIN RM40401 B ON A.[RMDTYPAL] = B.[RMDTYPAL]
WHERE [VOIDSTTS] = 0
UNION
SELECT A.[CUSTNMBR],
A.[CPRCSTNM],
A.[DOCNUMBR],
A.[RMDTYPAL],
B.[DOCDESCR] AS DOCTYPE,
A.[DOCDATE],
A.[VOIDDATE] AS GLPOSTINGDATE,
A.[ORTRXAMT] * -1
FROM [dbo].[RM20101] A
INNER JOIN [dbo].[RM40401] B ON A.[RMDTYPAL] = B.[RMDTYPAL]
WHERE [VOIDSTTS] = 1
UNION
SELECT A.[CUSTNMBR],
A.[CPRCSTNM],
A.[DOCNUMBR],
A.[RMDTYPAL],
B.[DOCDESCR] AS DOCTYPE,
A.[DOCDATE],
A.[GLPOSTDT] AS GLPOSTINGDATE,
A.[ORTRXAMT]
FROM [dbo].[RM30101] A
INNER JOIN [dbo].[RM40401] B ON A.[RMDTYPAL] = B.[RMDTYPAL]
WHERE [VOIDSTTS] = 0
UNION
SELECT A.[CUSTNMBR],
A.[CPRCSTNM],
A.[DOCNUMBR],
A.[RMDTYPAL],
B.[DOCDESCR] AS DOCTYPE,
A.[DOCDATE],
A.[VOIDDATE] AS GLPOSTINGDATE,
A.[ORTRXAMT] * -1
FROM [dbo].[RM30101] A
INNER JOIN [dbo].[RM40401] B ON A.[RMDTYPAL] = B.[RMDTYPAL]
WHERE [VOIDSTTS] = 1
) Z
WHERE Z.GLPOSTINGDATE <= @ASOFDATE
) X
http://cvakumar.com/msdynamics/2010/11/07/receivables-hatb-report-using-gl-posting-date/