So I am working on an Invoicing Report in SSRS 2008. The database contains 4 relevant tables:
- Work Order
- Labor
- Materials
- Services (Subcontractors)
Obviously the work order table contains all the relevant information about the overall work order (we display things such as location, priority, etc). For this invoice report, I need to display the work order details up at the top, then show the labor, materials, and services used on the work order (with totals for each), then show a complete total for the entire cost of the work order.
My issue is this: I can do a dataset that works with Work Order + any ONE of the child tables, however I cannot figure out how to do all 3! I can't simply do a parameter for WONUM with 3 (or 4) tables on it, because this report will have MANY work orders (one per page) on it. When I use a dataset with the Work Order table and one child table, I group by WONUM then do a page break between each instance.
Any ideas for how to handle it? Most answers I came across say make one gigantic "union all" dataset and then group it after that, or use subreports for each child table. However, we will be exporting this report to Excel, and I've been told that subreports do not render properly when exported.
Any and all help is greatly appreciated! Thanks!
EDIT:
Below are the 4 queries I'd LIKE to use:
This retrieves all the work orders that need to be billed:
SELECT wonum, description, location FROM workorder WHERE billable=1 AND status='COMP'
This retrieves the labor for a work order (specified by #wonum)
SELECT regularhrs, laborrate, totalcost FROM labor WHERE refwo=#wonum
This retrieves the materials for a work order (specified by #wonum)
SELECT description, quantity, unitcost, totalcost FROM material WHERE refwo=#wonum
This retrieves the services (subcontractor hours) for a work order (specified by #wonum)
SELECT description, hours, laborrate, totalcost FROM service WHERE refwo=#wonum
So as I stated in the original post, my first query retrieves all the work orders that need to be billed, then for each work order (one per page), I need to retrieve the labor, materials, and services and display it in 3 tables below the work order details, then put an overall total cost on the work order invoice (at the end of each work order, not the end of all work orders)
I can get a screenshot of my current report if that would help also. Just let me know in comments!
Your query should look something like this
SELECT WO.wonum, WO.description as WorkorderDescription, WO.location, L.regularhrs, L.laborrate, L.totalcost,
M.description as MaterialDescription, M.quantity, M.unitcost, M.totalcost as MaterialCost,
S.description as ServiceDescription, S.hours, S.laborrate, S.totalcost as ServiceCost
FROM workorder AS WO
INNER JOIN labor AS L on L.refwo = WO.wonum
INNER JOIN material AS M on M.refwo = WO.wonum
INNER JOIN service AS S on S.refwo = WO.wonum
WHERE billable = 1 AND STATUS = 'COMP'
This will efficiently gather the information you need into one dataset. You will need to use the grouping features to setup the table in SSRS. You may have to do some additional research if you get stuck on getting the table layout right.
Related
I'm creating an Intranet for my company, and we want to have a stock management in it. We sell and rent alarm systems, and we want to have a good overview of what product is still in our offices, what has been rented or sold, at what time, etc.
At the moment I thought about this database design :
Everytime we create a new contract, this contract is about a location or a sale of an item. So we have an Product table (which is the type of product : alarms, alarm watches, etc.), and an Item table, which is the item itself, with it unique serial number. I thought about doing this, because I'll need to have a trace of where a specific item is, if it's at a client house (rented), if it's sold, etc. Products are related to a specific supplier, to whom we can take orders. But here, I have a problem, shouldn't the order table be related to Product ?
The main concern here is the link between Stock, Item, Movement stock. I wanted to create a design where I'd be able to see when a specific Item is pulled out of our stock, and when it enters the stock with the date. That's why I thought about a Movement_stock table. The Type_Movement is either In / Out.
But I'm a bit lost here, I really don't know how to do it nicely. That's why I'm asking for a bit of help.
I have the same need, and here is how I tackled your stock movement issue (which became my issue too).
In order to modelize stock movement (+/-), I have my supplying and my order tables. Supplying act as my +stock, and my orders my -stock.
If we stop to this, we could compute our actual stock which would be transcribed into this SQL query:
SELECT
id,
name,
sup.length - ord.length AS 'stock'
FROM
product
# Computes the number of items arrived
INNER JOIN (
SELECT
productId,
SUM(quantity) AS 'length'
FROM
supplying
WHERE
arrived IS TRUE
GROUP BY
productId
) AS sup ON sup.productId = product.id
# Computes the number of order
INNER JOIN (
SELECT
productId,
SUM(quantity) AS 'length'
FROM
product_order
GROUP BY
productId
) AS ord ON ord.productId = product.id
Which would give something like:
id name stock
=========================
1 ASUS Vivobook 3
2 HP Spectre 10
3 ASUS Zenbook 0
...
While this could save you one table, you will not be able to scale with it, hence the fact that most of the modelization (imho) use an intermediate stock table, mostly for performance concerns.
One of the downside is the data duplication, because you will need to rerun the query above to update your stock (see the updatedAt column).
The good side is client performance. You will deliver faster responses through your API.
I think another downside could be if you are managing high traffic store. You could imagine creating another table that stores the fact that a stock is being recomputed, and make the user wait until the recomputation is finished (push request or long polling) in order to check if every of his/her items are still available (stock >= user demand). But that is another deal...
Anyway even if the stock recomputation query is using anonymous subqueries, it should actually be quite fast enough in most of the relatively medium stores.
Note
You see in the product_order, I duplicated the price and the vat. This is for reliability reasons: to freeze the price at the moment of the purchase, and to be able to recompute the total with a lot of decimals (without loosing cents in the way).
Hope it helps someone passing by.
Edit
In practice, I use it with Laravel, and I use a console command, which will compute my product stock in batch (I also use an optional parameter to compute only for a certain product id), so my stock is always correct (relative to the query above), and I never manually update the stock table.
This is an interesting discussion and one that also could be augmented with stock availability as of a certain date...
This means storing:
Planned Orders for the Product on a certain date
Confirmed Orders as of a certain date
Orders Delivered
Orders Returned (especially if this is a hire product)
Each one of these product movements could be from and to a location
The user queries would then include:
What is my overall stock on hand
What is due to be delivered on a certain date
What will the stock on hand be as of a date overall
What will the stock on hand be as of a date for a location
The inventory design MUST take into account the queries and use cases of the users to determine design and also breaking normalisation rules to provide adequate performance at the right time.
Lots to consider and it all depends on the software use cases.
I recently ran into a problem in our SQL Server 2008 Analysis Services Cube. Imagine you have a simple sales data warehouse with orders and products. Each order can be associated with several products, and each product can be contained in several orders. So the data warehouse consists out of at least 3 tables: One for the Products, one for the Orders and one for the reference table, modelling the n:n relationship between both.
The question I want our cube to answer is: How many orders are there which contain both product x and product y?
In SQL, this is easy:
select orderid from dbo.OrderRefProduct
where ProductID = 1
intersect
select orderid from dbo.OrderRefProduct
where ProductID = 3
Since I am fairly proficient in SQL, but a newbie in MDX, I have been unable to implement that in MDX. I have tried using distinct count measures, the MDX-functions intersect and nonempty and subcubes. I also tried duplicating the dimensions logically (by adding the dimension to the cube twice) as well as physically (by duplicating the data source table and the dimension).
On http://www.zeitz.net/thts/intersection.zip, you can download a zip file of 25kB size which contains an SQL script with some test data and the Analysis Services Solution using the tables.
We are using SQL Server 2008 R2 and its Analysis Services counterpart. Performance considerations are not that important, as the data volume is rather low (millions of rows) compared to the other measure groups included in that cube (billions of rows).
The ultimate goal would be to be able to use the desired functionality in standard OLAP (custom calculated measures are ok), since Excel is our primary frontend, and our customers would like to choose their Products from the dimension list and get the correct result in the cube measures. But even a working standalone MDX-Query would greatly help.
Thank you!
Edit March 12th
Did I miss something or can't this be solved somehow?
If it helps to build the mdx, here is another way to get the results in sql, using subquerys. It can be further nested.
select distinct b.orderid from
(
select distinct orderid from dbo.OrderRefProduct
where ProductID = 1
) a
join dbo.OrderRefProduct b on (a.orderid = b.orderid)
where ProductID = 3
I tried something like this with subcubes in mdx, but didn't manage to succeed.
I've had a go - you can download my solution from here:
http://sdrv.ms/YWtMod
I've added a copy of your Fact table as a "Cross Reference", Aliased the Product1 dimension as a "Cross Reference", set the Dimension references to Product independently from your existing relationships, and specified the Many-to-Many relationships.
It is returning the right answer in Excel (sample attached).
You could extend that pattern as many times as you need.
Good luck!
Mike
an other way to deal with this in SQL (I know it works, but I didn't test this query) is to use double negation
select distinct orderid
from X
where TK NOT in (
select TK
from X x_alias
where productid NOT in (id1,id2)
)
I'm pretty sure you can do the same in MDX.
I am trying to make the queries my website uses more efficient.
Being a bit vague about SQL, I've not really learnt how to use nested queries, but I have just managed to get something that is pretty near what I want.
I sell guitars, I have a big database with all the products with different finish options listed individually. Items have unique IDs in the dB but are grouped by their title, for example, a Gibson Les Paul Standard is listed in my dB 7 times with 7 different finish options. Not all the finish options will necessarily have the same price, and not all finish options will necessarily be in stock.
In the search results page of my website I want to be able to show:
1) Just one record per product, ie 1 record for Gibson LP Std, which can then be sub-linked to the different finishes.
2) The actual product displayed must either be the cheapest finish option, OR, the cheapest in stock.
This is currently working on my website, but it's using N+1 queries and seems to be running dreadfully slowly, but for an example of what I mean, click here: http://www.hartnollguitars.co.uk/search.asp?subcat=Gibson-Les-Pauls (if the bloody thing works)
Part one is fine, I can just group the title in SQL, it's getting part 2 out that's the problem.
Using the following SQL query I can get the lowest price and the highest price and I have counted how many variants there are, I also have the max and min stock levels.
results.Open "SELECT * FROM
(SELECT *, count(id) as Variants, MAX(price) as highestPrice, MIN(price) as
lowestPrice, MAX(shopstock) as highestStock, MIN(shopstock) as lowestStock FROM
products WHERE item LIKE '%"& replace([searchterm]," ","%") &"%' GROUP BY item)
AS UnknownVar LIMIT 40", conn, 3, &H0001
What I need to be able to do is get the ID value for the rows representing the max and min stock and price values.
I basically need to be able to run if/or logic on it and I am not sure if this is possible.
So, I need to be able to say
if Item_With_Cheapest_Price is in stock, display this as the thumbnail & link
else
display first item in price sorted list where stock >=1
I also need a fall back, if none of the finishes are in stock, display the cheapest one.
The database is MySQL using ODBC connections, I am currently scripting in Classic ASP but aim to upgrade to .NET, once I've worked out how!!! :-)
I think for the order by part you should use something like
order by case
when stock > 0 then 0
when stock < 0 then 1
end ascending,
price ascending
I didn't check the syntax but that's the idea. You can google case in order by for more info.
As for the rest of you requirements I would need the table structure to better understand...
Do you know the concept of dense_rank? If not I could explain it to you. Your purpose could be solved by following queries. Have a look at this.
SELECT id,
MIN(stock) KEEP (DENSE_RANK FIRST ORDER BY stock,price) "Lowest"
,MAX(stock) KEEP (DENSE_RANK LAST ORDER BY stock,price) "Highest"
FROM products
GROUP BY id;
I have a MySQL table that represents a list of orders and a related child table that represents the shipments associated with each order (some orders have more than one shipment, but most have just one).
Each shipment has a number of costs, for example:
ItemCost
ShippingCost
HandlingCost
TaxCost
There are many places in the application where I need to get consolidated information for the order such as:
TotalItemCost
TotalShippingCost
TotalHandlingCost
TotalTaxCost
TotalCost
TotalPaid
TotalProfit
All those fields are dependent on the aggregated values in the related shipment table. This information is used in other queries, reports, screens, etc., some of which have to return a result on tens of thousands of records quickly for a user.
As I see it, there are a few basic ways to go with this:
Use a subquery to calculate these items from the shipment table whenever they are needed. This complicates things quite a bit for all the queried that needs all or part of this information. It is also slow.
Create a view that exposes the subqueries as simple fields. This keeps the reports that needs them simple.
Add these fields in the order table. These would give me the performance I am looking for, at the expense of having to duplicate data and calculate it when I make any changes to the shipment records.
One other thing, I am using a business layer that exposes functions to get this data (for example GetOrders(filter)) and I don't need the subtotals each time (or only some of them some of the time), so generating a subquery each time (even from a view) is probably a bad idea.
Are there any best practices that anybody can point me to help me decide what the best design for this is?
Incidentally, I ended up doing #3 primarily for performance and query simplicity reasons.
Update:
Got lots of great feedback pretty quickly, thank you all. To give a bit more background, one of the places the information is shown is on the admin console where I have a potentially very long list of orders and needs to show TotalCost, TotalPaid, and TotalProfit for each.
Theres absolutely nothing wrong with doing rollups of your statistical data and storing it to enhance application performance. Just keep in mind that you will probably need to create a set of triggers or jobs to keep the rollups in sync with your source data.
I would probably go about this by caching the subtotals in the database for fastest query performance if most of the time you're doing reads instead of writes. Create an update trigger to recalculate the subtotal when the row changes.
I would only use a view to calculate them on SELECT if the number of rows was typically pretty small and access somewhat infrequent. Performance will be much better if you cache them.
Option 3 is the fastest
If and when you are running into performance issues and if you cannot solve these any other way, option #3 is the way to go.
Use triggers to do the updating
You should use triggers after insert, update and delete to keep the subtotals in your order table in sync with the underlying data.
Take special care when retrospectively changing prices and stuff as this will require a full recalc of all subtotals.
So you will need a lot of triggers, that usually don't do much most of the time.
if a taxrate changes, it will change in the future, for orders that you don't yet have
If the triggers take a lot of time, make sure you do these updates in off-peak hours.
Run an automatic check periodically to make sure the cached values are correct
You may also want to keep a golden subquery in place that calculates all the values and checkes them against the stored values in the order table.
Run this query every night and have it report any abnormalities, so that you can see when the denormalized values are out-of-sync.
Do not do any invoicing on orders that have not been processed by the validation query
Add an extra date field to table order called timeoflastsuccesfullvalidation and have it set to null if the validation was unsuccessful.
Only invoice items with a dateoflastsuccesfullvalidation less than 24 hours ago.
Of course you don't need to check orders that are fully processed, only orders that are pending.
Option 1 may be fast enough
With regards to #1
It is also slow.
That depends a lot on how you query the DB.
You mention subselects, in the below mostly complete skeleton query I don't see the need for many subselects, so you have me puzzled there a bit.
SELECT field1,field2,field3
, oifield1,oifield2,oifield3
, NettItemCost * (1+taxrate) as TotalItemCost
, TotalShippingCost
, TotalHandlingCost
, NettItemCost * taxRate as TotalTaxCost
, (NettItemCost * (1+taxrate)) + TotalShippingCost + TotalHandlingCost as TotalCost
, TotalPaid
, somethingorother as TotalProfit
FROM (
SELECT o.field1,o.field2, o.field3
, oi.field1 as oifield1, i.field2 as oifield2 ,oi.field3 as oifield3
, SUM(c.productprice * oi.qty) as NettItemCost
, SUM(IFNULL(sc.shippingperkg,0) * oi.qty * p.WeightInKg) as TotalShippingCost
, SUM(IFNULL(hc.handlingperwhatever,0) * oi.qty) as TotalHandlingCost
, t.taxrate as TaxRate
, IFNULL(pay.amountpaid,0) as TotalPaid
FROM orders o
INNER JOIN orderitem oi ON (oi.order_id = o.id)
INNER JOIN products p ON (p.id = oi.product_id)
INNER JOIN prices c ON (c.product_id = p.id
AND o.orderdate BETWEEN c.validfrom AND c.validuntil)
INNER JOIN taxes t ON (p.tax_id = t.tax_id
AND o.orderdate BETWEEN t.validfrom AND t.validuntil)
LEFT JOIN shippingcosts sc ON (o.country = sc.country
AND o.orderdate BETWEEN sc.validfrom AND sc.validuntil)
LEFT JOIN handlingcost hc ON (hc.id = oi.handlingcost_id
AND o.orderdate BETWEEN hc.validfrom AND hc.validuntil)
LEFT JOIN (SELECT SUM(pay.payment) as amountpaid FROM payment pay
WHERE pay.order_id = o.id) paid ON (1=1)
WHERE o.id BETWEEN '1245' AND '1299'
GROUP BY o.id DESC, oi.id DESC ) AS sub
Thinking about it, you would need to split this query up for stuff that's relevant per order and per order_item but I'm lazy to do that now.
Speed tips
Make sure you have indexes on all fields involved in the join-criteria.
Use a MEMORY table for the smaller tables, like tax and shippingcost and use a hash index for the id's in the memory-tables.
I would avoid #3 as possible as I can. I prefer that for different reasons:
It's too hard to discuss performance without measurement. Imaging the user is shopping around, adding order items into an order; every time an item is added, you need to update the order record, which may not be necessary (some sites only show order total when you click shopping cart and ready to checkout).
Having a duplicated column is asking for bugs - you cannot expect every future developer/maintainer to be aware of this extra column. Triggers can help but I think triggers should only be used as a last resort to address a bad database design.
A different database schema can be used for reporting purpose. The reporting database can be highly de-normalized for performance purpose without complicating the main application.
I tend to put the actual logic for computing subtotal at application layer because subtotal is actually an overloaded thing related to different contexts - sometimes you want the "raw subtotal", sometimes you want the subtotal after applying discount. You just cannot keep adding columns to the order table for different scenario.
It's not a bad idea, unfortunately MySQL doesn't have some features that would make this really easy - computed columns and indexed (materialized views). You can probably simulate it with a trigger.
I have a query which i have been working on for hours and can't seem to get it working.
the query is to Generate list of customers that have spent > €100 in the last 365 days. I am creating a video rental database..
This is how far i have gotten but cant seem to link the data together with date_rented data table.
SELECT CUST_ID, CUSTOMER_SPEND
FROM ACCOUNT_TEST
WHERE CUSTOMER_SPEND > 100;
the tables I am working with are cust_id, customer_spend, date_rented and account_test
Instead of trying to answer the question that was (sort of) asked, maybe it makes sense to step back, look at the (apparently) desired result, and show how that could be achieved. For the moment, I'm going to only look at one table out of what should be a number. This table will hold the details of an individual customer rental:
customer_id
date_rented
cost
More fields are certainly needed, but those seem to cover what we care about for this query. From this, we want a list of customers who've spent at least 100 (of whatever unit cost is in), along with the amount spent by each. The only slightly tricky part is that we can't use an aggregate like sum(customer_paid) in a where clause, so we put that in a having clause instead.
select customer_id, sum(cost) as customer_paid
from rental_details
where to_days(now()) - to_days(date_rented) <= 365
group by customer_id
having customer_paid > 100
As a quick warning, that might need minor tweaking to work with MySQL -- most of what I've written recently has been for SQL Server.