I have two tables.
Sales
------
ID Charge VAT
1 100 10
2 200 20
SaleProducts
------------
ID Product Auto SalesID
1 aa True 1
2 bb False 1
I want to get this
SaleOnProduct
-------------
ID Product Charge VAT Total TotalAmount(All of total is plus)
1 aa 100 10 110 220
2 aa 100 10 110 220
How can I do this. Please help me.
declare #Sales table (ID int, Charge int, VAT int)
declare #SaleProducts table (ID int, Product char(2), Auto bit, SalesID int)
insert into #Sales values
(1, 100, 10),
(2, 200, 20)
insert into #SaleProducts values
(1, 'aa', 1, 1),
(2, 'bb', 0, 1)
select
SP.ID,
SP.Product,
S.Charge,
S.VAT,
S.Charge+S.VAT as Total,
sum(S.Charge+S.VAT) over() as TotalAmount
from #Sales as S
inner join #SaleProducts as SP
on S.ID = SP.SalesID
In order to get data from both tables in the same resultset you need to do a join.
Since you want to have a summary row for all sales ((charge+VAT)*numberofSaleProductsRows) of each particular product, you need to use the SUM aggregate funciton, and a GROUP BY clause. All the columns which you need in your resultset and which do not have a specified aggregation needs to be included in the GROUP BY list.
Disclaimer: Untested code
SELECT ID, Product, Charge ,VAT, Charge + VAT as Total,
Sum(Charge + VAT) as TotalAmount
FROM Sales INNER JOIN SaleProducts
ON Sales.ID = SaleProducts.SalesID
GROUP BY ID, Product, Charge, VAT, Charge + VAT
This query do the job: (I supposed SQL Server 2005 or above, otherwise you´ve to change the cte by a temp table)
WITH ReportCte as(
SELECT b.Id IdSale,
a.Id IdProduct,
a.Product,
b.Charge,
b.VAT,
b.Charge+b.VAT Total
FROM [dbo].[SaleProducts] a left join
[dbo].[Sales] b on a.[SalesID] = b.ID)
SELECT a.IdProduct,
a.IdProduct,
a.Charge,
a.VAT,
a.Total,
b.TotalAmount
FROM ReportCte a left join
(select IdSale,SUM(Total) TotalAmount
from ReportCte group by IdSale) b on a.IdSale=b.IdSale
select *, (charge+VAT) as total, SUM(charge+VAT) as totalAmount
from sales
join saleproducts on sales.ID=saleproducts.salesID
group by sales.ID
Related
I have the following table:
ProductCategory
Qty
Price
Wood
2
40
Metal
2
20
Glass
2
40
Other
2
30
Misc
3
10
Extra
5
20
I would like to merge Other, Misc and Extra categories as "Other" category. Qty and Price can have sum of Other, Misc and Extra categories.
Product Category
Qty
Price
Wood
2
40
Metal
2
20
Glass
2
40
Extra
10 (i.e. 2+3+5)
60 (i.e. 30 + 10 + 20)
One of the ways is to:
-- Create temp table to hold sum of Other, Misc, Extra
DECLARE #Qty AS INT, #Price AS INT
SELECT #Qty = Sum(Qty), #Price = Sum(Price)
FROM Product
WHERE ProductCategory IN ('Other', 'Extra', 'Misc')
DELETE FROM Product
WHERE ProductCategory IN ('Other', 'Extra', 'Misc')
INSERT INTO Product (ProductCategory, Qty, Price)
VALUES ('Extra', #Qty , #Price)
What is the easiest way to do this using SQL?
You could do this using a case epxression with group by
select v.ProductCategory, Sum(qty) Qty, Sum(price) Price
from t
cross apply (values(
case when productcategory in ('Misc','Extra') then 'Other' /*or Extra...?*/
else ProductCategory
end)
)v(ProductCategory)
group by v.ProductCategory
Example fiddle
Using an OUTPUT to a temp table
declare #tmp table(Qty int, Price int);
delete tbl
output deleted.qty, deleted.price into #tmp (Qty, Price)
where ProductCategory in ('Misc','Other');
update t
set Qty = t.Qty + u.qty , Price = t.Price + u.Price
from tbl t
join (select sum(Qty) qty, sum(Price) Price
from #tmp) u
on t.ProductCategory = 'Extra';
db<>fiddle
I am trying to find the second highest salary in each department.
Schema:
CREATE TABLE employees
(
ID int NOT NULL,
NAME char(50) NOT NULL,
departmentid int,
salary int
);
Sample records:
/*departmentid =1 */
INSERT INTO employees VALUES (1, 'Max', 1, 90000);
INSERT INTO employees VALUES (2, 'Joe', 1, 70000);
INSERT INTO employees VALUES (3, 'Randy', 1, 70000);
/*departmentid =2 */
INSERT INTO employees VALUES (4, 'Henry', 2, 80000);
INSERT INTO employees VALUES (5, 'SAM', 2, 60000);
/*departmentid =3 */
INSERT INTO employees VALUES (6, 'Janet', 3, 69000);
My query:
SELECT departmentid,
NAME,
salary
FROM
(
SELECT
departmentid,
NAME,
salary,
Dense_rank()OVER (partition BY departmentid
ORDER BY salary DESC) AS Rank,
Count(1)OVER(partition BY departmentid) AS cnt
FROM
employees
)t
WHERE
t.rank = 2
OR ( t.rank = 1
AND cnt = 1 )
The output I am getting is as below;
departmentid NAME salary
1 Joe 70000
1 Randy 70000
2 SAM 60000
3 Janet 69000
My expected output is
departmentid NAME salary
1 Joe 70000
1 Randy 70000
2 SAM 60000
3 NULL NULL
As there is only one record for departmentid=3, it should return null.
What is wrong with this query? Any other ways to achieve this result?
I've also included a SQL fiddle.
ROW_NUMBER() and select = 2
;WITH salary AS
(
[RN] = SELECT ROW_NUMBER() OVER (PARTITION BY departmentid ORDER BY salary),*
FROM <table>
)
SELECT
*
FROM salary
WHERE [RN] = 2
I've used two CTEs.
The first returns a list of every department. You'll need this to ensure departments with less than 2 salaries are included in the final result.
The second ranks each employee within their department.
Finally, I've used a left outer join to maintain the complete list of departments.
WITH Department AS
(
-- Returns a list of the departments.
SELECT
departmentid
FROM
employees
GROUP BY
departmentid
),
EmployeeRanked AS
(
SELECT
DENSE_RANK() OVER (PARTITION BY departmentid ORDER BY salary DESC) AS [Rank],
departmentid,
NAME,
salary
FROM
employees
)
SELECT
er.Rank,
d.departmentid,
er.NAME,
er.salary
FROM
Department AS d
LEFT OUTER JOIN EmployeeRanked AS er ON er.departmentid = d.departmentid
AND er.[Rank] = 2
;
Returns
Rank departmentid NAME salary
2 1 Joe 70000
2 1 Randy 70000
2 2 SAM 60000
(null) 3 (null) (null)
Use a sub query as i wrote here : http://sqlfiddle.com/#!6/bb5e1/26
with ranks as(
SELECT departmentid,
salary,
row_number() over (partition by (departmentid) order by salary desc) as rank
FROM employees
)
Select *
from ranks
Where ranks.rank = 2
If the departmentid having only one row, and if you consider that also. Then
Query
;with cte as(
select [rank] = dense_rank() over(
partition by departmentid
order by departmentid, salary desc
), *
from employees
)
select ID, NAME, departmentid, salary from cte
where [rank] = 2
union all
select max(ID), max(NAME), departmentid, max(salary)
from cte
group by departmentid
having count([rank]) = 1;
There is also a simple way:
SELECT TOP 1 * FROM (Select top 2 * FROM employees order by salary desc ) e Order by salary asc
Edit: this returns only the 2nd highest overall
I think you can get correct answer by just removing below code from your code
OR ( t.rank = 1
AND cnt = 1 )
also main table should be left join from this result to get null in rest of columns
I have a SQL query which performs a simple left join based on the given conditions which is as follows
SELECT
a.Product,a.Grade,a.Term,a.Bid,
a.Offer,b.Ltrd
FROM CCS AS a
left JOIN LTR AS b ON b.Product=a.Product
and b.Grade=a.Grade
and b.Term=a.Term
I have two tables CCS and LTR with following data
CCS Table
Id Product Grade Term Bid Offer
1 Xyz A Jan 20 30
2 XYz A Jan 25 35
3 abc B Feb 25 30
LTR Table
Id Product Grade Term Ltrd
1 Xyz A Jan 500
2 XYz A Jan 400
Upon running the above query it looks to match Product,Grade,Term and if all the three are equal, it performs left join and gives the following results
Product Grade Term Bid Offer Ltrd
Xyz A Jan 20 30 500
Xyz A Jan 20 30 400
XYz A Jan 25 35 500
XYz A Jan 25 35 400
abc B Feb 25 30 NULL
above returned 5 rows and I trying to get only three rows that are in the table CCS with the lowest value of the column Ltrd(ie 400) in LTR table as shown below
Product Grade Term Bid Offer Ltrd
Xyz A Jan 20 30 400
Xyz A Jan 25 35 NULL
abc B Feb 25 30 NULL
In the above results in Ltrd column I just want the lowest value taken from the match in LTR table and assign it to Ltrd in first row and make other one NULL(in the above case 2nd row) and in third row it of course NULL as there is no match in LTR table
One way is to use a CTE. It is essentially the same query, but we use ROW_NUMBER() to a) break the groups up based on Product, Grade, and Term, and b) sort within those groups based on Ltrd. The sort pushes the lowest Ltrd value within each grow to the first row of each group, and then we just select the first row from each group.
DECLARE #CCS TABLE (Id INT, Product VARCHAR(20), Grade VARCHAR(5), Term VARCHAR(5),
Bid INT, Offer INT);
INSERT INTO #CCS VALUES (1, 'Xyz', 'A', 'Jan', 20, 30);
INSERT INTO #CCS VALUES (2, 'XYz', 'A', 'Jan', 25, 35);
INSERT INTO #CCS VALUES (3, 'abc', 'B', 'Feb', 25, 30);
DECLARE #LTR TABLE (Id INT, Product VARCHAR(20), Grade VARCHAR(5), Term VARCHAR(5),
Ltrd INT)
INSERT INTO #LTR VALUES (1, 'Xyz', 'A', 'Jan', 500);
INSERT INTO #LTR VALUES (2, 'XYz', 'A', 'Jan', 400);
;WITH cte AS
(
SELECT b.Product, b.Grade, b.Term, b.Ltrd,
ROW_NUMBER() OVER
(PARTITION BY b.Product, b.Grade, b.Term ORDER BY b.Ltrd ASC)
AS [RowNum]
FROM #LTR b
)
SELECT a.Product, a.Grade, a.Term, a.Bid, a.Offer,
CASE WHEN ROW_NUMBER()
OVER (PARTITION BY a.Product, a.Grade, a.Term ORDER BY a.Id ASC) = 1
THEN b.Ltrd
ELSE NULL
END AS [Ltrd]
FROM #CCS a
LEFT JOIN cte b
ON b.Product = a.Product
AND b.Grade = a.Grade
AND b.Term = a.Term
AND b.RowNum = 1
ORDER BY a.Id ASC;
Results:
Product Grade Term Bid Offer Ltrd
Xyz A Jan 20 30 400
XYz A Jan 25 35 NULL
abc B Feb 25 30 NULL
--
And just to have options, another way that might look nicer is to use OUTER APPLY:
SELECT a.Product, a.Grade, a.Term, a.Bid, a.Offer,
CASE WHEN ROW_NUMBER() OVER
(PARTITION BY a.Product, a.Grade, a.Term ORDER BY a.Bid, a.Offer ASC)
= 1 THEN c.Ltrd
ELSE NULL END AS [Ltrd]
FROM #CCS a
OUTER APPLY (SELECT TOP (1) b.Ltrd
FROM #LTR b
WHERE b.Product = a.Product
AND b.Grade = a.Grade
AND b.Term = a.Term
ORDER BY b.Ltrd ASC
) c
The results are the same. This might perform a little worse as OUTER APPLY runs the query inside of the parens (or function if that was specified instead of a query) for each row of the outer table. But sometimes it is good to see how these things work as it might help solve a different problem later. The CROSS APPLY and OUTER APPLY clauses can be quite helpful
You could use a derived table with the minimum value as source for the left join:
SELECT
a.Product,
a.Grade,
a.Term,
a.Bid,
a.Offer,
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY a.Product ORDER BY a.Id) = 1
THEN b.Ltrd ELSE NULL
END AS LTRD
FROM CCS AS a
LEFT JOIN (
SELECT Product, Grade, Term, MIN(ltrd) ltrd
FROM LTR
GROUP BY Product, Grade, term
) AS b
ON b.Product = a.Product
AND b.Grade = a.Grade
AND b.Term = a.Term
ORDER BY a.Id
Sample SQL Fiddle
I am trying to do a complex query (at least, it is complex for me) on SQL Server 2008 and so far I can come this far. Here is the code;
DECLARE #Hotels AS TABLE(
HotelID INT,
HotelName NVARCHAR(100)
);
DECLARE #HotelAllotments AS TABLE(
HotelID INT,
StartDate DATETIME,
EndDate DATETIME,
Allotment INT
);
DECLARE #Reservations AS TABLE(
ReservationID INT,
HotelID INT,
CheckIn DATETIME,
CheckOut DATETIME,
IsCanceled BIT
);
INSERT #Hotels VALUES(1,'Foo Hotel');
INSERT #Hotels VALUES(2,'Poo Hotel');
INSERT #HotelAllotments VALUES(1,'2011-01-01', '2011-02-01', 10);
INSERT #HotelAllotments VALUES(1,'2011-02-02', '2011-02-18', 7);
INSERT #HotelAllotments VALUES(1,'2011-02-19', '2011-05-18', 19);
INSERT #HotelAllotments VALUES(1,'2011-05-19', '2011-10-18', 30);
INSERT #HotelAllotments VALUES(2,'2011-05-19', '2011-10-18', 30);
INSERT #Reservations VALUES(100, 1, '2011-05-10','2011-05-24',0);
INSERT #Reservations VALUES(101, 1, '2011-05-18','2011-05-28',0);
INSERT #Reservations VALUES(102, 1, '2011-03-07','2011-03-19',0);
INSERT #Reservations VALUES(103, 1, '2011-08-29','2011-09-07',0);
INSERT #Reservations VALUES(104, 1, '2011-09-01','2011-09-07',1);
INSERT #Reservations VALUES(105, 1, '2011-09-01','2011-09-07',1);
with e as(
SELECT ReservationID as resid1, CheckIn as chin1, 1 as lvl
FROM #Reservations res1
WHERE res1.HotelID = 1
UNION ALL
SELECT ReservationID as resid2, DATEADD(DAY,1,stall.chin1) as chin2, 1
FROM #Reservations res2
INNER JOIN e stall ON stall.chin1 < res2.CheckOut
WHERE stall.resid1 = res2.ReservationID
)
SELECT tb.chin1, SUM(lvl)
FROM e tb
GROUP BY tb.chin1
ORDER BY tb.chin1 DESC
On #HotelAllotments section, there are start and end dates as you can see. The allotment is for daily basis. I mean if row is like below;
INSERT #HotelAllotments VALUES(1,'2011-01-01', '2011-01-03', 10);
It means this;
The Hotel whose id is 1 has 10 allotment on 2011-01-01
The Hotel whose id is 1 has 10 allotment on 2011-01-02
The Hotel whose id is 1 has 10 allotment on 2011-01-03
Then, after that if we receive a reservation between 2011-01-01 and 2011-01-03, like below;
INSERT #Reservations VALUES(106, 1, '2011-01-01','2011-01-03',0);
The situation will be as below;
The Hotel whose id is 1 has 9 allotment left after the reservation on 2011-01-01
The Hotel whose id is 1 has 9 allotment left after the reservation on 2011-01-02
The Hotel whose id is 1 has 10 allotment left after the reservation on 2011-01-03
Above, I have created some temp tables and inserted some fake values and I tried a query. It gets me somewhere (I don't know how to call it. So if you have a
chance to run the query, you would see where it has gotten me so far) but not the place I need. What I need here is that;
I need to list all the dates which a hotel has an agreement and its left allotments after received reservations. here is an example;
HotelID Date Allotment
------- ---------- ---------
1 2011-01-01 9
1 2011-01-02 9
1 2011-01-03 10
1 2011-01-04 10
1 2011-01-05 10
So how can I achieve this?
EDIT
Some them should wonder why an allotment is taken away for the first two days of the reservation, but not the last one. It is because the guest wouldn't be staying all day at the hotel at the last day. S/he should empty the room until 12:00 am. So there won't be any allotment usage on the last date.
;WITH expanded AS (
SELECT
a.HotelID,
Date = DATEADD(DAY, v.number, a.StartDate),
a.Allotment
FROM #HotelAllotments a
INNER JOIN master..spt_values v ON v.type = 'P'
AND v.number BETWEEN 0 AND DATEDIFF(DAY, a.StartDate, a.EndDate)
),
filtered AS (
SELECT
e.HotelID,
e.Date,
Allotment = e.Allotment - COUNT(r.ReservationID)
FROM expanded e
LEFT JOIN #Reservations r ON e.HotelID = r.HotelID
AND e.Date >= r.CheckIn AND e.Date < r.CheckOut
AND r.IsCanceled = 0
GROUP BY e.HotelID, e.Date, e.Allotment
)
SELECT *
FROM filtered;
This solution uses a system table, master..spt_values, as a tally table to obtain the lists of dates instead of the date ranges. Next, the expanded allotment list is joined with the #Resevations table. For every date in the list, the correpsonding allotment is decreased by the number of reservations whose ranges match the given date.
I was a bit hasty on writing my where clause. I didnt know if you wanted to sort out the blank days. here is what i came up with after setting the where clause. The reason i have the datejumps is to compensate for the limitation of 100 recusive calls in sql. So I join with 10 rows from a system table make better use of the 100 recusive, that way i can get 1000 rows instead of 100.
WITH cte(HOTELID, STARTDATE, ENDDATE, Allotment)
as
(
SELECT H.HOTELID, A.STARTDATE + RN STARTDATE, (SELECT MAX(ENDDATE) FROM #HotelAllotments) ENDDATE, (select Allotment from #HotelAllotments where A.STARTDATE + RN between StartDate and enddate and H.HOTELID = HOTELID) Allotment
FROM (
SELECT MIN(STARTDATE) STARTDATE from #HotelAllotments c
) A,
(SELECT TOP 10 rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1))-1 FROM INFORMATION_SCHEMA.COLUMNS) B,
#Hotels H
UNION ALL
SELECT ch.HOTELID, ch.STARTDATE + 10, ENDDATE, (select Allotment from #HotelAllotments where CH.STARTDATE + 10 between StartDate and enddate and CH.HOTELID = HOTELID)
FROM cte ch
WHERE CH.STARTDATE< ENDDATE
AND CH.HOTELID = HOTELID
)
SELECT HotelID, StartDate Date , Allotment - (select count(*) from #Reservations where cte.STARTDATE between CheckIn and CheckOut and cte.HOTELID = HOTELID) Allotment
FROM CTE where allotment is not null
ORDER BY STARTDATE, HOTELID
I have the follwoing structure:
Emp PayDate Amount
1 11/23/2010 500
1 11/25/2010 -900
1 11/28/2010 1000
1 11/29/2010 2000
2 11/25/2010 2000
3 11/28/2010 -3000
2 11/28/2010 4000
3 11/29/2010 -5000
I need to get the following result if emp 1 is selected (top 3 dates and their corresponding vals - if they exist - 4th row is always ignored)
PayDate1 Amount1 Paydate2 Amount2 Paydate3 Amount3
11/23/2010 500 11/25/2010 -900 11/28/2010 1000
I need to get the following result if emp 2 is selected
Paydate1 Amount1 Paydate2 Amount2 Paydate3 Amount3
11/25/2010 2000 11/28/2010 4000 NULL NULL
I need to get the following result if emp 3 is selected
Paydate1 Amount1 Paydate2 Amount2 Paydate3 Amount3
11/28/2010 -3000 11/29/2010 -5000
To get the respective data in rows I can run the following query:
select top 3 Paydate, Amount from Table where Emp = #Emp
But how do I get result in a pivoted fashion?
There's an excellent article on Pivots with SQL Server 2005+ here.
CREATE TABLE dbo.Table1
(
Emp int,
PayDate datetime,
Amount int
)
GO
INSERT INTO dbo.Table1 VALUES (1, '11/23/2010',500)
INSERT INTO dbo.Table1 VALUES (1, '11/25/2010',-900)
INSERT INTO dbo.Table1 VALUES (1, '11/28/2010',1000)
INSERT INTO dbo.Table1 VALUES (1, '11/29/2010',2000)
INSERT INTO dbo.Table1 VALUES (2, '11/25/2010',2000)
INSERT INTO dbo.Table1 VALUES (3, '11/28/2010',-3000)
INSERT INTO dbo.Table1 VALUES (2, '11/28/2010',4000)
INSERT INTO dbo.Table1 VALUES (3, '11/29/2010',-5000)
;WITH cte AS
(SELECT Emp, PayDate, Amount, PayDateRowNumber
FROM
(SELECT Emp,
PayDate,
Amount,
ROW_NUMBER() OVER (PARTITION BY Emp ORDER BY PayDate) AS PayDateRowNumber
FROM Table1) AS RankedTable1
WHERE PayDateRowNumber < 4)
SELECT c1.Emp AS Emp, c1.PayDate AS PayDate1
,c1.Amount AS Amount1, c2.PayDate AS PayDate2
,c2.Amount AS Amount2, c3.PayDate AS PayDate3, c3.Amount AS Amount3
FROM cte c1
LEFT JOIN cte c2 ON c2.Emp = c1.Emp AND c2.PayDateRowNumber = 2
LEFT JOIN cte c3 ON c3.Emp = c2.Emp AND c3.PayDateRowNumber = 3
WHERE c1.PayDateRowNumber = 1
Output is:
Some caveats are that it won't aggregate amounts for the same employer/date (though can easily be changed). Also may want to change to review use of ROW_NUMBER() versus RANK() and DENSE_RANK() depending on your definition of "TOP 3"