SQL Query to split shipping cost over multiple items / rows - sql

I am trying to create a query that will allow me to split a single figure over multiple rows.
For example, purchase order x may have 15 items allocated to it. Shipping cost is, say, 9.95. How can I calculate 1/15th of 9.95 and then update the cost price of the stock with 1/15th of the shipping cost?
Therefore the cost price of the item would increase from 4.50 to 5.16 (4.50 + 0.66).

Here's a solution for SQL Server:
update ol
set price = price + 5.0 / ol.LineCount
from [Order] o
join (
select *
, count(*) over () as LineCount
from OrderLine
) ol
on o.ID = ol.OrderID
where o.OrderNr = 'Ord1';
Live example at SQL Fiddle.
If you're using another DBMS, please update your post!

I have created a product table and updated the shipping price of items.
Hope that's what you are looking for.
INSERT INTO [TravelAgentDB].[dbo].[Product]
([ID]
,[Name]
,[Price]
,[shippingPrice])
VALUES
(1,'Nexus 7' , 250 , 20),
(2,'Nexus 7 case' , 50 , 20),
(3,'Nexus 7 headphone' , 20 , 20)
GO
select * from product
Declare #itemsCount int
Select #itemsCount = count(*) From product where id in (1,2,3)
Declare #totalShippingPrice Decimal(18,2) = 9.95
Declare #shippingPriceperItem Decimal(18,2) = #totalShippingPrice / #itemsCount
Update Product
set [shippingPrice] = #shippingPriceperItem
Where id in (1,2,3)
select * from product

Related

Find similar sales orders in SQL

This is my first post.
I work at a manufacturing company and most of the products we are making are custom made.
We believe we can find some commonalities in the products we sale.
To do this, we need to analyze sales orders and compare them to all the sales orders in our system to find identical ones.
Here's an example in form of a SQL result:
etc...
+------------------------------+
| OrderId ProductCode Qty |
+------------------------------+
| SS1234 Widget1 1 |
| SS1234 Widget2 3 |
| SS1234 Widget3 1 |
+------------------------------+
I would like to find orders similar to SS1234, ie orders with the same products (widget1, widget2 and widget3) and the same quantities.
How do I do this in SQL Server 2008R2?
Thanks for your help!
Raf
I won't be able to test this before I go to bed for the evening. This is an overly verbose approach, but I wanted to grind this out as quickly as possible so I tried to use structure / syntax that I know well, instead of trying to write more concise, efficient code that would require I lean on the documentation. Basically, we're counting the number of items in each order, selecting a pair of order ids every time we find two matching line items, then we count how many times an exact pair of order IDs appears. Use inner joins to filter out pairs that matched fewer times than there are products in the order.
WITH
ProductCounts AS (
SELECT COUNT(OrderID) AS ProductCodesInOrder, OrderID
FROM Table
GROUP BY OrderID
), MatchingLineItems AS (
SELECT A.OrderID AS FirstOrderID, B.OrderID AS SecondOrderID
FROM Table AS A
INNER JOIN Table AS B
ON A.ProductCode = B.ProductCode AND A.Qty = B.Qty
ORDER BY FirstOrderID, SecondOrderID
), MatchTotals AS (
SELECT
COUNT(FirstOrderID) AS Matches, FirstOrderID, SecondOrderID
FROM MatchingLineItems
GROUP BY FirstOrderID, SecondOrderID
), FirstMatches AS (
SELECT MatchTotals.FirstOrderID, MatchTotals.SecondOrderID, MatchTotals.Matches
FROM MatchTotals
INNER JOIN ProductCounts
ON MatchTotals.FirstOrderID = ProductCounts.OrderID
WHERE MatchTotals.Matches = ProductCounts.ProductCodesInOrder
)
SELECT FirstMatches.FirstOrderID, FirstMatches.SecondOrderID
FROM FirstMatches
INNER JOIN ProductCounts
ON FirstMatches.SecondOrderID = ProductCounts.OrderID
WHERE FirstMatches.Matches = ProductCounts.ProductCodesInOrder
Setup:
CREATE TABLE #ord (
OrderId VARCHAR(20),
ProductCode VARCHAR(40),
qty int
)
INSERT INTO #ord (OrderId, ProductCode, Qty)
VALUES
('SS1234','Widget1',1)
,('SS1234','Widget2',3)
,('SS1234','Widget3',1)
,('SS1234a','Widget1',1)
,('SS1234a','Widget2',3)
,('SS1234a','Widget3',1)
,('xSS1234','Widget1',1)
,('xSS1234','Widget2',3)
,('xSS1234','Widget3',1)
,('xSS1234','Widget4',1)
,('ySS1234','Widget1',10)
,('ySS1234','Widget2',3)
,('ySS1234','Widget3',1)
,('zSS1234','Widget2',3)
,('zSS1234','Widget3',1)
;
Query:
with CTE as (
select distinct
o.OrderID, ca.ProductString, ca.QtyString
from #ord o
cross apply (
SELECT
STUFF((
SELECT
', ' + o2.ProductCode
FROM #ord o2
WHERE o.OrderID = o2.OrderID
ORDER BY o2.ProductCode
FOR XML PATH ('')
)
, 1, 1, '')
, STUFF((
SELECT
', ' + cast(o2.Qty as varchar)
FROM #ord o2
WHERE o.OrderID = o2.OrderID
ORDER BY o2.ProductCode
FOR XML PATH ('')
)
, 1, 1, '')
) ca (ProductString, QtyString)
)
select
ProductString, QtyString, count(*) Num_Orders
from CTE
group by
ProductString, QtyString
having
count(*) > 1
order by
Num_Orders DESC
, ProductString
Result:
ProductString QtyString Num_Orders
Widget1, Widget2, Widget3 1, 3, 1 2
See: http://rextester.com/DJEN59714

SQLITE: Calculate price changes with multiple rows in same table

I have a database with the following schema and I need to be able to calculate the difference when a price changes for any given store and include it in the query results. Is it possible with the following query?:
SELECT * from prices
WHERE rowid in (
SELECT p.rowid from prices p
LEFT JOIN prices p1
ON (p1.rowid = (select max(rowid) from prices pp where pp.rowid < p.rowid))
WHERE p.price > p1.price + .01 and p.address = p1.address
)
I would like to print a report that looks similar to this:
Store--------State-------City--------Address-------Price-------Difference
ACME........Ohio........Akron........123 Elm.......10.25......+0.25
ACME........Ohio........Akron........123 hurst.....9.25.......+1.25
ACME........Ohio........Akron........125 Elm.......5.00.......-0.60
Here is a sample of the data that I am working with:
http://www.sqlfiddle.com/#!7/d020f/37/0
If you want to show the change between a price and the one before it you could do that using the insert query. Below I set the price to be 3.64 . In the query I've ordered by row but it would be better to order by date
INSERT INTO PRICES
Select url, state, city, location, 3.64 as price, company, address, '5:45:11 PM 5/30/2014 est.', 3.64 -price as change FROM PRICES
WHERE address = '1670 US-31 N & I-65'
ORDER BY rowid DESC LIMIT 0,1

SQL Year to Date report involving 3 tables

I am trying to create a year to date report that lists all clients with the totals from all invoices from the current date since the beginning of the year. I also want to join the same total columns from a budgeting table so they are able to compare actual vs budget. I am able to join the clients to the Invoices and get their totals correctly, but I am not able to get the correct values when I join the budgets table.
tbl_Clients
- ID uniqueidentifier
- Name varchar(100)
- isActive bit
- isDeleted bit
tbl_Invoices
-ClientID uniqueidentifier
-Month int
-Year int
-Energy int
-Demand int
-DemandDollar decimal(18,2)
-EnergyDollar decimal(18,2)
-IsActive bit
-IsDeleted bit
tbl_Budgets
-ClientID uniqueidentifier
-Month int
-Year int
-Energy int
-Demand int
-DemandDollar decimal(18,2)
-EnergyDollar decimal(18,2)
-IsActive bit
-IsDeleted bit
This procedure currently works to get each clients totals from tbl_Invoices. I'm just not sure how to then add the tbl_Budgets table totals as 5 more columns. The Mills column from the budget table is calculated in the same fashion.
Here is the procedure I currently have with the static values of 2012 and 9(September) They will eventually be parameters in a stored proc.
Select cli.name
, Sum(inv.DemandDollar) as DemandDollar
, SUM(inv.EnergyDollar) as EnergyDollar
, Sum(inv.Energy) as Energy
, Sum(inv.Demand) as Demand
, Mills =
(
case when sum(inv.Energy) = 0
then 0.00
else
Cast((((sum(inv.DemandDollar) + sum(inv.EnergyDollar))/sum(inv.Energy))* 1000) as decimal(18,2))
end
)
from tbl_Clients cli
join tbl_Invoices inv
on inv.ClientID = cli.ID
where cli.IsActive = 1 and cli.IsDeleted = 0
and inv.IsActive = 1
and inv.IsDeleted = 0 and inv.Year = 2012 and inv.Month <= 9
group by cli.name
The first thing you need to ask yourself is whether clients that have no invoices can have budgets and do they need to be included? Also, can clients that have no budgets have invoices? Does every client that has an invoice have a budget and does every client that has a budget have an invoice? The answers to these questions will effect how you structure your query.
So maybe this, which picks up all budgets and invoices regardless:
SELECT cli.Name, SUM(UsedDollar) AS UsedDollar, SUM(BudgetDollar) AS BudgetDollar
(SELECT ClientID, EnergyDollar AS UsedDollar, 0.0 AS BudgetDollar
FROM tbl_Invoices
WHERE Year = 2012 AND Month <= 9
UNION ALL
SELECT ClientID, 0.0 AS UsedDollar, EnergyDollar AS BudgetDollar
FROM tbl_Budgets
WHERE Year = 2012 AND Month <= 9
) DT
INNER JOIN tbl_Clients cli ON DT.ClientID = cli.ID
GROUP BY cli.Name

Get the average with quantity

I have a problem to calculate easily some simple average. My table :
id / user / action / data
1 / a / unit_price / 40
2 / a / quantity / 1
3 / b / unit_price / 70
4 / b / quantity / 2
Unit_price is a price for a user and quantity is quantity. So there i should get :
(40 + 70 + 70) / 3 = 60
If i do an
(AVG(action) WHERE action = unit_price)
I get :
(70+40)/2 = 55
If I do an
(SUM(action) WHERE action = unit_price) / (SUM(action) WHERE action = quantity)
I get :
110 / 3 = 36.6
The easiest way I found is to don't put the unit_price but the global price then make a division in the PHP code to get the unit_price, but I was hoping SQL could do something for me.
select coalesce(sum(quantity * unit_price) /sum(quantity), 0) from
(select
sum(case when action='unit_price' then data else 0 end) as unit_price,
sum(case when action='quantity' then data else 0 end) as quantity
from test
group by user) as a
SqlFiddle
You can use something like this which basically pivots the data to a more usable format and then gets the values that you need:
select avg(unit_price) AvgUnitPrice,
sum(unit_price*quantity)/sum(quantity) AvgPrice
from
(
select user,
max(case when action = 'unit_price' then data end) unit_price,
max(case when action = 'quantity' then data end) quantity
from table1
group by user
) x;
See SQL Fiddle With Demo
Ok, obviously your table design is not optimal, you should have unit_price and quantity as separate columns. But, working with what you have, try this:
SELECT SUM(A.data*B.data)/SUM(B.data) Calculation
FROM ( SELECT user, data
FROM YourTable
WHERE action = 'unit_price') AS A
INNER JOIN (SELECT user, data
FROM YourTable
WHERE action = 'quantity') AS B
ON A.user = B.user
I would join the table to itself in order to get the two records beloning together on one line
SELECT
SUM(unit_price * quantity) / SUM(quantity) AS average_unit_price
FROM
(SELECT
U.data AS unit_price, Q.data AS quantity
FROM
theTable U
INNER JOIN theTable Q
ON U.user = Q.user
WHERE
U.action = 'unit_price' AND
Q.action = 'quantity')
If you have more than two records per user and the ids of the both records are consequtive, then you would have to change the WHERE-clause to
WHERE
U.action = 'unit_price' AND
Q.action = 'quantity' AND
U.id + 1 = Q.id
Note:
If you calculate AVG(unit_price * quantity) you get the average sum per user.
(1*40 + 2*70) / 2 = 90
If you calculate SUM(unit_price * quantity) / SUM(quantity) you get the average unit price.
(1*40 + 2*70) / 3 = 60
Something like this should work; syntax might not be perfect since I didn't try it out, but you get the main idea at least.
SELECT sumUnitPrice.sum / sumQuantity.sum
FROM
(
(SELECT SUM(data) as sum
FROM WhateverTheHellYourTableIsNamed
WHERE action = 'unit_price') sumUnitPrice
(SELECT SUM(data) as sum
FROM WhateverTheHellYourTableIsNamed
WHERE action = 'quantity') sumQuantity
)
Your table design doesn't look good.
Make 2 tables instead:
ITEM
ItemId int not null PK,
Name varchar(200) not null,
UnitPrice decimal (10,2) not null
SALES
SalesId int not null PK,
ItemId int not null FK,
Quantity decimal(10,2)
PK - primary key, FK - foreign key
Average:
select
I.Name, avg(I.UnitPrice * S.Quantity) as avgSales
from
Sales S
join Items I on I.ItemId = S.ItemId
group by
I.Name

Multiple Running Totals with Group By

I am struggling to find a good way to run running totals with a group by in it, or the equivalent. The below cursor based running total works on a complete table, but I would like to expand this to add a "Client" dimension. So I would get running totals as the below creates but for each company (ie Company A, Company B, Company C, etc.) in one table
CREATE TABLE test (tag int, Checks float, AVG_COST float, Check_total float, Check_amount float, Amount_total float, RunningTotal_Check float,
RunningTotal_Amount float)
DECLARE #tag int,
#Checks float,
#AVG_COST float,
#check_total float,
#Check_amount float,
#amount_total float,
#RunningTotal_Check float ,
#RunningTotal_Check_PCT float,
#RunningTotal_Amount float
SET #RunningTotal_Check = 0
SET #RunningTotal_Check_PCT = 0
SET #RunningTotal_Amount = 0
DECLARE aa_cursor CURSOR fast_forward
FOR
SELECT tag, Checks, AVG_COST, check_total, check_amount, amount_total
FROM test_3
OPEN aa_cursor
FETCH NEXT FROM aa_cursor INTO #tag, #Checks, #AVG_COST, #check_total, #Check_amount, #amount_total
WHILE ##FETCH_STATUS = 0
BEGIN
SET #RunningTotal_CHeck = #RunningTotal_CHeck + #checks
set #RunningTotal_Amount = #RunningTotal_Amount + #Check_amount
INSERT test VALUES (#tag, #Checks, #AVG_COST, #check_total, #Check_amount, #amount_total, #RunningTotal_check, #RunningTotal_Amount )
FETCH NEXT FROM aa_cursor INTO #tag, #Checks, #AVG_COST, #check_total, #Check_amount, #amount_total
END
CLOSE aa_cursor
DEALLOCATE aa_cursor
SELECT *, RunningTotal_Check/Check_total as CHECK_RUN_PCT, round((RunningTotal_Check/Check_total *100),0) as CHECK_PCT_BIN, RunningTotal_Amount/Amount_total as Amount_RUN_PCT, round((RunningTotal_Amount/Amount_total * 100),0) as Amount_PCT_BIN
into test_4
FROM test ORDER BY tag
create clustered index IX_TESTsdsdds3 on test_4(tag)
DROP TABLE test
----------------------------------
I can the the running total for any 1 company but I would like to do it for multiple to produce something like the results below.
CLIENT COUNT Running Total
Company A 1 6.7%
Company A 2 20.0%
Company A 3 40.0%
Company A 4 66.7%
Company A 5 100.0%
Company B 1 3.6%
Company B 2 10.7%
Company B 3 21.4%
Company B 4 35.7%
Company B 5 53.6%
Company B 6 75.0%
Company B 7 100.0%
Company C 1 3.6%
Company C 2 10.7%
Company C 3 21.4%
Company C 4 35.7%
Company C 5 53.6%
Company C 6 75.0%
Company C 7 100.0%
This is finally simple to do in SQL Server 2012, where SUM and COUNT support OVER clauses that contain ORDER BY. Using Cris's #Checks table definition:
SELECT
CompanyID,
count(*) over (
partition by CompanyID
order by Cleared, ID
) as cnt,
str(100.0*sum(Amount) over (
partition by CompanyID
order by Cleared, ID
)/
sum(Amount) over (
partition by CompanyID
),5,1)+'%' as RunningTotalForThisCompany
FROM #Checks;
SQL Fiddle here.
I originally started posting the SQL Server 2012 equivalent (since you didn't mention what version you were using). Steve has done a great job of showing the simplicity of this calculation in the newest version of SQL Server, so I'll focus on a few methods that work on earlier versions of SQL Server (back to 2005).
I'm going to take some liberties with your schema, since I can't figure out what all these #test and #test_3 and #test_4 temporary tables are supposed to represent. How about:
USE tempdb;
GO
CREATE TABLE dbo.Checks
(
Client VARCHAR(32),
CheckDate DATETIME,
Amount DECIMAL(12,2)
);
INSERT dbo.Checks(Client, CheckDate, Amount)
SELECT 'Company A', '20120101', 50
UNION ALL SELECT 'Company A', '20120102', 75
UNION ALL SELECT 'Company A', '20120103', 120
UNION ALL SELECT 'Company A', '20120104', 40
UNION ALL SELECT 'Company B', '20120101', 75
UNION ALL SELECT 'Company B', '20120105', 200
UNION ALL SELECT 'Company B', '20120107', 90;
Expected output in this case:
Client Count Running Total
--------- ----- -------------
Company A 1 17.54
Company A 2 43.86
Company A 3 85.96
Company A 4 100.00
Company B 1 20.55
Company B 2 75.34
Company B 3 100.00
One way:
;WITH gt(Client, Totals) AS
(
SELECT Client, SUM(Amount)
FROM dbo.Checks AS c
GROUP BY Client
), n (Client, Amount, rn) AS
(
SELECT c.Client, c.Amount,
ROW_NUMBER() OVER (PARTITION BY c.Client ORDER BY c.CheckDate)
FROM dbo.Checks AS c
)
SELECT n.Client, [Count] = n.rn,
[Running Total] = CONVERT(DECIMAL(5,2), 100.0*(
SELECT SUM(Amount) FROM n AS n2
WHERE Client = n.Client AND rn <= n.rn)/gt.Totals
)
FROM n INNER JOIN gt ON n.Client = gt.Client
ORDER BY n.Client, n.rn;
A slightly faster alternative - more reads but shorter duration and simpler plan:
;WITH x(Client, CheckDate, rn, rt, gt) AS
(
SELECT Client, CheckDate, rn = ROW_NUMBER() OVER
(PARTITION BY Client ORDER BY CheckDate),
(SELECT SUM(Amount) FROM dbo.Checks WHERE Client = c.Client
AND CheckDate <= c.CheckDate),
(SELECT SUM(Amount) FROM dbo.Checks WHERE Client = c.Client)
FROM dbo.Checks AS c
)
SELECT Client, [Count] = rn,
[Running Total] = CONVERT(DECIMAL(5,2), rt * 100.0/gt)
FROM x
ORDER BY Client, [Count];
While I've offered set-based alternatives here, in my experience I have observed that a cursor is often the fastest supported way to perform running totals. There are other methods such as the quirky update which perform about marginally faster but the result is not guaranteed. The set-based approach where you perform a self-join becomes more and more expensive as the source row counts go up - so what seems to perform okay in testing with a small table, as the table gets larger, the performance goes down.
I have a blog post almost fully prepared that goes through a slightly simpler performance comparison of various running totals approaches. It is simpler because it is not grouped and it only shows the totals, not the running total percentage. I hope to publish this post soon and will try to remember to update this space.
There is also another alternative to consider that doesn't require reading previous rows multiple times. It's a concept Hugo Kornelis describes as "set-based iteration." I don't recall where I first learned this technique, but it makes a lot of sense in some scenarios.
DECLARE #c TABLE
(
Client VARCHAR(32),
CheckDate DATETIME,
Amount DECIMAL(12,2),
rn INT,
rt DECIMAL(15,2)
);
INSERT #c SELECT Client, CheckDate, Amount,
ROW_NUMBER() OVER (PARTITION BY Client
ORDER BY CheckDate), 0
FROM dbo.Checks;
DECLARE #i INT, #m INT;
SELECT #i = 2, #m = MAX(rn) FROM #c;
UPDATE #c SET rt = Amount WHERE rn = 1;
WHILE #i <= #m
BEGIN
UPDATE c SET c.rt = c2.rt + c.Amount
FROM #c AS c
INNER JOIN #c AS c2
ON c.rn = c2.rn + 1
AND c.Client = c2.Client
WHERE c.rn = #i;
SET #i = #i + 1;
END
SELECT Client, [Count] = rn, [Running Total] = CONVERT(
DECIMAL(5,2), rt*100.0 / (SELECT TOP 1 rt FROM #c
WHERE Client = c.Client ORDER BY rn DESC)) FROM #c AS c;
While this does perform a loop, and everyone tells you that loops and cursors are bad, one gain with this method is that once the previous row's running total has been calculated, we only have to look at the previous row instead of summing all prior rows. The other gain is that in most cursor-based solutions you have to go through each client and then each check. In this case, you go through all clients' 1st checks once, then all clients' 2nd checks once. So instead of (client count * avg check count) iterations, we only do (max check count) iterations. This solution doesn't make much sense for the simple running totals example, but for the grouped running totals example it should be tested against the set-based solutions above. Not a chance it will beat Steve's approach, though, if you are on SQL Server 2012.
UPDATE
I've blogged about various running totals approaches here:
http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals
I didn't exactly understand the schema you were pulling from, but here is a quick query using a temp table that shows how to do a running total in a set based operation.
CREATE TABLE #Checks
(
ID int IDENTITY(1,1) PRIMARY KEY
,CompanyID int NOT NULL
,Amount float NOT NULL
,Cleared datetime NOT NULL
)
INSERT INTO #Checks
VALUES
(1,5,'4/1/12')
,(1,5,'4/2/12')
,(1,7,'4/5/12')
,(2,10,'4/3/12')
SELECT Info.ID, Info.CompanyID, Info.Amount, RunningTotal.Total, Info.Cleared
FROM
(
SELECT main.ID, SUM(other.Amount) as Total
FROM
#Checks main
JOIN
#Checks other
ON
main.CompanyID = other.CompanyID
AND
main.Cleared >= other.Cleared
GROUP BY
main.ID) RunningTotal
JOIN
#Checks Info
ON
RunningTotal.ID = Info.ID
DROP TABLE #Checks