Merge rows in SQL with specific logic - sql-server

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

Related

How to add and deduct quantity in inventory for SQL Server

I am developing simple inventory system but I am having hard time getting the accurate stock on hand after calculating the 3 quantities from 3 different tables. My goal is to add the sum of receiving_stock + sum of returning_stock - the outgoing_stock. But if outgoing_stock has no data all the records are null.
Here’s my data and query.
receiving_stock
Prod_ID, Qty
123 10
124 10
returning_stock
Prod_ID, Qty
124 10
125 10
outgoing_stock
No Data Yet
Actual Result:
Prod_id, qty
Null 10
Null 20
Null 10
Desired Result:
Prod_id, qty
123 10
124 20
125 10
Query:
Select prod_id, isnull(qty,0)-isnull(sold,0) on-hand
Select prod_id, sum(qty) qty
(
select prod_id,qty
From receiving_stock
Union all
select prod_id,qty
From returning_stock
) Za
Group by prod_id
) Zb
Left join
(
From
Select prod_id, sum(qty) sold
From outgoing_stock
Group by prod_id
) zc
) zd
On
Zb.prod_id=zd.prod_id
You can include the third table in UNION ALL with a negative quantity :
SELECT prod_id,qty
From receiving_stock
UNION ALL
SELECT prod_id,qty
From returning_stock
UNION ALL
SELECT prod_id, -qty
From outgoing_stock

Create Hierarchy from Ordered Table

I have a table that has parent-child relationship in one column.
I can use a row number that has been added when the file was loaded to sort the table into the correct order.
I would like to get a full hierarchy from the column using the sort order
Sample Data
RowNumber Type Area Name
1 1 Europe Bob
2 2 Scotland Bill
3 3 Edinburgh Dave
4 2 England Sharron
5 3 London Tessa
6 2 Spain Steve
7 2 Portugal Carie
8 1 Asia Helen
9 2 Thailand John
1 2 Japan Frank
11 3 Tokyo Kate
12 3 Osaka Brian
13 1 North America Joe
I would like to be able to say exclude rows where level 1 = Asia.
Happy to use temporary tables or anything to get this working!
Help!
I'm going by a guess on here, but it looks like you're looking for the previous level with a value of type 1 lower. This isn't how traditional hierarchical data is stored, so we need to fix that.
First some consumable Sample Data:
CREATE TABLE dbo.SampleData (RowNumber int,
[Type] int,
Area varchar(30),
[name] varchar(30));
GO
INSERT INTO dbo.SampleData (RowNumber,
[Type],
Area,
[name])
VALUES(1,1,'Europe','Bob'),
(2,2,'Scotland','Bill'),
(3,3,'Edinburgh','Dave'),
(4,2,'England','Sharron'),
(5,3,'London','Tessa'),
(6,2,'Spain','Steve'),
(7,2,'Portugal','Carie'),
(8,1,'Asia','Helen'),
(9,2,'Thailand','John'),
(10,2,'Japan','Frank'),
(11,3,'Tokyo','Kate'),
(12,3,'Osaka','Brian'),
(13,1,'North America','Joe');
GO
Now we need to add a new column for the parent's ID (row number):
ALTER TABLE dbo.SampleData ADD ParentID int;
Then we need to populate that column:
UPDATE SD
SET ParentID = P.RowNumber
FROM dbo.SampleData SD
CROSS APPLY (SELECT TOP 1 ca.RowNumber
FROM dbo.SampleData ca
WHERE ca.[Type] = SD.[Type] - 1
AND ca.RowNumber < SD.RowNumber
ORDER BY ca.RowNumber DESC) P;
Now we can achieve what you're after, such as getting all rows that aren't related to 'Asia' by use of a rCTE (recursive Common Table Expression):
WITH rCTE AS(
SELECT RowNumber,
[Type],
Area,
[name]
FROM dbo.SampleData
WHERE [Type] = 1
AND Area != 'Asia'
UNION ALL
SELECT SD.RowNumber,
SD.Type,
SD.Area,
SD.name
FROM rCTE r
JOIN dbo.SampleData SD ON r.RowNumber = SD.ParentID)
SELECT r.RowNumber,
r.[Type],
r.Area,
r.[name]
FROM rCTE r;
If you have a known or maximum number of levels
Example
Select *
,ID = RowNumber
,Pt = choose( [Type]-1
,max(case when [Type]=1 then RowNumber end) over (Order By RowNumber)
,max(case when [Type]=2 then RowNumber end) over (Order By RowNumber)
,max(case when [Type]=3 then RowNumber end) over (Order By RowNumber)
,max(case when [Type]=4 then RowNumber end) over (Order By RowNumber)
,max(case when [Type]=5 then RowNumber end) over (Order By RowNumber)
)
From YourTable A
Returns

How to get previous record if current record does not exist in table

my table like this
Id Date type quantity
1 29/04/2019 APPLE 2
2 29/04/2019 Banana 15
3 29/04/2019 Mango 100
4 29/04/2019 Grapes 50
5 29/04/2019 Fish 80
6 30/04/2019 APPLE 4
7 30/04/2019 Grapes 100
8 30/04/2019 Fish 90
9 01/05/2019 APPLE 6
10 01/05/2019 Banana 30
11 01/05/2019 Grapes 150
12 01/05/2019 Fish 100
13 02/05/2019 Mango 200
14 02/05/2019 Grapes 200
15 02/05/2019 Fish 110
16 03/05/2019 APPLE 8
17 03/05/2019 Banana 45
18 03/05/2019 Mango 300
19 04/05/2019 APPLE 10
20 04/05/2019 Grapes 300
21 04/05/2019 Fish 120
22 05/05/2019 APPLE 12
23 05/05/2019 Fish 130
i miss some inputs every day,But i need to fill the gaps with previous row of the same "Type" on 30/04/2019 i missed "Banana & Mango" bu i need like
Id Date type quantity
1 29/04/2019 APPLE 2
2 29/04/2019 Banana 15
3 29/04/2019 Mango 100
4 29/04/2019 Grapes 50
5 29/04/2019 Fish 80
6 30/04/2019 APPLE 4
7 30/04/2019 Grapes 100
8 30/04/2019 Fish 90
9 30/04/2019 Banana 15
10 30/04/2019 Mango 100
actually last two rows are null but it should updated same on 29/04/2019
I think the easiets way might be this:
DECLARE #PDate DATE = SELECT TOP 1 Date FROM YourTable ORDER BY Date ASC --Previous Date
DECLARE #NDate DATE = SELECT TOP 1 Date FROM YourTable WHERE DATE>#PDate --Next Date
WHILE (#NDate IS NOT NULL)
BEGIN
WITH X AS
(
SELECT T1.Date AS Date1, T1.Type AS Type1, T1.Quantity AS Q1
T2.Date AS Date2, T2.Type AS Type2, T2.Quantity AS Q2
FROM YourTable T1
LEFT JOIN YourTable T2 ON T1.Type = T2.Type
WHERE T1.Date = #PDate AND T2.Date = #NDate
)
INSERT INTO YourTable (Date,Type,Quantity)
SELECT #NDate,Type1,Q1
WHERE X.Type2 IS NULL
SET #PDate = #NDate
SET #NDate = NULL -- If next result wasnt found this stays null for while condition
SET #NDate = SELECT TOP 1 Date FROM YourTable WHERE Date>#PDate
END
I think this is the way that may work and I wish so
( if there is any syntax or ... mistakes its because I didnt have SSMS installed to test. Sorry)
try this :
declare #date date
and for initiate #date you can use select #date=max(date) from table1 or pass static value set #date='02/01/2019'
and then find input
select input,max(date) as MaxDate into #temp
from table1
where input not in (select input from table1 where date=#date )
group by input
then :
select t.* from Table1 t join #temp on Table1.input=#temp.Input and Table1.date=#temp.MaxDate
OK, after the goal posts are settled, this is one method. Note that this solution builds both a Types and Dates dataset. Really the Types dataset should already exist somewhere in your database, and you should create a Calendar Table if you're going to be doing this type of work often.
Any way, I've left comments in the code for you. I've assumed you're using SQL Server 2012+, as 2008 is literally about to run out of support.
CREATE TABLE dbo.MyTable (ID int IDENTITY(1,1),
[date] date,
[type] varchar(10),
Quantity int);
INSERT INTO dbo.MyTable
SELECT CONVERT(date,[date],103),
RTRIM([Type]),
Quantity
FROM (VALUES('29/04/2019','APPLE ',2),
('29/04/2019','Banana',15),
('29/04/2019','Mango ',100),
('29/04/2019','Grapes',50),
('29/04/2019','Fish ',80),
('30/04/2019','APPLE ',4),
('30/04/2019','Grapes',100),
('30/04/2019','Fish ',90),
('01/05/2019','APPLE ',6),
('01/05/2019','Banana',30),
('01/05/2019','Grapes',150),
('01/05/2019','Fish ',100),
('02/05/2019','Mango ',200),
('02/05/2019','Grapes',200),
('02/05/2019','Fish ',110),
('03/05/2019','APPLE ',8),
('03/05/2019','Banana',45),
('03/05/2019','Mango ',300),
('04/05/2019','APPLE ',10),
('04/05/2019','Grapes',300),
('04/05/2019','Fish ',120),
('05/05/2019','APPLE ',12),
('05/05/2019','Fish ',130)) V([date],[Type],Quantity);
GO
--SELECT *
--FROM dbo.MyTable;
GO
--Create a calendar table
WITH N AS (
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3), --1000 days shuld be enough
Dates AS(
SELECT DATEADD(DAY, T.I, MIN(MT.[date])) AS [Date]
FROM Tally T
CROSS JOIN dbo.MyTable MT
GROUP BY T.I
HAVING DATEADD(DAY, T.I, MIN(MT.[date])) <= MAX([Date])),
--Get Types
Types AS (
SELECT DISTINCT [Type]
FROM dbo.MyTable MT),
--Create islands
Grps AS(
SELECT MT.ID,
D.[Date],
T.[Type],
MT.Quantity,
COUNT(MT.Quantity) OVER (PARTITION BY T.[Type] ORDER BY D.[date]
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Grp
FROM Dates D
CROSS JOIN Types T
LEFT JOIN dbo.MyTable MT ON D.[Date] = MT.[date]
AND T.[type] = MT.[type])
SELECT G.ID AS ID,
ROW_NUMBER() OVER (ORDER BY G.[Date], G.[Type]) AS RN,
G.[Date],
G.[Type],
MAX(G.Quantity) OVER (PARTITION BY G.[Type], G.Grp) AS Quantity
FROM Grps G
ORDER BY G.[Date],
G.[Type];
GO
DROP TABLE dbo.MyTable;
db<>fiddle
I think using cursor is a good option to insert your missing entries in the table. By cursor you will be able to check date wise missing types and insert them with the previous quantity.
You can also use this following script to find the missing records in your table. To create the script I consider the table name = 'add_missing_records'
SELECT AA.date AS [Date],
AA.type AS [Type],
BB.quantity AS [Original Quantity] ,
CASE
WHEN BB.quantity IS NULL THEN
(
SELECT quantity
FROM add_missing_records C
WHERE C.date = (
SELECT MAX([date])
FROM add_missing_records B
WHERE B.date < AA.date
AND B.type = AA.type
)
AND C.type = AA.type
)
ELSE BB.quantity
END AS [New Quantuty]
FROM (
SELECT date,type
FROM (
SELECT DISTINCT 'A' AS common,date
FROM add_missing_records
)A
FULL JOIN (
SELECT DISTINCT 'A' as common, type
FROM add_missing_records
)B
ON a.common = b.common
) AA
LEFT JOIN add_missing_records BB
ON AA.date = BB.date
AND AA.type = BB.type
WHERE BB.quantity IS NULL
ORDER BY 1,2

second highest salary in each department

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

How to get total sum

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

Resources