SQL UPDATE rows from related TOP 1 records - sql-server

I have a table Products with 2 columns: ProductID, ProductName.
When I sell those products, I store the sale in 2 tables:
SalesHeaders with 3 columns: IDHeader, EmployeeName, Date
SalesRows with 3 columns: IDHeader, IDRow, ProductID
There are 2 employees, John and Mary.
Now I need to add a new column in my table Products called LastMarySaleDate. So I want to update Products.LastMarySaleDate and for that I need to select the most recent record from SalesHeaders where EmployeeName = 'mary' and SalesRows.ProductID = Products.ProductID.
Here are sample data with the expected results
Products (before UPDATE):
ProductID ProductName LastMarySaleDate
--------- ------------ ----------------
A01 Mouse
A02 Keyboard
A03 Speakers
SalesHeaders:
IDHeader EmployeeName Date
-------- ------------ ----------
1 Mary 2020-05-01
2 Mary 2020-05-02
3 John 2020-05-03
SalesRows:
IDHeader IDRow ProductID
-------- ----- ---------
1 1 A01
1 2 A02
2 3 A01
3 4 A02
3 5 A03
Products (after UPDATE):
ProductID ProductName LastMarySaleDate
--------- ------------ ----------------
A01 Mouse 2020-05-02
A02 Keyboard 2020-05-01
A03 Speakers Note: Empty, since Mary never sold this productID
I've tried
UPDATE Products
SET Products.LastMarySaleDate = H.Date
FROM
(SELECT TOP 1 *
FROM SalesHeaders
LEFT OUTER JOIN SalesRows ON SalesHeaders.IDHeader = SalesRows.IDHeader
WHERE SalesHeaders.EmployeeName = 'Mary'
AND SalesRows.ProductID = Products.ProductID
ORDER BY SalesHeaders.Date DESC) AS H
but I can't figure it out. If anyone could help me with this it would be great, thanks!

I think this is what you are looking for:
CREATE TABLE #Product (ProductID varchar(10), ProductName varchar(20), LastMarySaleDate DATE)
INSERT INTO #Product
VALUES ('A01','Mouse', NULL),
('A02','Keyboard',NULL),
('A03','Speakers',NULL)
CREATE TABLE #SalesHeader(IDHeader INT, EmployeeName varchar(20), DT Date)
INSERT INTO #SalesHeader VALUES (1,'Mary','2020-05-01'),
(2,'Mary','2020-05-02'),
(3,'John','2020-05-03')
CREATE TABLE #SalesRows(IDHeader int, IDRow int, ProductID varchar(10))
INSERT INTO #SalesRows VALUES(1,1,'A01'),
(1,2,'A02'),
(2,3,'A01'),
(3,4,'A02'),
(3,5,'A03')
UPDATE #Product
SET LastMarySaleDate = t2.DT
FROM #Product t1
INNER JOIN
(
SELECT ProductID, MAX(dt) DT
FROM #SalesHeader t1
INNER JOIN #SalesRows t2 on t1.IDHeader = t2.IDHeader
WHERE t1.EmployeeName = 'Mary'
GROUP BY ProductID
) t2 on t1.ProductID = t2.ProductID

Related

If Value is present in two consecutive months , display only one month in sql

I would want to check ID in consecutive months, IF Same ID is present in two consecutive months then consider that ID only for 1st month.
If ID's are not in consecutive month then show the distinct ID's grouped by start date month.(We consider only start date)
For example, ID 1 is present in start date months january and Feb , then Distinct count of this ID will be 1 in Jan, how ever ID 2 and 3 are
present in Jan and March and Feb and May Resp, now I would like to see this distinct count of ID in Jan and March.
Current Data
Table1:
ID StartDate EndDate
1 2017-01-12 2017-01-28
1 2017-01-19 2017-01-28
1 2017-01-29 2017-02-11
1 2017-02-01 2017-02-11
1 2017-02-19 2017-02-24
2 2017-01-12 2017-01-28
2 2017-01-19 2017-01-28
2 2017-03-09 2017-03-20
3 2017-02-12 2017-02-28
3 2017-02-19 2017-02-28
3 2017-05-05 2017-05-29
3 2017-05-09 2017-05-29
I tried with below logic bt I know I am missing on something here.
select t.* from Table1 t
join Table1 t t1
on t1.ID=t.ID
and datepart(mm,t.StartDate)<> datepart(mm,t1.StartDate)+1
Expected Result:
DistinctCount StartDateMonth(In Numbers)
1 1(Jan)
2 1(Jan)
2 3(March)
3 2(Feb)
3 5(May)
Any help is appreciated!
Here's my solution. The thinking for this is:
1) Round all the dates to the first of the month, then work with the distinct dataset of (ID, StartDateRounded). From your dataset, the result should look like this:
ID StartDateRounded
1 2017-01-01
1 2017-02-01
2 2017-01-01
2 2017-03-01
3 2017-02-01
3 2017-05-01
2) From this consolidated dataset, find all records by ID that do not have a record for the previous month (which means it's not a consecutive month and thus is a beginning of a new data point). This is your final dataset
with DatesTable AS
(
SELECT DISTINCT ID
,DATEADD(month,DateDiff(month,0,StartDate),0) StartDateRounded
,DATEADD(month,DateDiff(month,0,StartDate)+1,0) StartDateRoundedPlusOne
FROM Table1
)
SELECT t1.ID, DatePart(month,t1.StartDateRounded) AS StartDateMonth
FROM DatesTable t1
LEFT JOIN DatesTable t2
ON t1.ID = t2.ID
AND t1.StartDateRounded = t2.StartDateRoundedPlusOne
WHERE t2.ID IS NULL; --Verify no record exists for prior month
sqlfiddler for reference. Let me know if this helps
Just need to take advantage of the lag on the inner query to compare values between rows, and apply the logic in question on the middle query, and then do a final select.
/*SAMPLE DATA*/
create table #table1
(
ID int not null
, StartDate date not null
, EndDate date null
)
insert into #table1
values (1, '2017-01-12', '2017-01-28')
, (1, '2017-01-19', '2017-01-28')
, (1, '2017-01-29', '2017-02-11')
, (1, '2017-02-01', '2017-02-11')
, (1, '2017-02-19', '2017-02-24')
, (2, '2017-01-12', '2017-01-28')
, (2, '2017-01-19', '2017-01-28')
, (2, '2017-03-09', '2017-03-20')
, (3, '2017-02-12', '2017-02-28')
, (3, '2017-02-19', '2017-02-28')
, (3, '2017-05-05', '2017-05-29')
, (3, '2017-05-09', '2017-05-29')
/*ANSWER*/
--Final Select
select c.ID
, c.StartDateMonth
from (
--Compare record values to rule a record in/out based on OP's logic
select b.ID
, b.StartDateMonth
, case when b.StartDateMonth = b.StartDateMonthPrev then 0 --still the same month?
when b.StartDateMonth = b.StartDateMonthPrev + 1 then 0 --immediately prior month?
when b.StartDateMonth = 1 and b.StartDateMonthPrev = 12 then 0 --Dec/Jan combo
else 1
end as IncludeFlag
from (
--pull StartDateMonth of previous record into current record
select a.ID
, datepart(mm, a.StartDate) as StartDateMonth
, lag(datepart(mm, a.StartDate), 1, NULL) over (partition by a.ID order by a.StartDate asc) as StartDateMonthPrev
from #table1 as a
) as b
) as c
where 1=1
and c.IncludeFlag = 1
Output:
+----+----------------+
| ID | StartDateMonth |
+----+----------------+
| 1 | 1 |
| 2 | 1 |
| 2 | 3 |
| 3 | 2 |
| 3 | 5 |
+----+----------------+
Try the below query,
SELECT ID,MIN(YEARMONTH) AS YEARMONTH
FROM (
SELECT ID
,YEAR([StartDate])*100+MONTH([StartDate]) AS YEARMONTH
,LAG(YEAR([StartDate])*100+MONTH([StartDate]))
OVER(ORDER BY ID) AS PREVYEARMONTH
,ROW_NUMBER() OVER(ORDER BY ID) AS ROW_NO
FROM #Table1
GROUP BY ID,((YEAR([StartDate])*100)+MONTH([StartDate]))
) AS T
GROUP BY ID
,(CASE WHEN YEARMONTH - PREVYEARMONTH > 1 THEN ROW_NO ELSE 0 END)
ORDER BY ID
Output:
ID YEARMONTH
1 201701
2 201701
2 201703
3 201702
3 201705
Thank you all guys. most of the logic seemed to work..but I tried just with below one and I Was good with thiis.
SELECT t1.ID, DatePart(month,t1.Startdate) AS StartDateMonth
FROM DatesTable t1
LEFT JOIN DatesTable t2
ON t1.ID = t2.ID
AND DatePart(month,t1.Startdate) = DatePart(month,t2.Startdate)+1
WHERE t2.ID IS NULL;
Thanks again
Ok, I wrote my first query without checking, believed that will work correctly. This is my updated version, should be faster than other solutions
select
id
, min(st)%12 --this will return start month
, min(st)/12 + 1 --this will return year, just in case if you need it
from (
select
id, st, gr = st - row_number() over (partition by ID order by st)
from (
select
distinct ID, st = (year(StartDate) - 1) * 12 + month(StartDate)
from
#table2
) t
) t
group by id, gr

Please advise my the stored procedure which is group by and total which different criteria

I have answered one of the interview questions as below.
There are two tables (employee and Department).
Show report No. of people(count) and total salary where IT Dept. salary from 250 to 500 and Sales Dept. salary from 250 to 1000 and Marketing Dept. salary from 250 to 1500.
Sample expected result below
Marketing 0 0.00
Information Technology 1 250.00
Sales 2 1200.00
Employee table
EmpID EmpName DeptID Salary
1 Mike 1 1000.00
2 Paul 1 1500.00
3 John 1 2000.00
4 Joe 2 500.00
5 Kim 3 2000.00
6 Lim 3 2500.00
7 Sam 2 700.00
8 Mario 1 250.00
Department table
DeptID DeptCode DeptName
1 IT Information Technology
2 ST Sales
3 MT Marketing
My Answer:
ALTER PROCEDURE [dbo].[TheseAndThat]
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT dd.DeptName, ISNULL(TT.c,0) AS StaffCount , ISNULL(TT.s,0) AS TotalSalary FROM [dbo].[Department] dd
LEFT JOIN
(
SELECT d.DeptCode AS dcode, COUNT(*) as c, SUM(e.Salary) as s FROM [dbo].[Employee] e
JOIN [dbo].[Department] d
ON e.DeptID = d.DeptID
WHERE e.Salary between 250 and 500 AND d.DeptID = 1
GROUP BY e.DeptID, d.DeptCode
UNION
SELECT d.DeptCode AS dcode, COUNT(*) as c, SUM(e.Salary) as s FROM [dbo].[Employee] e
JOIN [dbo].[Department] d
ON e.DeptID = d.DeptID
WHERE e.Salary between 250 and 1000 AND d.DeptID = 2
GROUP BY e.DeptID, d.DeptCode
UNION
SELECT d.DeptCode AS dcode, COUNT(*) as c, SUM(e.Salary) as s FROM [dbo].[Employee] e
JOIN [dbo].[Department] d
ON e.DeptID = d.DeptID
WHERE e.Salary between 250 and 1500 AND d.DeptID = 3
GROUP BY e.DeptID, d.DeptCode
) TT
ON dd.DeptCode = TT.dcode
ORDER BY TT.c
END
I'm not sure my answer is correct. However, the result seems to be ok.
Please advise.
If I was you I will go this this query (only 1 time scan to employee table)
SELECT d.DeptName, ISNULL(e.NoEmp,0) AS NoEmp, ISNULL(SumSalary,0) AS SumSalary
FROM [dbo].[Department] AS d
LEFT JOIN (
SELECT DeptID, COUNT(EmpID) As NoEmp, SUM (Salary) AS SumSalary
FROM [dbo].[Employee]
WHERE Salary BETWEEN 250 AND CASE WHEN DeptID = 1 THEN 500
WHEN DeptID = 2 THEN 1000
WHEN DeptID = 3 THEN 1500
END
GROUP BY DeptID) AS e ON d.DeptID = e.DeptID
WHERE d.DeptID IN(1,2,3)
First, the code to setup temp tables:
declare #employees table
(
EmpID int,
EmpName varchar(100),
DeptID int,
Salary decimal
)
declare #departament table
(
DeptID int,
DeptCode char(2),
DeptName varchar(100)
)
insert into #employees values (1,'Mike',1,1000.00)
insert into #employees values (2,'Paul',1,1500.00)
insert into #employees values (3,'John',1,2000.00)
insert into #employees values (4,'Joe',2,500.00)
insert into #employees values (5,'Kim',3,2000.00)
insert into #employees values (6,'Lim',3,2500.00)
insert into #employees values (7,'Sam',2,700.00)
insert into #employees values (8,'Mario',1,250.00)
insert into #departament values (1, 'IT', 'Information Technology')
insert into #departament values (2, 'ST', 'Sales')
insert into #departament values (3, 'MT', 'Marketing')
Now, the report:
select DeptName, COALESCE(d2.DeptID, 0), COALESCE(Salaries,0) from #departament d2
left join
(
select COUNT(*) as DeptID, SUM(Salary) as Salaries from #departament d
inner join #employees e on d.DeptID = e.DeptID
where
(d.DeptID = 1 and e.Salary between 250 and 500)
or
(d.DeptID = 2 and e.Salary between 250 and 1000)
or
(d.DeptID = 3 and e.Salary between 250 and 1500)
group by d.DeptID) as sums on sums.DeptID = d2.DeptID
An alternative (uses the same temp tables as #GustavoF answer):
DECLARE #Input TABLE( DeptID INT, SalaryRangeMin decimal, SalaryRangeMax decimal )
INSERT INTO #Input
VALUES
( 1, 250, 500),
( 2 ,250 ,1000 ),
( 3 ,250 , 1500 )
SELECT D.DeptName, COUNT(EmpID) as EmployeeCount, ISNULL( SUM( e.Salary ), 0.0 ) as TotalSalary
FROM #Input AS I
INNER JOIN #departament AS D ON I.DeptID = D.DeptID
LEFT JOIN #Employees AS E ON I.DeptID = E.DeptID AND E.Salary BETWEEN I.SalaryRangeMin AND I.SalaryRangeMax
GROUP BY E.DeptID, D.DeptCode, D.DeptName
ORDER BY TotalSalary ASC
Output:
DeptName EmployeeCount TotalSalary
--------------------------- ------------- -------------
Marketing 0 0
Information Technology 1 250
Sales 2 1200

Retrieve column based on closest date

Thanks for your help in advance. I have an invoice table which is similar to below:
INV DATE | ITEM |SELL PRICE |COST PRICE
------------+-------+-----------+----------
30/06/2016 | DOOR1 |10 |5
The above item is sourced from the EU so I have another table (VEN_HIS) which lists a history of supplier prices for that item as below:
DATE |ITEM |CURRENCY |PRICE
------------+-------+-----------+------
17/05/2017 |DOOR1 |EUR |6
01/01/2017 |DOOR1 |EUR |5.8
29/05/2016 |DOOR1 |EUR |5.6
05/03/2016 |DOOR1 |EUR |5.5
What I want to do is join the tables to drop in the correct currency price we would have paid onto the invoice table as at the invoice date in other words the correct price is 5.6
This is my first post and I need to get some FX analysis done quickly so apologies for the formatting, any help would be greatly appreciated.
Its a simple question of Join and where clause.
create table #t1 (INV_DATE DATE,ITEM varchar(100), SELL_PRICE int, COST_PRICE int);
insert into #t1 values
('30/06/2016','DOOR1',10,5)
create table #ven_his (colDATE date, ITEM varchar(100), CURRENCY varchar(100),PRICE float);
insert into #ven_his values
('17/05/2017', 'DOOR1', 'EUR',6 ),
('01/01/2017', 'DOOR1', 'EUR',5.8),
('29/05/2016', 'DOOR1', 'EUR',5.6),
('05/03/2016', 'DOOR1', 'EUR',5.5)
SELECT top 1 a.INV_DATE, a.ITEM, b.CURRENCY, b.PRICE
FROM #t1 a
left join #ven_his b
on a.ITEM = b.ITEM
WHERE b.colDATE <=a.INV_DATE
ORDER BY b.colDATE DESC
output :
#t1
INV_DATE ITEM SELL_PRICE COST_PRICE
---------- ------ ----------- -----------
2016-06-30 DOOR1 10 5
#ven_his
colDATE ITEM CURRENCY PRICE
---------- --------- ------------ ------
2017-05-17 DOOR1 EUR 6
2017-01-01 DOOR1 EUR 5.8
2016-05-29 DOOR1 EUR 5.6
2016-03-05 DOOR1 EUR 5.5
INV_DATE ITEM CURRENCY PRICE
---------- --------- ----------- --------
2016-06-30 DOOR1 EUR 5.6
DECLARE #invoice table (INV_Date date, Item varchar(100),SellPrice numeric(5,2), Costprice numeric(5,2))
INSERT INTO #invoice
SELECT '20160630','DOOR1',10,5
SELECT * FrOm #invoice
DECLARE #ven_his table(Dates date,Item varchar(100),Currency varchar(10),Price numeric(5,2))
INSERT INTO #ven_his
SELECT '20170517','DOOR1','EUR',6
UNION ALL
SELECT '20170101','DOOR1','EUR',5.8
UNION ALL
SELECT '20160529','DOOR1','EUR',5.6
UNION ALL
SELECT '20160305','DOOR1','EUR',5.5
SELECT * FROM #ven_his
SELECT Top 1 i.Item,INV_Date,PRICE,Dates
FROM #invoice i
JOIN #ven_his v
ON i.Item = v.Item
ORDER BY ABS(DATEDIFF(DAY,i.INV_Date,v.Dates))
Use LEAD function to return the correct price for all items:
Create table #Invoice (INVDATE DATE, ITEM NVARCHAR(20), SELLPRICE SMALLMONEY, COSTPRICE SMALLMONEY)
INSERT #INVOICE VALUES ('2016-06-30', 'DOOR1', 10, 5)
--Test with additional invoices
--INSERT #INVOICE VALUES ('2017-02-22', 'DOOR1', 10, 5), ('2016-09-22', 'DOOR1', 10, 5)
CREATE TABLE #VENHIS ([DATE] DATE, ITEM NVARCHAR(20), CURRENCY NVARCHAR(5), PRICE SMALLMONEY)
INSERT #VENHIS VALUES ('2017-05-17', 'DOOR1', 'EUR', 6), ('2017-01-01', 'DOOR1', 'EUR', 5.8),('2016-05-29', 'DOOR1', 'EUR', 5.6), ('2016-03-05', 'DOOR1', 'EUR', 5.5)
SELECT I.*, V.PRICE
FROM #Invoice i
INNER JOIN (SELECT *, [DATE] as STARTDATE, LEAD([DATE]) OVER (PARTITION BY ITEM ORDER BY ITEM, [DATE] ASC) AS EndDate
FROM #VENHIS ) v ON i.ITEM=v.ITEM AND I.[INVDATE] BETWEEN V.STARTDATE AND DATEADD(D,-1,ISNULL(V.ENDDATE, '2099-01-01'))

Select all rows that has only specific value in records

I have a table that contains id, type.
I want to select all the ids that have only one or more records of the same type.
For example,
Assuming this is my table:
id type
456 4
123 4
123 4
123 18
123 4
789 4
789 4
000 7
I want to get ids: 456,789 cause those ids have only records with type = 4:
456 has one record, and 789 has two records of type = 4.
123 has type = 4, but has type = 18.
How can I do it?
I know I can use partition, but I want something like join/exists..
http://sqlfiddle.com/#!9/731e1
You can use:
SELECT id
FROM cards
GROUP BY id
HAVING MIN(type) = MAX(type)
Demo here
Select Id
FROM cards
GROUP BY Id
HAVING COUNT(DISTINCT [type]) = 1
I don't think #M.Ali answer mets your critera. His resultset includes id = '000'
if OBJECT_ID('Tempdb..#Work') is not null
drop table #Work;
Create Table #Work (Id char(3), [Type] int)
insert into #Work values
( '456', 4)
, ('123', 4)
, ('123', 4)
, ('123', 18)
, ('123', 4)
, ('789', 4)
, ('789', 4)
, ('000', 7)
select distinct *
from #Work a
where exists (
select Type
,Count(Distinct Id) cntId
from #Work b
where a.Type = b.Type
group by Type
having Count(Distinct Id) > 1
)
and exists (
select Id
,count(distinct Type)
from #Work c
where a.Id = c.Id
group by id
having count(distinct type)= 1
)
output:
Id Type
---- -----------
456 4
789 4

Pagination and INNER JOIN

I have a situation which I need to do pagination along with INNET JOIN. Here is a similar scenario I have:
DECLARE #categories AS TABLE(
CatID INT,
CategoryName NVARCHAR(100)
);
DECLARE #fooTable AS TABLE(
ID INT,
CatID INT,
Name NVARCHAR(100),
MinAllow INT,
Price DECIMAL(18,2)
);
INSERT INTO #categories VALUES(1, 'Cat1');
INSERT INTO #categories VALUES(2, 'Cat2');
INSERT INTO #categories VALUES(3, 'Cat3');
INSERT INTO #categories VALUES(4, 'Cat4');
INSERT INTO #categories VALUES(5, 'Cat5');
INSERT INTO #fooTable VALUES(1, 1, 'Product1', 2, 112.2);
INSERT INTO #fooTable VALUES(3, 1, 'Product3', 5, 233.32);
INSERT INTO #fooTable VALUES(6, 1, 'Product6', 4, 12.43);
INSERT INTO #fooTable VALUES(7, 4, 'Product7', 4, 12.43);
INSERT INTO #fooTable VALUES(8, 5, 'Product8', 4, 12.43);
These are the records I have. As you can see, some categories do not have any products inside #fooTable. As a next step, we have the following SELECT statement:
SELECT * FROM #fooTable ft
INNER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY CatID) AS RowNum, * FROM #categories
) AS cat ON (cat.CatID = ft.CatID);
It is a basic JOIN except that the output will also carry the row number of the categories. The result I got for this query is as follows:
ID CatID Name MinAllow Price RowNum CatID CategoryName
---- ------- ------------- ----------- --------- -------- -------- -------------
1 1 Product1 2 112.20 1 1 Cat1
3 1 Product3 5 233.32 1 1 Cat1
6 1 Product6 4 12.43 1 1 Cat1
7 4 Product7 4 12.43 4 4 Cat4
8 5 Product8 4 12.43 5 5 Cat5
When you look at the RowNum column, you will see that those values are not pagination friendly. So, when I try to paginate this table as follows, I got an incorrect output:
SELECT * FROM #fooTable ft
INNER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY CatID) AS RowNum, * FROM #categories
)AS cat ON (cat.CatID = ft.CatID) AND (cat.RowNum BETWEEN 1 AND 2);
The real situation I have is similar to this one but that query is so complicated and I need to get it working with INNER JOIN. I hope I made it clear. Any idea how I got something like that working?
Edit
According to the above result of my first select query, I should be able to retrieve products whose CatID is 1 and 4 on my second query. That's what I aim.
One solution can be the next one:
SELECT x.*
FROM
(
SELECT ft.*,
cat.CategoryName,
DENSE_RANK() OVER (ORDER BY ft.CatID) AS Rnk
FROM #fooTable ft
INNER JOIN #categories cat ON (cat.CatID = ft.CatID)
) AS x
WHERE x.Rnk BETWEEN 1 AND 2
Results:
ID CatID Name MinAllow Price CategoryName Rnk
-- ----- -------- -------- ------- ------------ ---
1 1 Product1 2 112.20 Cat1 1
3 1 Product3 5 233.32 Cat1 1
6 1 Product6 4 12.43 Cat1 1
7 4 Product7 4 12.43 Cat4 2

Resources