SQL - Joining multiple rows into one, including blank values - sql

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

Related

Sql query group by question, creating monthly averages

I am trying to compile a table which shows three columns:
product name, average monthly sales volume and average monthly sales price for said product.
I am using adventureworks 2019.
I have written the below query based upon some help I previously received. I have summed the order quantity and unit price for each product and then in the outer query averaged them.
SELECT
Product_Name,
AVG(Sales_Volume) AS Avg_Sales_Volume,
AVG(Price) AS Avg_Price
FROM
(SELECT
PP.[Name] AS Product_Name,
SUM(SSOD.[OrderQty]) AS Sales_Volume,
SUM(SSOD.[UnitPrice]) AS Price,
FORMAT(SSOH.[OrderDate], 'MM-yyyy') AS Month_Year
FROM
[Sales].[SalesOrderHeader] AS SSOH
LEFT JOIN Sales.SalesOrderDetail AS SSOD
ON SSOH.SalesOrderID = SSOD.SalesOrderID
LEFT JOIN production.product AS PP
ON SSOD.ProductID = PP.ProductID
GROUP BY PP.[Name], SSOH.[OrderDate]) AS T
--WHERE Product_Name = 'Road-150 Red, 44' (doing this for reference)
GROUP BY T.Product_Name, Month-Year
If I group by Month-Year I get around 3600 rows, where as if I don't, I get 266. I am confused about this. Basically, I am not sure what it is actually showing.
To some of you this may seem really basic, but it feels like I cannot get my head around it.
Could anyone take a bit of time to explain this to me?
Thanks
Product_Name Avg_Sales_Volume Avg_Price
LL Mountain Frame - Silver, 48 14 844.96
LL Touring Frame - Blue, 50 26 2100.546
Women's Mountain Shorts, L 13 161.494
Road-550-W Yellow, 44 3 1890.7332
HL Road Frame - Red, 48 18 6025.3137
Mountain-500 Silver, 42 8 1395.0116
(266 rows)
Query with group by Month-Year also:
Product_Name Avg_Sales_Volume Avg_Price
Road-150 Red, 44 1 6758.9544
HL Mountain Frame - Silver, 46 15 4465.6362
AWC Logo Cap 14 76.4672
Long-Sleeve Logo Jersey, L 2 102.611
Road-150 Red, 56 2 6817.546
Mountain-500 Silver, 52 13 2118.7125
LL Touring Frame - Yellow, 62 62 5001.30
ML Mountain Frame-W - Silver, 40 115 6546.3382
(3862 rows)
A few notes first, then an explanation of what you're seeing.
First, your outer query has GROUP BY Month-Year with a dash, but I believe this is intended to be the column Month_Year (with an underscore) from the subquery.
Next, I like your use of AS to explicitly define aliases on columns, as well as your use of square brackets [ and ] around object names rather than quoted identifiers.
Finally, start getting in the practice of using schema- and alias-identifiers throughout your code - especially when working with subqueries. They'll make it much more clear which objects should be returned, and will prevent "ambiguous column" errors.
Explanation:
The difference in row-counts returned for each of your two queries is due to how those queries instruct the grouping to occur.
GROUP BY T.Product_Name; returns 266 rows because it is aggregating all of the sales volume and pricing information for the given product across all time. Changing up your subquery a bit to read:
SELECT COUNT(DISTINCT pp.Name)
FROM [Sales].[SalesOrderHeader] AS SSOH
LEFT JOIN Sales.SalesOrderDetail AS SSOD
ON SSOH.SalesOrderID = SSOD.SalesOrderID
LEFT JOIN production.product AS PP
ON SSOD.ProductID = PP.ProductID
Results in 266 rows being returned, indicating that there are 266 distinct product names included in that set.
When you add more grouping conditions (such as T.Month_Year), you are telling the engine to make "subgroups" in the aggregation structure.
GROUP BY T.Product_Name, T.Month_Year runs your aggregates for each of those distinct 266 product names as well as aggregating the data for each distinct T.Month_Year value that appears in each of your 266 product name groups.
Examining those row-counts a bit closer, the two-condition grouping returns 3,862 rows while the single-condition grouping returns 266 rows. Across those 3,862 rows there are 266 distinct product names represented an average of 14.52 times (3862 / 266 = 15.518...). If you assumed that every product had at least one sale per month, then you might conclude that we are looking at slightly more than one year's worth of sales data here. More likely, this is a set of several years of sales data with a lot of variation in sales volume and frequency between products.
ADDENDUM: Adding your GROUP BY columns to the SELECT illustrates the difference in result sets:
SELECT Product_Name,
T.Month_Year,
AVG(Sales_Volume) AS Avg_Sales_Volume,
AVG(Price) AS Avg_Price
FROM (SELECT PP.[Name] AS Product_Name,
SUM(SSOD.[OrderQty]) AS Sales_Volume,
SUM(SSOD.[UnitPrice]) AS Price,
FORMAT(SSOH.[OrderDate], 'MM-yyyy') AS Month_Year
FROM [Sales].[SalesOrderHeader] AS SSOH
LEFT JOIN Sales.SalesOrderDetail AS SSOD
ON SSOH.SalesOrderID = SSOD.SalesOrderID
LEFT JOIN production.product AS PP
ON SSOD.ProductID = PP.ProductID
GROUP BY PP.[Name], SSOH.[OrderDate]) AS T
--WHERE Product_Name = 'Road-150 Red, 44' (doing this for reference)
GROUP BY T.Product_Name,
T.Month_Year
ORDER BY Product_Name;
Examining the results shows that each product name also has records for any month in which that product sold:
+--------------------------+-----------+-------+-----------+
| ProductName | Month_Year|Avg_Vol| Avg_Price |
+--------------------------+-----------+-------+-----------+
| All-Purpose Bike Stand | 12-2013 | 1 | 193.0714 |
| All-Purpose Bike Stand | 06-2014 | 1 | 218.625 |
| All-Purpose Bike Stand | 05-2014 | 1 | 187.909 |
| All-Purpose Bike Stand | 10-2013 | 1 | 212.00 |
| AWC Logo Cap | 02-2014 | 6 | 57.7928 |
| AWC Logo Cap | 02-2012 | 48 | 93.357 |
| AWC Logo Cap | 08-2011 | 68 | 103.73 |
| AWC Logo Cap | 01-2013 | 124 | 129.4896 |
| AWC Logo Cap | 03-2014 | 21 | 71.1747 |
+--------------------------+-----------+-------+-----------+

Multi-Table Invoice SUM Comparison

Say I have 3 tables in a rails app:
invoices
id | customer_id | employee_id | notes
---------------------------------------------------------------
1 | 1 | 5 | An order with 2 items.
2 | 12 | 5 | An order with 1 item.
3 | 17 | 12 | An empty order.
4 | 17 | 12 | A brand new order.
invoice_items
id | invoice_id | price | name
---------------------------------------------------------
1 | 1 | 5.35 | widget
2 | 1 | 7.25 | thingy
3 | 2 | 1.25 | smaller thingy
4 | 2 | 1.25 | another smaller thingy
invoice_payments
id | invoice_id | amount | method | notes
---------------------------------------------------------
1 | 1 | 4.85 | credit card | Not enough
2 | 1 | 1.25 | credit card | Still not enough
3 | 2 | 1.25 | check | Paid in full
This represents 4 orders:
The first has 2 items, for a total of 12.60. It has two payments, for a total paid amount of 6.10. This order is partially paid.
The second has only one item, and one payment, both totaling 1.25. This order is paid in full.
The third order has no items or payments. This is important to us, sometimes we use this case. It is considered paid in full as well.
The final order has one item again, for a total of 1.25, but no payments as of yet.
Now I need a query:
Show me all orders that are not paid in full yet; that is, all orders such that the total of the items is greater than the total of the payments.
I can do it in pure sql:
SELECT invoices.*,
invoice_payment_amounts.amount_paid AS amount_paid,
invoice_item_amounts.total_amount AS total_amount
FROM invoices
LEFT JOIN (
SELECT invoices.id AS invoice_id,
COALESCE(SUM(invoice_payments.amount), 0) AS amount_paid
FROM invoices
LEFT JOIN invoice_payments
ON invoices.id = invoice_payments.invoice_id
GROUP BY invoices.id
) AS invoice_payment_amounts
ON invoices.id = invoice_payment_amounts.invoice_id
LEFT JOIN (
SELECT invoices.id AS invoice_id,
COALESCE(SUM(invoice_items.item_price), 0) AS total_amount
FROM invoices
LEFT JOIN invoice_items
ON invoices.id = invoice_items.invoice_id
GROUP BY invoices.id
) AS invoice_item_amounts
ON invoices.id = invoice_item_amounts.invoice_id
WHERE amount_paid < total_amount
But...now I need to get that into rails (probably as a scope). I can use find_by_sql, but that then returns an array, rather than an ActiveRecord::Relation, which is not what I need, since I want to chain it with other scopes (there is, for example, an overdue scope, which uses this), etc.
So raw SQL probably isn't the right way to go here.....but what is? I've not been able to do this in activerecord's query language.
The closest I've gotten so far was this:
Invoice.select('invoices.*, SUM(invoice_items.price) AS total, SUM(invoice_payments.amount) AS amount_paid').
joins(:invoice_payments, :invoice_items).
group('invoices.id').
where('amount_paid < total')
But that fails, since on orders like #1, with multiple payments, it incorrectly doubles the price of the order (due to multiple joins), showing it as still unpaid. I had the same problem in SQL, which is why I structured it in the way I did.
Any thoughts here?
You can get your results using group by and having clause of MySQL as:
Pure MySQL Query:
SELECT `invoices`.* FROM `invoices`
INNER JOIN `invoice_items` ON
`invoice_items`.`invoice_id` = `invoices`.`id`
INNER JOIN `invoice_payments` ON
`invoice_payments`.`invoice_id` = `invoices`.`id`
GROUP BY invoices.id
HAVING sum(invoice_items.price) < sum(invoice_payments.amount)
ActiveRecord Query:
Invoice.joins(:invoice_items, :invoice_payments).group("invoices.id").having("sum(invoice_items.price) < sum(:invoice_payments.amount)")
When building more complex queries in Rails usually Arel Really Exasperates Logicians comes in handy
Arel is a SQL AST manager for Ruby. It
simplifies the generation of complex SQL queries, and
adapts to various RDBMSes.
Here is a sample how the Arel implementation would look like based on the requirements
invoice_table = Invoice.arel_table
# Define invoice_payment_amounts
payment_arel_table = InvoicePayment.arel_table
invoice_payment_amounts = Arel::Table.new(:invoice_payment_amounts)
payment_cte = Arel::Nodes::As.new(
invoice_payment_amounts,
payment_arel_table
.project(payment_arel_table[:invoice_id],
payment_arel_table[:amount].sum.as("amount_paid"))
.group(payment_arel_table[:invoice_id])
)
# Define invoice_item_amounts
item_arel_table = InvoiceItem.arel_table
invoice_item_amounts = Arel::Table.new(:invoice_item_amounts)
item_cte = Arel::Nodes::As.new(
invoice_item_amounts,
item_arel_table
.project(item_arel_table[:invoice_id],
item_arel_table[:price].sum.as("total"))
.group(item_arel_table[:invoice_id])
)
# Define main query
query = invoice_table
.project(
invoice_table[Arel.sql('*')],
invoice_payment_amounts[:amount_paid],
invoice_item_amounts[:total]
)
.join(invoice_payment_amounts).on(
invoice_table[:id].eq(invoice_payment_amounts[:invoice_id])
)
.join(invoice_item_amounts).on(
invoice_table[:id].eq(invoice_item_amounts[:invoice_id])
)
.where(invoice_item_amounts[:total].gt(invoice_payment_amounts[:amount_paid]))
.with(payment_cte, item_cte)
res = Invoice.find_by_sql(query.to_sql)
for r in res do
puts "---- Invoice #{r.id} -----"
p r
puts "total: #{r[:total]}"
puts "amount_paid: #{r[:amount_paid]}"
puts "----"
end
This will return the same output as your SQL query using the sample data you have provided to the question.
Output:
<Invoice id: 2, notes: "An order with 1 items.", created_at: "2017-12-18 21:15:47", updated_at: "2017-12-18 21:15:47">
total: 2.5
amount_paid: 1.25
----
---- Invoice 1 -----
<Invoice id: 1, notes: "An order with 2 items.", created_at: "2017-12-18 21:15:47", updated_at: "2017-12-18 21:15:47">
total: 12.6
amount_paid: 6.1
----
Arel is quite flexible so you can use this as a base and refine the query conditions based on more specific requirements you might have.
I would strongly recommend for you to consider creating a cache columns (total, amount_paid) in the Invoice table and maintain them so you can avoid this complex query. At least the total additional column would be quite simple to create and fill the data.

SQL Server 2014 Join Doubling Line Items

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.

SQL Server - Select data from one table based on a string value

I am wanting to look up data from one of my database tables based on a varchar stored in another table.
We have a table of Manufacturers (M) and a table of Parameters (P), and rather than having to have a parameter per Manufacturer (as there is a lot of crossover between 60% of them), we thought it would be cleaner to have parameters per manufacturer where required, and for the other ones just have a set of default parameters.
This means that I cannot store ManufacturerID in both tables and create a simple link, instead I need to link using the ManufacturerName.
So I need to know if it is possible to Link via ManufacturerName, and where there is no match look in the Parameters table (P) for Manufacturer 'Default'.
As part of the link, we are also including:
P.CategoryID = M.CategoryID AND (P.PriceFrom <= M.BasePrice AND
P.PriceTo >= M.BasePrice)
Here is the data structures:
Manufacturers (M):
ManufacturerID, ManufacturerName, CategoryID, BasePrice
Parameters (P)
CategoryID, ManufacturerName, PriceFrom, PriceTo, Percentage
Here is some sample data:
Manufacturers (M):
ManufacturerID | ManufacturerName | CategoryID | BasePrice
3 | Apple | 1 | 150.00
3 | Apple | 9 | 99.99
10 | HTC | 1 | 50.00
15 | Nokia | 1 | 25.00
19 | Samsung | 1 | 80.00
Parameters (P):
CategoryID | ManufacturerName | PriceFrom | PriceTo | Percentage |
1 | Samsung | 0.00 | 99.99 | 50% |
1 | Apple | 0.00 | 99.99 | 55% |
1 | Apple | 100.00 | 149.99 | 45% |
9 | Apple | 0.00 | 99.99 | 65% |
1 | Default | 0.00 | 99.99 | 60% |
So we still need to just return 1 result in each link.
Any suggestions of guidance much appreciated.
Thanks in advance.
As per the conditions you have posted a Left Join should serve the purpose:
select M.ManufacturerID, P.ManufacturerName, M.CategoryID
from Manufacturers M
left join Parameters P On P.CategoryID = M.CategoryID AND (P.PriceFrom <= M.BasePrice AND P.PriceTo >= M.BasePrice)
Please post your table structure here if I have missed something: SQL Fiddle
Without a table structure to work with is difficoult but something within the line of
SELECT m.name Manufacturer, p.name, p.value, p.Manufacturer alias
FROM Manufacturers m
INNER JOIN Parameter p ON (m.name = p.Manufacturer)
UNION ALL
SELECT m.name Manufacturer, p.name, p.value, p.Manufacturer alias
FROM Manufacturers m
LEFT JOIN Parameter p on p.Manufacturer = 'default'
WHERE m.name + p.name not in (SELECT m.name + p.name
FROM Manufacturers m
INNER JOIN Parameter p
ON (m.name = p.Manufacturer)
)
ORDER BY 1, 2
Can get what you need. The first part get you the linked part, the second part get the default value only for the parameters name that don't have already a match.
This is a SQLFiddle with a few data and the query
Ok, so it turns out this was another situation where I was overthinking the problem.
I managed to get this to work by having the following Select statement nested within another select:
(SELECT
TOP(1) Percentage
FROM
Parameters AS P
WHERE
P.CategoryID = R.CategoryID
AND (P.PriceFrom <= M.BasePrice AND P.PriceTo >= M.BasePrice)
AND (P.ManufacturerName = M.ManufacturerName OR P.ManufacturerName = 'Default')
ORDER BY
CASE WHEN P.ManufacturerName = 'Default' THEN 'ZZZZZ' ELSE P.ManufacturerName END
) AS Markup
Many thanks for your help.

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