SQL Server 2014 Join Doubling Line Items - sql

I can usually find answers to my questions if I search hard enough but I don't know how to word my question well enough to get the results I desire.
The issue I am having is that I am trying to Join my Order Charge Table to my other Query so that I can see if there was a Discount or a Shipping charge applied.
There are 3 scenarios that can happen per order in the Order Charge Table (Discount AND Shipping charge applied) OR (Discount applied) OR (Shipping applied)
NULL values are NOT allowed so if there is no discount or shipping for the order, it does not show up in this table.
My query without the order charges applied is:
SELECT
[Order].[OrderNumber],
CASE
WHEN [ShopifyOrder].[PaymentStatusCode] = '2' THEN 'Paid'
WHEN [ShopifyOrder].[PaymentStatusCode] = '4' THEN 'Refunded'
WHEN [ShopifyOrder].[PaymentStatusCode] = '5' THEN 'Voided'
WHEN [ShopifyOrder].[PaymentStatusCode] = '6' THEN 'Partially Refunded'
END AS 'PaymentStatus',
[Store].[StoreName] as 'MarketplaceNames',
[OrderItem].[SKU],
[LookupList].[MainSKU], [LookupList].[ProductName],
[LookupList].[Classification] as 'Classification',
[LookupList].[Cost],
([OrderItem].[Quantity] * [OrderItem].[UnitPrice]) AS 'Sales',
(([OrderItem].[Quantity] * [LookupList].[Quantity]) * [LookupList].[Cost]) AS 'Total Cost',
[OrderItem].[Quantity] * [LookupList].[Quantity] AS 'Total Qty'
FROM
[SHIPSERVER].[dbo].[Order]
JOIN
[SHIPSERVER].[dbo].[ShopifyOrder] ON [Order].[OrderID] = [ShopifyOrder].[OrderID]
JOIN
[SHIPSERVER].[dbo].[OrderItem] ON [OrderItem].[OrderID] = [Order].[OrderID]
JOIN
[SHIPSERVER].[dbo].[Store] ON [Order].[StoreID] = [Store].[StoreID]
LEFT JOIN
[SHIPSERVER].[dbo].[LookupList] ON [OrderItem].[SKU] = [LookupList].[SKU]
WHERE
([Store].[StoreName]= 'Shopify')
AND ([Order].[OrderDate] BETWEEN '2015-09-01 00:00:00.000' AND '2015-09-30 23:59:59.999')
AND ([Order].[IsManual] = '0')
I am going to give 1 order as an example in the Results
+-------------+---------------+------------------+---------------+---------------+----------------+-------+-------+------------+-----------+
| OrderNumber | PaymentStatus | MarketplaceNames | SKU | MainSKU | Classification | Cost | Sales | Total Cost | Total Qty |
| 7177 | Paid | Shopify | 300TLSH SL PL | 300TLSH SL PL | Sheet Set | 21.00 | 48.99 | 21 | 1 |
+-------------+---------------+------------------+---------------+---------------+----------------+-------+-------+------------+-----------+
Here is my query when I join the OrderCharge table:
SELECT
[Order].[OrderNumber],
CASE
WHEN [ShopifyOrder].[PaymentStatusCode] = '2' THEN 'Paid'
WHEN [ShopifyOrder].[PaymentStatusCode] = '4' THEN 'Refunded'
WHEN [ShopifyOrder].[PaymentStatusCode] = '5' THEN 'Voided'
WHEN [ShopifyOrder].[PaymentStatusCode] = '6' THEN 'Partially Refunded'
END AS 'PaymentStatus',
[Store].[StoreName] as 'MarketplaceNames',
[OrderItem].[SKU],
[LookupList].[MainSKU],
[OrderCharge].[Type], [OrderCharge].[Description], [OrderCharge].[Amount],
[LookupList].[Classification] as 'Classification', [LookupList].[Cost],
([OrderItem].[Quantity]* [OrderItem].[UnitPrice]) AS 'Sales',
(([OrderItem].[Quantity] * [LookupList].[Quantity]) * [LookupList].[Cost]) AS 'Total Cost',
[OrderItem].[Quantity] * [LookupList].[Quantity] AS 'Total Qty'
FROM
[SHIPSERVER].[dbo].[Order]
JOIN
[SHIPSERVER].[dbo].[ShopifyOrder] ON [Order].[OrderID] = [ShopifyOrder].[OrderID]
JOIN
[SHIPSERVER].[dbo].[OrderItem] ON [OrderItem].[OrderID] = [Order].[OrderID]
JOIN
[SHIPSERVER].[dbo].[Store] ON [Order].[StoreID] = [Store].[StoreID]
LEFT JOIN
[SHIPSERVER].[dbo].[LookupList] ON [OrderItem].[SKU] = [LookupList].[SKU]
JOIN
[SHIPSERVER].[dbo].[OrderCharge] ON [Order].[OrderID] = [OrderCharge].[OrderID]
WHERE
([Store].[StoreName]= 'Shopify')
AND ([Order].[OrderDate] BETWEEN '2015-09-01 00:00:00.000' AND '2015-09-30 23:59:59.999')
AND ([Order].[IsManual] = '0')
Again I am going to give the same order as an example in the Results
+-------------+---------------+------------------+---------------+---------------+----------+------------------------+--------+----------------+-------+-------+------------+-----------+
| OrderNumber | PaymentStatus | MarketplaceNames | SKU | MainSKU | Type | Description | Amount | Classification | Cost | Sales | Total Cost | Total Qty |
| 7177 | Paid | Shopify | 300TLSH SL PL | 300TLSH SL PL | DISCOUNT | 15chance | -7.35 | Sheet Set | 21.00 | 48.99 | 21 | 1 |
| 7177 | Paid | Shopify | 300TLSH SL PL | 300TLSH SL PL | SHIPPING | FREE Standard Shipping | 0.00 | Sheet Set | 21.00 | 48.99 | 21 | 1 |
+-------------+---------------+------------------+---------------+---------------+----------+------------------------+--------+----------------+-------+-------+------------+-----------+
If I were to export this into an excel file, the Cost, Sales, and Total Qty fields are now all doubled for this order, this becomes an even bigger issue if there are multiple line items in an order.
I thought a solution would be to make the Discount and Shipping Fields their own columns but all that did was put Discount and Shipping on the same line but all the line items were still doubled.
I have to be over looking something.

SELECT [Order].[OrderNumber]
,CASE WHEN [ShopifyOrder].[PaymentStatusCode] = '2' THEN 'Paid'
WHEN [ShopifyOrder].[PaymentStatusCode] = '4' THEN 'Refunded'
WHEN [ShopifyOrder].[PaymentStatusCode] = '5' THEN 'Voided'
WHEN [ShopifyOrder].[PaymentStatusCode] = '6' THEN 'Partially Refunded'
END AS 'PaymentStatus'
,[Store].[StoreName] as 'MarketplaceNames'
,[OrderItem].[SKU]
,[LookupList].[MainSKU]
,[ShippingCharge].[Description] as shippingDescription
,[ShippingCharge].[Amount] as shippingAmount
,[DiscountCharge].[Description] as discountDescription
,[DiscountCharge].[Amount] as discountAmount
,[LookupList].[Classification] as 'Classification'
,[LookupList].[Cost]
,([OrderItem].[Quantity]* [OrderItem].[UnitPrice]) AS 'Sales'
,(([OrderItem].[Quantity] * [LookupList].[Quantity]) * [LookupList].[Cost]) AS 'Total Cost'
,[OrderItem].[Quantity] * [LookupList].[Quantity] AS 'Total Qty'
FROM [SHIPSERVER].[dbo].[Order]
JOIN [SHIPSERVER].[dbo].[ShopifyOrder]
ON [Order].[OrderID]=[ShopifyOrder].[OrderID]
JOIN [SHIPSERVER].[dbo].[OrderItem]
ON [OrderItem].[OrderID]=[Order].[OrderID]
JOIN [SHIPSERVER].[dbo].[Store]
ON [Order].[StoreID]=[Store].[StoreID]
LEFT JOIN [SHIPSERVER].[dbo].[LookupList]
ON [OrderItem].[SKU]=[LookupList].[SKU]
LEFT JOIN [SHIPSERVER].[dbo].[OrderCharge] [ShippingCharge]
ON [Order].[OrderID]=[ShippingCharge].[OrderID] AND [ShippingCharge].[Type] = 'SHIPPING'
LEFT JOIN [SHIPSERVER].[dbo].[OrderCharge] [DiscountCharge]
ON [Order].[OrderID]=[DiscountCharge].[OrderID] AND [DiscountCharge].[Type] = 'DISCOUNT'
WHERE ([Store].[StoreName]= 'Shopify')
AND ([Order].[OrderDate] BETWEEN '2015-09-01 00:00:00.000' AND '2015-09-30 23:59:59.999')
AND ([Order].[IsManual] = '0')
The differences are :
,[ShippingCharge].[Description] as shippingDescription
,[ShippingCharge].[Amount] as shippingAmount
,[DiscountCharge].[Description] as discountDescription
,[DiscountCharge].[Amount] as discountAmount
and
LEFT JOIN [SHIPSERVER].[dbo].[OrderCharge] [ShippingCharge]
ON [Order].[OrderID]=[ShippingCharge].[OrderID] AND [ShippingCharge].[Type] = 'SHIPPING'
LEFT JOIN [SHIPSERVER].[dbo].[OrderCharge] [DiscountCharge]
ON [Order].[OrderID]=[DiscountCharge].[OrderID] AND [DiscountCharge].[Type] = 'DISCOUNT'
Basically, what I did is I left joined on OrderCharge twice, once for Discount and once for Shipping, with a different alias each time. This means that you're potentially linked to a discount row and potentially linked to a shipping row, and from there getting the data is incredibly easy.
As #thab pointed out in comments though, there are glaring issues with this. First of all, having more than one Shipping or Discount entries will duplicate rows, at which point you would have to use a sum on the [Amount] (and probably an XML concatenation on the description). This also means that the query must be altered whenever a new Type of ChargeOrder appears.
The idea solution would be using Pivot, but I haven't dabbled with that yet so I can't help you with that one. I do believe that Pivot tables run slower though (well, at least dynamic ones do), so as long as your problem doesn't change you should be fine.

Related

SQL Return corresponding values for duplicated rows in separate column

I have over 10K codes that I want to extract from the table to and show Euro and Sterling selling price in separate columns. My code duplicates each code and returns both currencies in 1 column making over 20K rows.
I am not advanced tech guy :-( I am trying to find a better ways of doing things. I browsed through few examples which were less complicated and suggested PIVOT or Dynamic Table functions but I could not understand how to implement it in my code hence I am reaching to you guys. I know you can probably do it with closed eyes.
SQL Code
SELECT
SI.Code AS [Item Code], SI.Name AS [Item Name],
PLSA.SupplierAccountNumber AS [Supplier Code], SC.Symbol AS [Currency],
SIS.ListPrice AS [€ Selling Price], PG.Code AS [PG Code]
, PG.Description AS [PG Name], SIP.Price AS [Standard Cost]
, CASE WHEN PB.PriceBandID = 129519 THEN '£ Standard'
WHEN PB.PriceBandID = 1001 THEN '€ Standard'
ELSE 'UNKNOWN' END AS [Selling Currency Std]
, SIS.SupplierStockCode AS [Supplier Stock Code],
SIStatus.StockItemStatusName [Stock Code Status], SI.AnalysisCode8 AS
[Core / Non-Core]
, SI.AnalysisCode7 AS [Product Chart], SI.AnalysisCode6 AS [Website Product]
FROM StockItem SI
INNER JOIN StockItemSupplier SIS ON SIS.ItemID = SI.ItemID
INNER JOIN PLSupplierAccount PLSA ON SIS.SupplierID =
PLSA.PLSupplierAccountID
INNER JOIN SYSCurrency SC ON PLSA.SYSCurrencyID = SC.SYSCurrencyID
INNER JOIN ProductGroup PG ON PG.ProductGroupID = SI.ProductGroupID
INNER JOIN StockItemPrice SIP ON SIP.ItemID = SI.ItemID
INNER JOIN PriceBand PB ON PB.PriceBandID = SIP.PriceBandID
INNER JOIN StockItemStatus SIStatus ON SIStatus.StockItemStatusID =
SI.StockItemStatusID
the result is
| Item Code |... |Selling Currency Std|
----------------------------
| M1 | | €1.00 |
| M1 | | £0.90 |
| M2 | | €5.00 |
| M2 | | £4.50 |
| M3 | | €9.99 |
What I want it to be:
| Item Code |... |Selling Currency Std €|Selling Currency Std £|
------------------------------------------------------------------
| M1 | | €1.00 | £0.90|
| M2 | | €5.00 | £4.50|
| M3 | | €9.99 | £8.99|
I would suggest you to use a something like excel's sumif, only sum the value if condition is true. If you have no duplicate for (Item, Currency) the sum will only add up 2 values, one always 0 and the other is the actual SalesPrice in the specific currency.
; with ItemPriceList as
(select
SI.Code as [Item Code]
, sum(iif(PB.PriceBandID = 1001, SIP.Price, 0)) as [Selling Currency Std €]
, sum(iif(PB.PriceBandID = 129519, SIP.Price, 0)) as [Selling Currency Std £]
from
StockItem SI
inner join StockItemPrice SIP on SIP.ItemID = SI.ItemID
inner join PriceBand PB on PB.PriceBandID = SIP.PriceBandID
group by
SI.Code
)
select
IPL.[Item Code]
, IPL.[Selling Currency Std €]
, IPL.[Selling Currency Std £]
from
ItemPriceList IPL
--inner join the additional data for analysis
Oh, and don't brother with the additional information, like [Supplier Code], [PG Code], etc. while you're collecting the Sales Price, they can be added later on, that's why I used a CTE.

How to count number of clients who have not paid their's first loan repayment in time?

I have a table which looks like
ContractID | RepaymentNumber | MaturityDateID | PaymentDateID | Amount
242105 | 1 | 20170605 | 20170604 | 4825
322105 | 32 | 20170608 | 20170601 | 825
245105 | 6 | 20170611 | 20170804 | 148
578105 | 11 | 20170711 | 20170809 | 0
578185 | 21 | 20170712 | 19000101 | 3541
Where MaturityDateID is an date (in INT) to which a client should pay his loan according to his repaymentschedule and paymentDateID is a date in INT when he really send the payment. Amount = 0 means a client has a postpone in his repaymentschedule. RepaymentNumber is a count number of client's payment.
Now, I need to count a number of clients who's first repayment for a loan is PaymentDateID > MaturityDateID. The issue is the can have countless number of postpones, so for the first repayment, the column RepaymentNumber can be arbitary.
I've tried:
;WITH Nots AS (
SELECT
c.ApplicationID
--,rs.Amount
,RANK() OVER (ORDER BY rs.Amount) AS RankID
FROM
dim.Contract c
JOIN dim.Application a ON c.ApplicationID = a.ApplicationID
JOIN dim.Calendar cal ON a.ApplicationDateID = cal.DateId
JOIN dim.CreditAdvisor ca ON a.OriginalCreditAdvisorID = ca.CreditAdvisorId
JOIN dim.RepaymentSchedule rs ON c.ContractID = rs.ContractID
WHERE
((cal.CalendarYear >= 2016) AND (rs.MaturityDateID < 20170811)) -- Since given year to this date
AND ((rs.PaymentDateID = 19000101) OR (rs.PaymentDateID > rs.MaturityDateID))
GROUP BY
1
)
SELECT *
FROM Nots
WHERE RankID > 1
Any help would be appreciated. Thanks in advance.
If the issue is just that postpones, defined as those rows with Amount = 0 don't count as payments and you want the first "real" payment, you would just need to exclude those rows with Amount = 0. The WHERE statement would then be:
WHERE
((cal.CalendarYear >= 2016) AND (rs.MaturityDateID < 20170811)) -- Since given year to this date
AND ((rs.PaymentDateID = 19000101) OR (rs.PaymentDateID > rs.MaturityDateID))
AND rs.Amount > 0

SQL 2 Left outer joins with Sum and Group By

Looking for some guidance on this. I am attempting to run a report in my complaint management system.. Complaints by Year, Location, Subcategory, Showing Totals for TotalCredits (child table) and TotalsCwts (childtable) as well as total ExternalRootCause (on master table).
This is my SQL, but the TotalCwts and TotalCredits are not being calculated correctly. It calculates 1 time for each child record rather than the total for each master record.
SELECT
dbo.Complaints.Location,
YEAR(dbo.Complaints.ComDate) AS Year,
dbo.Complaints.ComplaintSubcategory,
COUNT(Distinct(dbo.Complaints.ComId)) AS CustomerComplaints,
SUM(DISTINCT CASE WHEN (dbo.Complaints.RootCauseSource = 'External' ) THEN 1 ELSE 0 END) as ExternalRootCause,
SUM(dbo.ComplaintProducts.Cwts) AS TotalCwts,
Coalesce(SUM(dbo.CreditDeductions.CreditAmount),0) AS TotalCredits
FROM dbo.Complaints
JOIN dbo.CustomerComplaints
ON dbo.Complaints.ComId = dbo.CustomerComplaints.ComId
LEFT OUTER JOIN dbo.CreditDeductions
ON dbo.Complaints.ComId = dbo.CreditDeductions.ComId
LEFT OUTER JOIN dbo.ComplaintProducts
ON dbo.Complaints.ComId = dbo.ComplaintProducts.ComId
WHERE
dbo.Complaints.Location = Coalesce(#Location,Location)
GROUP BY
YEAR(dbo.Complaints.ComDate),
dbo.Complaints.Location,
dbo.Complaints.ComplaintSubcategory
ORDER BY
[YEAR] desc,
dbo.Complaints.Location,
dbo.Complaints.ComplaintSubcategory
Data Results
Location | Year | Subcategory | Complaints | External RC | Total Cwts | Total Credits
---------------------------------------------------------------------------------------
Boston | 2016 | Documentation | 1 | 0 | 8 | 8.00
Data Should Read
Location | Year | Subcategory | Complaints | External RC | Total Cwts | Total Credits
---------------------------------------------------------------------------------------
Boston | 2016 | Documentation | 1 | 0 | 4 | 2.00
Above data reflects 1 complaint having 4 Product Records with 1cwt each and 2 credit records with 1.00 each.
What do I need to change in my query or should I approach this query a different way?
The problem is that the 1 complaint has 2 Deductions and 4 products. When you join in this manner then it will return every combination of Deduction/Product for the complaint which gives 8 rows as you're seeing.
One solution, which should work here, is to not query the Dedustion and Product tables directly; query a query which returns one row per table per complaint. In other words, replace:
LEFT OUTER JOIN dbo.CreditDeductions ON dbo.Complaints.ComId = dbo.CreditDeductions.ComId
LEFT OUTER JOIN dbo.ComplaintProducts ON dbo.Complaints.ComId = dbo.ComplaintProducts.ComId
...with this - showing the Deductions table only, you can work out the Products:
LEFT OUTER JOIN (
select ComId, count(*) CountDeductions, sum(CreditAmount) CreditAmount
from dbo.CreditDeductions
group by ComId
) d on d.ComId = Complaints.ComId
You'll have to change the references to dbo.CreditDedustions to just d (or whatever you want to call it).
Once you've done them both then you'll one each per complaint, which will result with 1 row per complaint contaoining the counts and totals from the two sub-tables.

SQL - Joining multiple rows into one, including blank values

I have been searching for answers on this but found none. I think it might be due to the fact that I don't know what terms to search for. Also, this is my first post, so I want to apologise if I use the incorrect formats.
I need to following output:
Invoice | Inv Date | Created by | Destination | Goods $ | Freight | Insurance $ |
33813..| 12 Dec ..| Jack ........| Cape Town | 250.00 ..| 50.00 ..| 10.00 ...|
33814..| 12 Dec ..| Jenny .........| Durban ......| 5,000.00| 20.00 ..| ....|
The first 5 columns are build from various columns using the Invoice column as an index.
Then I want to add the freight and insurance. This is hosted in a different table with the below layout:
InvCostID | Invoice | Cost Code | Cost Description | Value |<br/>
556 ..........| 33813 .| 1 ...............| Freight ...............| 50.00 |
559 ..........| 33813 .| 2 ...............| Insurance ...........| 10.00 |
570 ..........| 33814 .| 1 ...............| Freight ...............| 20.00 |
The problem is that I cannot just select columns to include as the Freight and insurance are in different rows. To get around this, I have created two 'sub tables'. One were the Cost code is 1 (thus, the freight table) and one with the cost code 2 (the insurance table).
Then I just select the Value from the correct table.
The problem: If one of the cost components does not exist (like the Insurance for invoice 33814), my current query excludes that invoice from the results completely. (with the above tables, my below code would only show invoice 33813.
select
IT.Invoice as 'Invoice',
IT.InvDate as 'Inv Date',
UT.UserFullName as 'Created by',
IT.DestinationDescription as 'Destination',
IT.USDVal as 'Goods $',
FREIGHTLIST.Value as 'FREIGHT $',
INSURANCELIST.Value as 'INSURANCE $'
from InvoiceTable IT,
UserTable UT,
(select * from SundryCostTable where CostCode = 1) as FREIGHTLIST,
(select * from SundryCostTable where CostCode = 2) as INSURANCELIST
where IT.InvDate > '2014-12-01'
and IT.UserId = UT.UserId
and IT.Invoice = FREIGHTLIST.Invoice
and IT.Invoice = INSURANCELIST.Invoice
Please help.
Thank you
Nico
(I am using SQL Server Management Studio to run the query)
select
IT.Invoice as 'Invoice',
IT.InvDate as 'Inv Date',
UT.UserFullName as 'Created by',
IT.DestinationDescription as 'Destination',
IT.USDVal as 'Goods $',
FREIGHTLIST.Value as 'FREIGHT $',
INSURANCELIST.Value as 'INSURANCE $'
from InvoiceTable IT
join UserTable UT on IT.UserId = UT.UserId
left join SundryCostTable as FREIGHTLIST on CostCode = 1 and IT.Invoice = FREIGHTLIST.Invoice
left join SundryCostTable as INSURANCELIST on CostCode = 2 and IT.Invoice = INSURANCELIST.Invoice
where IT.InvDate > '2014-12-01'
Note the use of LEFT JOIN in order to not exclude rows from IT and UT in case there's no match with FREIGHTLIST or INSURANCELIST.
Take a look at this post to learn more about the use of JOIN(s).
You might as well use a subquery instead of the LEFT JOIN as described by Aquillo. the following query takes care of the NULL values for FREIGHT and INSURANCE.
SELECT
IT.Invoice as 'Invoice',
IT.InvDate as 'Inv Date',
UT.UserFullName as 'Created by',
IT.DestinationDescription as 'Destination',
IT.USDVal as 'Goods $',
ISNULL((select Top 1 Value from SundryCostTable where CostCode = 1 And SundryCostTable.Invoice=IT.Invoice),0) as 'FREIGHT $',
ISNULL((select Top 1 Value from SundryCostTable where CostCode = 2 And SundryCostTable.Invoice=IT.Invoice),0) as 'INSURANCE $'
from InvoiceTable IT,
UserTable UT
where IT.InvDate > '2014-12-01'
and IT.UserId = UT.UserId

Calculations over Multiple Rows SQL Server

If I have data in the format;
Account | Period | Values
Revenue | 2013-01-01 | 5432
Revenue | 2013-02-01 | 6471
Revenue | 2013-03-01 | 7231
Costs | 2013-01-01 | 4321
Costs | 2013-02-01 | 5672
Costs | 2013-03-01 | 4562
And I want to get results out like;
Account | Period | Values
Margin | 2013-01-01 | 1111
Margin | 2013-02-01 | 799
Margin | 2013-03-01 | 2669
M% | 2013-01-01 | .20
M% | 2013-02-01 | .13
M% | 2013-03-01 | .37
Where Margin = Revenue - Costs and M% is (Revenue - Costs)/Revenue for each period.
I can see various ways of achieving this but all are quite ugly and I wanted to know if there was elegant general approach for these sorts of multi-row calculations.
Thanks
Edit
Some of these calculations can get really complicated like
Free Cash Flow = Margin - Opex - Capex + Change in Working Capital + Interest Paid
So I am hoping for a general method that doesn't require lots of joins back to itself.
Thanks
Ok, then just Max over a Case statement, like such:
with RevAndCost as (revenue,costs,period)
as
(
select "Revenue" = Max(Case when account="Revenue" then Values else null end),
"Costs" = MAX(Case when account="Costs" then values else null end),
period
from data
group by period
)
select Margin = revenue-costs,
"M%" = (revenue-costs)/nullif(revenue,0)
from RevAndCost
Use a full self-join with a Union
Select 'Margin' Account,
coalesce(r.period, c.period) Period,
r.Values - c.Values Values
From myTable r
Full Join Mytable c
On c.period = r.period
Union
Select 'M%' Account,
coalesce(r.period, c.period) Period,
(r.Values - c.Values) / r.Values Values
From myTable r
Full Join Mytable c
On c.period = r.period
Here I use a Common Table Expression to do a full outer join between two instances of your data table to pull in Revenue and Costs into 1 table, then select from that CTE.
with RevAndCost as (revenue,costs,period)
as
(
select ISNULL(rev.Values,0) as revenue,
ISNULL(cost.values,0) as costs,
ISNULL(rev.period,cost.period)
from data rev full outer join data cost
on rev.period=cost.period
)
select Margin = revenue-costs,
"M%" = (revenue-costs)/nullif(revenue,0)
from RevAndCost
I'd do it like this:
SELECT r.PERIOD, r.VALUES AS revenue, c.VALUES AS cost,
r.VALUES - c.VALUES AS margin, (r.VALUES - c.VALUES) / r.VALUES AS mPct
FROM
(SELECT PERIOD, VALUES FROM t WHERE
ACCOUNT = 'revenue') r INNER JOIN
(SELECT PERIOD, VALUES FROM t WHERE
ACCOUNT = 'costs') c ON
r.PERIOD = c.PERIOD