Display Rows as Columns - sql-server

I have seen few similar questions asked on Stackoverflow with same subject, However this is not fitting into any of those.
I have 3 Tables Purchase, Hotel, Car Purchase to Hotel and Car is 1 to 0 or many relationship. MS SQL 2008 Server.
Purchase Table
pid bookingdate ...
1
2
3
Hotel Table
hid pid amount rooms location brand...
1 1
2 1
3 1
4 3
4 3
Car Table
cid pid make model ...
1 1
2 2
3 2
What I want is display the hotel data in columns
pid bookingdate cid make model hid1 amount1 rooms1 location1 brand1 hid2 amount2 rooms2 location2 brand2 hid3 amount3 rooms3 location3 brand3 hid4 amount4 rooms4 location4 brand4
If there is only one hotel for given purchase id, other columns should be null. If more than 4 hotel ignore the other hotels(5th 6th ect)
Please assume Car table can have 0 or 1 at this stage.
(Advance::>> If Car table can 0 to many (relationship to Purchase) 1.) take only first record to account 2.) get the sum of records.)
Purpose of this project is generate a csv file which can upload to a some third party product. Currently we have about 100 columns for Purchase and Car about 30 Columns for Hotel (130 all). With Hotel rows displaying in this way it will be 220 (100 + 30 x 4) columns. And about 100K rows. Performance is an issue as well.
I tried few things, but nothing successful not even close to getting stuck. This was the closest I got but I have 30 columns to duplicate not just a one, In-fact I have a feeling that PIVOT is not the way to go. I'm thinking using DENSE_RANK() or something like that
SELECT pid
, hid
, DENSE_RANK() OVER(ORDER BY pid)
FROM HOTEL
WHERE pid IN (
SELECT pid
FROM Hotel
WHERE pid IN (
SELECT pid
FROM Purchase
WHERE rdate BETWEEN '2014-04-01' AND '2014-12-31'
)
GROUP BY pid
HAVING COUNT(pid) > 1
)
Assuming then we might able to process hotels with more than 1 attached to Purchase first, using a cursor (ugly) or something like that

Okay I have found a solution. You are welcome to refine and add your thoughts. Dynamically name columns would be handy. (ie product1,product2,product3,product4)
Schema
create table Purchase
(
pid int,
purchasedate date,
currency varchar(10),
paymenttype varchar(10),
creditcard varchar(10),
creditcardtype varchar(10)
);
create table Hotel
(
hid int,
pid int,
product varchar(30),
country varchar(10),
city varchar(10),
rooms int,
starrating int
);
create table Car
(
cid int,
pid int,
product varchar(30),
country varchar(10),
city varchar(10),
cancel int,
starrating int
);
insert into Purchase values (1, '2015-01-15','AUD', 'CC','12345678','AMEX')
insert into Purchase values (2, '2015-01-15','AUD', 'CC','12345678','AMEX')
insert into Purchase values (3, '2015-01-15','AUD', 'CC','12345678','AMEX')
insert into Purchase values (4, '2015-01-15','AUD', 'CC','12345678','AMEX')
insert into Purchase values (5, '2015-01-15','AUD', 'CC','12345678','AMEX')
insert into Hotel values (1,1, 'Five for Two','Australia', 'Melbourne','1','3')
insert into Hotel values (2,1, 'Five for None','Australia', 'Sydney','1','3')
insert into Hotel values (3,1, 'Five for Five','Australia', 'Melbourne','1','3')
insert into Hotel values (4,1, 'Five for Two','Australia', 'Jamboora','1','3')
insert into Hotel values (5,2, 'Five for Three','Australia', 'Sydney','1','3')
insert into Hotel values (6,2, 'Five for Love','Australia', 'Cook','1','3')
insert into Hotel values (7,2, 'Five for Grease','Australia', 'Darwin','1','3')
insert into Hotel values (8,3, 'Love Me','Australia', 'Darwin','1','3')
insert into Hotel values (9,4, 'Live for Grease','Australia', 'Footscray','1','3')
insert into Hotel values (10,4, 'Love Grease','Australia', 'Officer','1','3')
insert into Car values (1,1, 'Love Grease','Australia', 'Officer','1','3')
insert into Car values (2,2, 'Love Grease','Australia', 'Cook','1','3')
insert into Car values (3,4, 'Live Grease','Australia', 'Jamboora','1','3')
-- For Advance insert into Car values (4,4, 'Cove Grease','Australia', 'Melbourne','1','3')
And the Code is
SELECT *, DENSE_RANK() OVER(PARTITION BY pid ORDER BY hid DESC) AS Ranking
INTO #TempHotel
FROM Hotel
SELECT P.pid, P.purchasedate, P.currency, P.paymenttype, P.creditcard, P.creditcardtype
,C.cid, C.product, C.country, C.city, C.cancel, C.starrating
,H1.hid, H1.product, H1.country, H1.city,H1.rooms,H1.starrating
,H2.hid, H2.product, H2.country, H2.city,H2.rooms,H2.starrating
,H3.hid, H3.product, H3.country, H3.city,H3.rooms,H3.starrating
,H4.hid, H4.product, H4.country, H4.city,H4.rooms,H4.starrating
FROM Purchase P
left join Car C on P.pid = C.pid
left join #TempHotel H1 on P.pid = H1.pid and H1.Ranking = 1
left join #TempHotel H2 on P.pid = H2.pid and H2.Ranking = 2
left join #TempHotel H3 on P.pid = H3.pid and H3.Ranking = 3
left join #TempHotel H4 on P.pid = H4.pid and H4.Ranking = 4
DROP Table #TempHotel
You can also find this in SQL fiddle Here.

If the column count never changes you could use Pivot / Unpivot
Here is a technet article which says it all: http://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
and a sample from the article.
-- Pivot table with one row and five columns
SELECT 'AverageCost' AS Cost_Sorted_By_Production_Days,
[0], [1], [2], [3], [4]
FROM
(SELECT DaysToManufacture, StandardCost
FROM Production.Product) AS SourceTable
PIVOT
(
AVG(StandardCost)
FOR DaysToManufacture IN ([0], [1], [2], [3], [4])
) AS PivotTable;

Related

Creating SQL Server (T-SQL) view that flattens nulls

I'm having a lot of difficulty trying to create a view that flattens the data without nulls. I've supplied the code that creates two basic tables and my view code so you can see what I've tried so far. Please note that the two tables do not have a matching primary or foreign key column, so the summary in the view is created by just joining on City. I can't use XML because my team of data analysts all have intermediate skills and won't be able to understand it. I considered using a recursive CTE, but I can't get it right. The result produces 6 lines but I want 3 lines.
Thanks for any ideas about a better way to achieve this.
CREATE TABLE A (
OrdID int,
Cat varchar(255),
Qty int,
City varchar(255),
Ctry varchar(255)
);
INSERT INTO A (OrdID, Cat, Qty, City, Ctry)
VALUES (1, 'TV', 5,'London', 'England');
INSERT INTO A (OrdID, Cat, Qty, City, Ctry)
VALUES (2, 'Laptop', 3,'London', 'England');
INSERT INTO A (OrdID, Cat, Qty, City, Ctry)
VALUES (3, 'Laptop', 4, 'Berlin', 'Germany');
CREATE TABLE Cust (
CustID int,
CustType varchar(255),
City varchar(255),
NumItems int,
);
INSERT INTO Cust (CustID, CustType, City, NumItems)
VALUES (1, 'New', 'London', 2);
INSERT INTO Cust (CustID, CustType, City, NumItems)
VALUES (2, 'Returning','London', 5);
INSERT INTO Cust (CustID, CustType, City, NumItems)
VALUES (3, 'Returning','Berlin', 2);
INSERT INTO Cust (CustID, CustType, City, NumItems)
VALUES (4, 'New','Berlin', 8);
alter view My_View
as
With CTE_FlattenNulls
as
(
Select
S.Cat
, S.Qty
, S.City
, S.Ctry
, case when C.CustType like 'New' then sum(C.NumItems) end as NewC
, case when C.CustType like 'Returning' then sum(C.NumItems) end as RetC
from A as S
left join Cust as C
on S.City = C.City
group by
S.Cat
, S.Qty
, S.City
, S.Ctry
, C.CustType
)
select
Cat
,Qty
,City
,Ctry
,NewC
,RetC
,SUM(IsNull(NewC, 0) + IsNull(RetC, 0)) as TotC
from CTE_FlattenNulls
group by
Cat
,Qty
,City
,Ctry
,NewC
,RetC
go
Just adding the output that I wanted:
Cat
Qty
City
Cntry
NewC
RetCust
TotC
Laptop
4
Berlin
Germany
8
2
10
Laptop
3
London
England
2
5
7
TV
5
London
England
2
5
7
You were very close.
See comments in code for explanation.
With CTE_FlattenNulls
as
(
Select S.Cat, S.Qty, S.City, S.Ctry,
-- To do conditional summation case expression needs to be inside the SUM function
sum( case when C.CustType like 'New' then C.NumItems else 0 end ) as NewC,
sum( case when C.CustType like 'Returning' then C.NumItems else 0 end ) as RetC
from A as S
left join Cust as C
on S.City = C.City
group by
S.Cat
, S.Qty
, S.City
, S.Ctry
-- then you do not need to group by this column and therefore you do not get extra rows
--, C.CustType
)
select Cat, Qty, City, Ctry, NewC, RetC,
-- As per your example, you would no longer need GROUP BY,
-- therefore SUM function should be removed
SUM(IsNull(NewC, 0) + IsNull(RetC, 0)) as TotC
from CTE_FlattenNulls
-- As per your example, you would no longer need GROUP BY
group by Cat, Qty, City, Ctry
-- ,NewC -- Definitely not needed anymore
-- ,RetC -- Definitely not needed anymore
Everything else stays the same
To get to your result, why can you not just do a simple group by with conditional sum ?
It has no need for a CTE
See this example, also in this DBFiddle
select A.Cat,
A.Qty,
A.City,
min(A.Ctry) as Country,
sum(case when C.CustType = 'New' then C.NumItems else 0 end) as NewC,
sum(case when C.CustType = 'Returning' then C.NumItems else 0 end) as RetCust,
sum(C.NumItems) as TotC
from A
join Cust C on A.City = C.City
group by A.Cat,
A.Qty,
A.City
order by A.Cat, A.City
it returns this
Cat
Qty
City
Country
NewC
RetCust
TotC
Laptop
4
Berlin
Germany
8
2
10
Laptop
3
London
England
2
5
7
TV
5
London
England
2
5
7

SQL Merge update issue

I am trying to increment a project code when I do a SQL merge operation
I have two tables. One has lots of information including customer's and projects. I want to merge the customer name and project name from one table to the other. This article is perfect and showed me how to do what I needed to do
https://www.mssqltips.com/sqlservertip/1704/using-merge-in-sql-server-to-insert-update-and-delete-at-the-same-time/
However I need to maintain a project number that increments every time a record is added and left alone when you do an edit of customer or project name. If the project is deleted then we carry on from the next available number. I was trying to do it using row number over partition but it didn't give me the correct number of projects.
Using the articles example and to provide a visualisation I would need another column called Type with Food or Drink as the answer and get
Item Cost Code Type
Tea 10 1 Drink
Coffee 12 2 Drink
Muffin 11 1 Food
Biscuit 4 2 Food
I will go with the data from the example provided from the link and add a bit more data to be sure im covering all the cases so first lets start with these tables and fill them.
--Create a target table
Declare #Products TABLE
(
ProductID INT PRIMARY KEY,
ProductName VARCHAR(100),
ProductNumber int,
ProductType VARCHAR(100),
Rate MONEY
)
--Insert records into target table
INSERT INTO #Products
VALUES
(1, 'Tea', 1,'Drink',10.00),
(2, 'Coffee', 2,'Drink', 20.00),
(3, 'BiscuitX1', 1,'Food', 45.00) ,
(4, 'Muffin', 2,'Food', 30.00),
(5, 'BiscuitX2', 3,'Food', 40.00),
(6, 'BiscuitX3', 4,'Food', 45.00),
(7, 'Donut', 5, 'Food', 30.00),
(8, 'BiscuitX4', 6,'Food', 40.00),
(9, 'BiscuitX5', 7,'Food', 45.00)
--Create source table
Declare #UpdatedProducts TABLE
(
ProductID INT PRIMARY KEY,
ProductName VARCHAR(100),
ProductNumber int,
ProductType VARCHAR(100),
Rate MONEY
)
--Insert records into source table
INSERT INTO #UpdatedProducts
VALUES
(1, 'Tea', 0,'Drink', 10.00),
(2, 'Coffee', 0,'Drink', 25.00),
(4, 'Muffin', 0,'Food', 35.00),
(7, 'Donut', 0, 'Food', 30.00),
(10, 'Pizza', 0,'Food', 60.00),
(11, 'PizzaLarge', 0,'Food', 80.00)
You can see that I added the ProductNumber and ProductType.
for the #UpdatedProducts table Im assuming you dont have the product Number, if you do then you will do the direct merge with no problem, if you dont you would need to find it.
so lets first update ProductNumber in #UpdatedProducts
;with cte as (
select u.ProductID,u.ProductName,u.ProductType,u.Rate
,coalesce(p.ProductNumber,row_number() over (partition by u.ProductType order by u.ProductID)
+(select max(pp.ProductNumber) from #Products pp where pp.ProductType=u.ProductType)
-(select Count(*) from #UpdatedProducts uu
inner join #Products ppp on ppp.ProductID=uu.ProductID
where uu.ProductType=u.ProductType)) [ProductNumber]
from #UpdatedProducts u
left outer join #Products p on p.ProductID=u.ProductID
)
update a
set a.[ProductNumber]=cte.[ProductNumber]
From #UpdatedProducts a
inner join cte on cte.ProductID=a.ProductID
I did not find a way to put this in the merge directly.
The result of the #UpdatedProducts after the update will be as below:-
ProductID ProductName ProductNumber ProductType Rate
========= =========== ============= =========== ====
1 Tea 1 Drink 10.00
2 Coffee 2 Drink 25.00
4 Muffin 2 Food 35.00
7 Donut 5 Food 30.00
10 Pizza 8 Food 60.00
11 PizzaLarge 9 Food 80.00
So now we can do a direct merge, as below:-
--Synchronize the target table with refreshed data from source table
MERGE #Products AS TARGET
USING #UpdatedProducts AS SOURCE
ON (TARGET.ProductID = SOURCE.ProductID)
--When records are matched, update the records if there is any change
WHEN MATCHED AND TARGET.ProductName <> SOURCE.ProductName OR TARGET.Rate <> SOURCE.Rate
THEN UPDATE SET TARGET.ProductName = SOURCE.ProductName, TARGET.Rate = SOURCE.Rate ,TARGET.ProductNumber= TARGET.ProductNumber --left alone on edit
--When no records are matched, insert the incoming records from source table to target table
WHEN NOT MATCHED BY TARGET
THEN INSERT (ProductID, ProductName, Rate,ProductNumber,ProductType)
VALUES (SOURCE.ProductID, SOURCE.ProductName, SOURCE.Rate,SOURCE.ProductNumber,SOURCE.ProductType)-- increments every time a record is added
--When there is a row that exists in target and same record does not exist in source then delete this record target
WHEN NOT MATCHED BY SOURCE
THEN DELETE
--$action specifies a column of type nvarchar(10) in the OUTPUT clause that returns
--one of three values for each row: 'INSERT', 'UPDATE', or 'DELETE' according to the action that was performed on that row
OUTPUT $action,
DELETED.ProductID AS TargetProductID,
DELETED.ProductName AS TargetProductName,
DELETED.Rate AS TargetRate,
INSERTED.ProductID AS SourceProductID,
INSERTED.ProductName AS SourceProductName,
INSERTED.Rate AS SourceRate;
SELECT * FROM #Products
The result of #Products would be as below:-
ProductID ProductName ProductNumber ProductType Rate
========= =========== ============= =========== ====
1 Tea 1 Drink 10.00
2 Coffee 2 Drink 25.00
4 Muffin 2 Food 35.00
7 Donut 5 Food 30.00
10 Pizza 8 Food 60.00
11 PizzaLarge 9 Food 80.00
For the product numbers (1,3,4,6,7) were all skipped and the new food product Pizza took Product number 8 and continued to be Product 9 for the PrizzaLarge.
hope this helps.

Something wrong with my Pivot function but I literally don't know why

I have table that has Order_Day,Product_ID, Quantity and Price columns:
create table dbo.AmazonTestTable
(order_day date,
order_id int,
product_id char(2),
quantity int,
price int)
go
insert into dbo.AmazonTestTable values
('01-JUL-11',1,'p1',5,5),
('01-JUL-11',2,'p2',2,10),
('01-JUL-11',3,'p3',10,25),
('01-JUL-11',4,'p1',20,5),
('02-JUL-11',5,'p3',5,25),
('02-JUL-11',6,'p4',6,20),
('02-JUL-11',7,'p1',2,5),
('02-JUL-11',8,'p5',1,50),
('02-JUL-11',9,'p6',2,50),
('02-JUL-11',10,'p2',4,10)
go
The problem is that I want to select order_day, product_id, and TotalSales(this can be calculated by using quantity multiple by price) and then pivot it. So the pivoted table should have three columns: product_id, '01-JUL-11','02-JUL-11'. So I use the codes below:
select product_id,'2011-07-01' as TotalSalesOnDay1,'2011-07-02' as
TotalSalesOnDay2
from (select product_id,order_day,(quantity*price) as TotalSales
from dbo.AmazonTestTable) as TotalSalesPerDay
pivot
(sum(TotalSales) for [order_day] in ([2011-07-01] ,[2011-07-02] )) as
PivotDataSet;
go
but it returns the result as below:
'2011-07-01' is a constant string. The escape method in SQL Server uses square braces:
select product_id, [2011-07-01] as TotalSalesOnDay1, [2011-07-02] as TotalSalesOnDay2
. . .
This query will do the trick -
;With PivotData
As
(
Select
product_id, -- grouping element
order_day, -- spreading element
(quantity * price) As TotalSales -- aggregating element
From AmazonTestTable
)
Select
product_id, [2011-07-01] As TotalSalesDay1, [2011-07-02] As TotalSalesDay2
From PivotData
Pivot (Sum(TotalSales) For order_day In ([2011-07-01], [2011-07-02])) As P
It will give you the following output -
product_id TotalSalesDay1 TotalSalesDay2
p1 125 10
p2 20 40
p4 NULL 120
p3 250 125
p5 NULL 50
p6 NULL 100
Do you want something like this
SELECT product_id, order_day, SUM(TotalSales) AS TotalSales FROM
(SELECT product_id,order_day,(quantity*price) as TotalSales
FROM AmazonTestTable) AS TotalSalesPerDay
GROUP BY TotalSalesPerDay.order_day;
The output will be:
product_id| order_day | TotalSales
p1 | 01-JUL-11 |395
p2 | 02-JUL-11 |445
I have seen hundreds of pivot questions here in the past few months. Let me post some sample solutions, and maybe help others to learn the basics, so we don't keep seeing the same thing over and over and over.
CREATE TABLE Products (
product VARCHAR(30),
market_year INT,
value INT,
quantity INT,
CONSTRAINT pk_products
PRIMARY KEY (product, market_year));
INSERT INTO Products VALUES('Corn', 2003, 100, 20);
INSERT INTO Products VALUES('Corn', 2004, 200, 25);
INSERT INTO Products VALUES('Corn', 2005, 150, 30);
INSERT INTO Products VALUES('Corn', 2006, 150, 10);
SELECT product,
SUM(CASE WHEN market_year = 2003 THEN value ELSE 0 END) AS v2003,
SUM(CASE WHEN market_year = 2003 THEN quantity ELSE 0 END) AS q2003,
SUM(CASE WHEN market_year = 2004 THEN value ELSE 0 END) AS v2004,
SUM(CASE WHEN market_year = 2004 THEN quantity ELSE 0 END) AS q2004,
SUM(CASE WHEN market_year = 2005 THEN value ELSE 0 END) AS v2005,
SUM(CASE WHEN market_year = 2005 THEN quantity ELSE 0 END) AS q2005,
SUM(CASE WHEN market_year = 2006 THEN value ELSE 0 END) AS v2006,
SUM(CASE WHEN market_year = 2006 THEN quantity ELSE 0 END) AS q2006
FROM Products
GROUP BY product;
Result:
product v2003 q2003 v2004 q2004 v2005 q2005 v2006 q2006
Corn 100 20 200 25 150 30 150 10
Also . . .
USE AdventureWorks
GO
-- Creating Test Table
CREATE TABLE Product(Cust VARCHAR(25), Product VARCHAR(20), QTY INT)
GO
-- Inserting Data into Table
INSERT INTO Product(Cust, Product, QTY)
VALUES('KATE','VEG',2)
INSERT INTO Product(Cust, Product, QTY)
VALUES('KATE','SODA',6)
INSERT INTO Product(Cust, Product, QTY)
VALUES('KATE','MILK',1)
INSERT INTO Product(Cust, Product, QTY)
VALUES('KATE','BEER',12)
INSERT INTO Product(Cust, Product, QTY)
VALUES('FRED','MILK',3)
INSERT INTO Product(Cust, Product, QTY)
VALUES('FRED','BEER',24)
INSERT INTO Product(Cust, Product, QTY)
VALUES('KATE','VEG',3)
GO
-- Selecting and checking entires in table
SELECT *
FROM Product
GO
-- Pivot Table ordered by PRODUCT
SELECT PRODUCT, FRED, KATE
FROM (
SELECT CUST, PRODUCT, QTY
FROM Product) up
PIVOT (SUM(QTY) FOR CUST IN (FRED, KATE)) AS pvt
ORDER BY PRODUCT
GO
-- Pivot Table ordered by CUST
SELECT CUST, VEG, SODA, MILK, BEER, CHIPS
FROM (
SELECT CUST, PRODUCT, QTY
FROM Product) up
PIVOT (SUM(QTY) FOR PRODUCT IN (VEG, SODA, MILK, BEER, CHIPS)) AS pvt
ORDER BY CUST
GO
-- Unpivot Table ordered by CUST
SELECT CUST, PRODUCT, QTY
FROM
(
SELECT CUST, VEG, SODA, MILK, BEER, CHIPS
FROM (
SELECT CUST, PRODUCT, QTY
FROM Product) up
PIVOT
( SUM(QTY) FOR PRODUCT IN (VEG, SODA, MILK, BEER, CHIPS)) AS pvt) p
UNPIVOT
(QTY FOR PRODUCT IN (VEG, SODA, MILK, BEER, CHIPS)
) AS Unpvt
GO
-- Clean up database
DROP TABLE Product
GO
ResultSet:
-- Selecting and checking entires in table
Cust Product QTY
------------------------- -------------------- -----------
KATE VEG 2
KATE SODA 6
KATE MILK 1
KATE BEER 12
FRED MILK 3
FRED BEER 24
KATE VEG 3
-- Pivot Table ordered by PRODUCT
PRODUCT FRED KATE
-------------------- ----------- -----------
BEER 24 12
MILK 3 1
SODA NULL 6
VEG NULL 5
-- Pivot Table ordered by CUST
CUST VEG SODA MILK BEER CHIPS
------------------------- ----------- ----------- ----------- ----------- -----------
FRED NULL NULL 3 24 NULL
KATE 5 6 1 12 NULL
-- Unpivot Table ordered by CUST
CUST PRODUCT QTY
------------------------- -------- -----------
FRED MILK 3
FRED BEER 24
KATE VEG 5
KATE SODA 6
KATE MILK 1
KATE BEER 12 12
You can even do a dynamic pivot, when you don't know, ahead of time, all the fields that you will be dealing with. Let SQL Server do the work for you!!
--Create Temporary Table #CourseSales
CREATE TABLE #CourseSales
(Course VARCHAR(50),Year INT,Earning MONEY)
GO
--Populate Sample records
INSERT INTO #CourseSales VALUES('.NET',2012,10000)
INSERT INTO #CourseSales VALUES('Java',2012,20000)
INSERT INTO #CourseSales VALUES('.NET',2012,5000)
INSERT INTO #CourseSales VALUES('.NET',2013,48000)
INSERT INTO #CourseSales VALUES('Java',2013,30000)
GO
SELECT *
FROM #CourseSales
PIVOT(SUM(Earning)
FOR Course IN ([.NET], Java)) AS PVTTable
You will find some great resources here!
http://blogs.lessthandot.com/index.php/DataMgmt/DataDesign/understanding-sql-server-2000-pivot/
http://blogs.lessthandot.com/index.php/DataMgmt/DataDesign/dynamic-pivot-on-multiple-columns/

Compare the most recent row with the immediate previous in the same table

I am facing this problem where I need to compare the most recent row with the immediate previous one based on the same criteria (it will be trader in this case).
Here is my table:
ID Trader Price
-----------------
1 abc 5
2 xyz 5.2
3 abc 5.7
4 xyz 5
5 abc 5.2
6 abc 6
Here is the script
CREATE TABLE Sale
(
ID int not null PRIMARY KEY ,
trader varchar(10) NOT NULL,
price decimal(2,1),
)
INSERT INTO Sale (ID,trader, price)
VALUES (1, 'abc', 5), (2, 'xyz', 5.2),
(3, 'abc', 5.7), (4, 'xyz', 5),
(5, 'abc', 5.2), (6, 'abc', 6);
So far I am working with this solution that is not perfect yet
select
a.trader,
(a.price - b.price ) New_price
from
sale a
join
sale b on a.trader = b.trader and a.id > b.ID
left outer join
sale c on a.trader = c.trader and a.id > c.ID and b.id < c.ID
where
c.ID is null
Above is not perfect because I want to compare only the most recent with the immediate previous on... In this sample for example
Trader abc : I will compare only id = 6 and id = 5
Trader xyz : id = 4 and id = 2
Thanks for any help!
If you are using SQL Server 2012 or later, you can use functions LEAD and LAG to join previous and next data. Unfortunately these function can only be used in SELECT or ORDER BY clause, so you will need to use subquery to get the data you need:
SELECT t.trader, t.current_price - t.previous_price as difference
FROM (
SELECT
a.trader,
a.price as current_price,
LAG(a.price) OVER(PARTITION BY a.trader ORDER BY a.ID) as previous_price,
LEAD(a.price) OVER(PARTITION BY a.trader ORDER BY a.ID) as next_price
FROM sale a
) t
WHERE t.next_price IS NULL
Here in your subquery you create additional columns for previous and next value. Then in your main query you filter only these rows where next price is NULL - that indicates this is the last row for the specific trader.

Trouble creating SQL Query to update line item amounts

I'm going to preface this question with the disclaimer that creating what I call "complex" queries isn't in my forte. Most of the time, there is a much simpler way to accomplish what I'm trying to accomplish so if the below query isn't up to par, I apologize.
With that said, I have a table that keeps track of Vendor Invoices and Vendor Invoice Items (along with a Vendor Invoice Type and Vendor Invoice Item Type). Our bookkeeper wants a report that simply shows: Vendor | Location | Inv Number | Inv Type | Item Type | Inv Date | Rental Fee | Restock Fee | Shipping Fee | Line Item Cost | Total (Line Item + Fees)
Most of the time, one vendor invoice is one line. However, there are exceptions where a vendor invoice can have many item types, thus creating two rows. Not a big deal EXCEPT the fees (Rental, Restock, Shipping) are attached to the Vendor Invoice table. So, I first created a query that checks the temp table for Invoices that have multiple rows, takes the last row, and zero's out the fees. So that only one line item would have the fee. However, our bookkeeper doesn't like that. Instead, she'd like the fees to be "distributed" among the line items.
So, if a vendor invoice has a $25 shipping charge, has two line items, then each line item would be $12.50.
After working with the query, I got it to update the Last Row to be the adjusted amount but row 1+ would have the original amount.
I'm going to post my entire query here (again - I'm sorry that this may not be the best looking query; however, suggestions are always welcome)
DROP TABLE #tVendorInvoiceReport
DROP TABLE #tSummary
SELECT v.Name AS Vendor ,
vii.Location ,
vi.VendorInvNumber ,
vit.Descr AS InvoiceType ,
vii.VendorInvoiceItemType ,
CONVERT(VARCHAR(10), vi.VendorInvDate, 120) VendorInvDate ,
vi.RentalFee ,
vi.RestockFee ,
vi.ShippingFee ,
SUM(vii.TotalUnitCost) TotalItemCost ,
CONVERT(MONEY, 0) TotalInvoice ,
RowID = IDENTITY( INT,1,1)
INTO #tVendorInvoiceReport
FROM dbo.vVendorInvoiceItems AS vii
JOIN dbo.VendorInvoices AS vi ON vii.VendorInvID = vi.VendorInvID
JOIN dbo.Vendors AS v ON vi.VendorID = v.VendorID
JOIN dbo.VendorInvoiceTypes AS vit ON vi.VendorInvTypeID = vit.VendorInvTypeID
WHERE vi.VendorInvDate >= '2012-01-01'
AND vi.VendorInvDate <= '2012-01-31'
GROUP BY v.Name ,
vii.Location ,
vi.VendorInvNumber ,
vit.Descr ,
vii.VendorInvoiceItemType ,
CONVERT(VARCHAR(10), vi.VendorInvDate, 120) ,
vi.RentalFee ,
vi.RestockFee ,
vi.ShippingFee
ORDER BY v.Name ,
vii.Location ,
vi.VendorInvNumber ,
vit.Descr ,
vii.VendorInvoiceItemType ,
CONVERT(VARCHAR(10), vi.VendorInvDate, 120)
SELECT VendorInvNumber ,
COUNT(RowID) TotalLines ,
MAX(RowID) LastLine
INTO #tSummary
FROM #tVendorInvoiceReport
GROUP BY VendorInvNumber
WHILE ( SELECT COUNT(LastLine)
FROM #tSummary AS ts
WHERE TotalLines > 1
) > 0
BEGIN
DECLARE #LastLine INT
DECLARE #NumItems INT
SET #LastLine = ( SELECT MAX(LastLine)
FROM #tSummary AS ts
WHERE TotalLines > 1
)
SET #NumItems = ( SELECT COUNT(VendorInvNumber)
FROM #tVendorInvoiceReport
WHERE VendorInvNumber IN (
SELECT VendorInvNumber
FROM #tSummary
WHERE LastLine = #LastLine )
)
UPDATE #tVendorInvoiceReport
SET RentalFee = ( RentalFee / #NumItems ) ,
RestockFee = ( RestockFee / #NumItems ) ,
ShippingFee = ( ShippingFee / #NumItems )
WHERE RowID = #LastLine
DELETE FROM #tSummary
WHERE LastLine = #LastLine
--PRINT #NumItems
END
UPDATE #tVendorInvoiceReport
SET TotalInvoice = ( TotalItemCost + RentalFee + RestockFee + ShippingFee )
SELECT Vendor ,
Location ,
VendorInvNumber ,
InvoiceType ,
VendorInvoiceItemType ,
VendorInvDate ,
RentalFee ,
RestockFee ,
ShippingFee ,
TotalItemCost ,
TotalInvoice
FROM #tVendorInvoiceReport AS tvir
I sincerely appreciate anyone who took the time to read this and attempt to point me in the right direction.
Thank you,
Andrew
PS - I did try and remove "WHERE RowID = #LastLine" from the first Update, but that changed the Shipping Fees for the first line with two items to "0.0868" instead of 12.50 ($25/2)
If I understand correctly, you're looking for a way to split something like an invoice shipping fee over one or more invoice items.
I created some sample invoice and invoice item tables shown below and used the
over(partition) clause to split out the shipping per item.
-- sample tables
declare #Invoice table (InvoiceID int, customerID int, Date datetime, ShippingFee float)
declare #InvoiceItem table (InvoiceItemID int identity, InvoiceID int, ItemDesc varchar(50), Quantity float, ItemPrice float)
-- Example 1
insert #Invoice values(1, 800, getdate(), 20);
insert #InvoiceItem values(1, 'Widget', 1, 10.00)
insert #InvoiceItem values(1, 'Wing Nut', 5, 2.00)
insert #InvoiceItem values(1, 'Doodad', 8, 0.50)
insert #InvoiceItem values(1, 'Thingy', 3, 1.00)
-- Example 2
insert #Invoice values(2, 815, getdate(), 15);
insert #InvoiceItem values(2, 'Green Stuff', 10, 1.00)
insert #InvoiceItem values(2, 'Blue Stuff', 10, 1.60)
-- Example 3
insert #Invoice values(3, 789, getdate(), 15);
insert #InvoiceItem values(3, 'Widget', 10, 1.60)
-- query
select
n.InvoiceID,
n.InvoiceItemID,
n.ItemDesc,
n.Quantity,
n.ItemPrice,
ExtendedPrice = n.Quantity * n.ItemPrice,
Shipping = i.ShippingFee / count(n.InvoiceItemID) over(partition by n.InvoiceID)
from #InvoiceItem n
join #Invoice i on i.InvoiceID = n.InvoiceID
Output:
InvoiceID InvoiceItemID ItemDesc Quantity ItemPrice ExtendedPrice Shipping
1 1 Widget 1 10 10 5
1 2 Wing Nut 5 2 10 5
1 3 Doodad 8 0.5 4 5
1 4 Thingy 3 1 3 5
2 5 Green Stuff 10 1 10 7.5
2 6 Blue Stuff 10 1.6 16 7.5
3 7 Widget 10 1.6 16 15

Resources