Related
I have people that do many multi-day assignments (date x to date Y). I would like to find the date that they completed a milestone e.g. 50 days work completed.
Data is stored as a single row per Assignment
AssignmentId
StartDate
EndDate
I can sum up the total days they have completed up to a date, but am struggling to see how I would find out the date that a milestone was hit. e.g. How many people completed 50 days in October 2020 showing the date within the month that this occurred?
Thanks in advance
PS. Our database is SQL Server.
As mentioned by prwvious comments, it would be much easier to help you if you could provide example data and table structure in order help you answer this question.
However, guessing a simple DB structure with a table for your peolple, your tasks and the work each user completed, you can get the required sum of days by use of a date table (or cte) which contains a entry for each day and the window function SUM with UNBOUNDED PRECEDING. Following an example:
DECLARE #people TABLE(
id int
,name nvarchar(50)
)
DECLARE #tasks TABLE(
id int
,name nvarchar(50)
)
DECLARE #work TABLE(
people_id int
,task_id int
,task_StartDate date
,task_EndDate date
)
INSERT INTO #people VALUES (1, 'Peter'), (2, 'Paul'), (3, 'Mary');
INSERT INTO #tasks VALUES (1, 'Devleopment'), (2, 'QA'), (3, 'Sales');
INSERT INTO #work VALUES
(1, 1, '2019-04-05', '2019-04-08')
,(1, 1, '2019-05-05', '2019-06-08')
,(1, 1, '2019-07-05', '2019-09-08')
,(2, 2, '2019-04-08', '2019-06-08')
,(2, 2, '2019-09-08', '2019-10-03')
,(3, 1, '2019-11-01', '2019-12-01')
;WITH cte AS(
SELECT CAST('2019-01-01' AS DATE) AS dateday
UNION ALL
SELECT DATEADD(d, 1, dateday)
FROM cte
WHERE DATEADD(d, 1, dateday) < '2020-01-01'
),
cteWorkDays AS(
SELECT people_id, task_id, dateday, 1 AS cnt
FROM #work w
INNER JOIN cte c ON c.dateday BETWEEN w.task_StartDate AND w.task_EndDate
),
ctePeopleWorkdays AS(
SELECT *, SUM(cnt) OVER (PARTITION BY people_id ORDER BY dateday ROWS UNBOUNDED PRECEDING) dayCnt
FROM cteWorkDays
)
SELECT *
FROM ctePeopleWorkdays
WHERE dayCnt = 50
OPTION (MAXRECURSION 0)
The solution depends on how you store your data. The solution below assumes that each worked day exists as a single row in your data model.
The approach below uses a common table expression (cte) to generate a running total (Total) for each person (PersonId) and then filters on the milestone target (I set it to 5 to reduce the sample data size) and target month.
Sample data
create table WorkedDays
(
PersonId int,
TaskDate date
);
insert into WorkedDays (PersonId, TaskDate) values
(100, '2020-09-01'),
(100, '2020-09-02'),
(100, '2020-09-03'),
(100, '2020-09-04'),
(100, '2020-09-05'), -- person 100 worked 5 days by 2020-09-05 = milestone (in september)
(200, '2020-09-29'),
(200, '2020-09-30'),
(200, '2020-10-01'),
(200, '2020-10-02'),
(200, '2020-10-03'), -- person 200 worked 5 days by 2020-10-03 = milestone (in october)
(200, '2020-10-04'),
(200, '2020-10-05'),
(200, '2020-10-06'),
(300, '2020-10-10'),
(300, '2020-10-11'),
(300, '2020-10-12'),
(300, '2020-10-13'),
(300, '2020-10-14'), -- person 300 worked 5 days by 2020-10-14 = milestone (in october)
(300, '2020-10-15'),
(400, '2020-10-20'),
(400, '2020-10-21'); -- person 400 did not reach the milestone yet
Solution
with cte as
(
select wd.PersonId,
wd.TaskDate,
count(1) over(partition by wd.PersonId
order by wd.TaskDate
rows between unbounded preceding and current row) as Total
from WorkedDays wd
)
select cte.PersonId,
cte.TaskDate as MileStoneDate
from cte
where cte.Total = 5 -- milestone reached
and year(cte.TaskDate) = 2020
and month(cte.TaskDate) = 10; -- in october
Result
PersonId MilestoneDate
-------- -------------
200 2020-10-03
300 2020-10-14
Fiddle (also shows the common table expression output).
I have the following table:
respid, uploadtime
I need a query that will show all the records that respid is duplicate and show them except the latest (by upload time)
exmple:
4 2014-01-01
4 2014-06-01
4 2015-01-01
4 2015-06-01
4 2016-01-01
In this case the query should return four records (the latest is : 4 2016-01-01 )
Thank you very much.
Use ROW_NUMBER:
WITH cte AS (
SELECT respid, uploadtime,
ROW_NUMBER() OVER (PARTITION BY respid ORDER BY uploadtime DESC) rn
FROM yourTable
)
SELECT respid, uploadtime
FROM cte
WHERE rn > 1
ORDER BY respid, uploadtime;
The logic here is to show all records except those having the first row number value, which would be the latest records for each respid group.
If I interpreted your question correctly, then you want to see all records where respid occurs multiple times, but exclude the last duplicate.
Translating this to SQL could sound like "show all records that have a later record for the same respid". That is exactly what the solution below does. It says that for every row in the result a later record with the same respid must exists.
Sample data
declare #MyTable table
(
respid int,
uploadtime date
);
insert into #MyTable (respid, uploadtime) values
(4, '2014-01-01'),
(4, '2014-06-01'),
(4, '2015-01-01'),
(4, '2015-06-01'),
(4, '2016-01-01'), --> last duplicate of respid=4, not part of result
(5, '2020-01-01'); --> has no duplicate, not part of result
Solution
select mt.respid, mt.uploadtime
from #MyTable mt
where exists ( select top 1 'x'
from #MyTable mt2
where mt2.respid = mt.respid
and mt2.uploadtime > mt.uploadtime );
Result
respid uploadtime
----------- ----------
4 2014-01-01
4 2014-06-01
4 2015-01-01
4 2015-06-01
We have to compare the sales on given based on the parameter from and to date at last three months.
Create table Net_sales
(terminalid varchar(14),
Region varchar(20),
City varchar(50),
ICC_TRNS_COUNT int,
ICC_AMount money,
Trns_Date datetime)
--SELECT * FROM Net_sales
insert into Net_sales values ('INMAA031000000','SOUTH','CHENNAI',1,5000,'08/01/2019')
insert into Net_sales values ('INMAA031000000','SOUTH','CHENNAI',1,4000,'08/02/2019')
insert into Net_sales values ('INMAA031000000','SOUTH','CHENNAI',1,200,'08/04/2019')
insert into Net_sales values ('INAMD03900030G','WEST','Gujarat',1,52000,'08/01/2019')
insert into Net_sales values ('INAMD03900030G','WEST','Gujarat',1,40700,'08/02/2019')
insert into Net_sales values ('INAMD03900030G','WEST','Gujarat',1,2200,'08/04/2019')
insert into Net_sales values ('INAMD03900030G','WEST','Gujarat',1,52000,'09/01/2019')
insert into Net_sales values ('INAMD03900030G','WEST','Gujarat',1,47000,'09/02/2019')
insert into Net_sales values ('INAMD03900030G','WEST','Gujarat',1,2200,'09/10/2019')
insert into Net_sales values ('INAMD03900030G','WEST','Gujarat',1,52000,'10/01/2019')
insert into Net_sales values ('INAMD03900030G','WEST','Gujarat',1,70000,'10/02/2019')
insert into Net_sales values ('INAMD03900030G','WEST','Gujarat',1,3200,'10/10/2019')
-------------------
DECLARE #FROMDATE DATETIME
DECLARE #TODAY DATETIME
SET #FROMDATE='08/01/2019'
SET #TODAY='10/10/2019'
SELECT
TERMINALID ,
CITY,
REGION,
SUM(ICC_AMOUNT) ICASHDMRAMOUNT
FROM NET_SALES WHERE CONVERT(VARCHAR,TRNS_DATE,101) BETWEEN CONVERT(VARCHAR,#FROMDATE,101) AND CONVERT(VARCHAR,#TODAY,101)
GROUP BY TERMINALID ,
CITY,
REGION
ORDER BY TERMINALID
we need to expected and result
Expected result
TERMINALID CITY REGION ICASHDMRAMOUNT_AUG ICASHDMRAMOUNT_SEP ICASHDMRAMOUNT_OCT
INAMD030001024 Gujarat WEST 94200 94200 94200
INAMD03900030G Gujarat WEST 94900 101200 125200
INMAA031000000 CHENNAI SOUTH 5000 0 0
A few things.
TerminalID INAMD030001024 doesn't exist in your sample data, so it's not in the sample result set.
Your dates are stored as dates, so leave them that way. When you convert them to strings, date functions no longer work properly on them. Also, it's best to use ANSI/ISO standard date format, which is 'YYYY-MM-DD'. Using 'MM/DD/YYYY' could be misinterpreted as 'DD/MM/YYY' depending on the regional settings on your servers. The standard form will always be interpreted correctly by the server.
With that said, your query just requires some conditional aggregation:
SELECT
TERMINALID,
CITY,
REGION,
SUM(CASE WHEN MONTH(TRNS_DATE) = 8 THEN ICC_AMOUNT ELSE 0 END) AS ICASHDMRAMOUNT_AUG,
SUM(CASE WHEN MONTH(TRNS_DATE) = 9 THEN ICC_AMOUNT ELSE 0 END) AS ICASHDMRAMOUNT_SEP,
SUM(CASE WHEN MONTH(TRNS_DATE) = 10 THEN ICC_AMOUNT ELSE 0 END) AS ICASHDMRAMOUNT_OCT
FROM
NET_SALES WHERE TRNS_DATE BETWEEN #FROMDATE AND #TODAY
GROUP BY
TERMINALID ,
CITY,
REGION
ORDER BY
TERMINALID;
Results:
TERMINALID CITY REGION ICASHDMRAMOUNT_AUG ICASHDMRAMOUNT_SEP ICASHDMRAMOUNT_OCT
INAMD03900030G Gujarat WEST 94900 101200 125200
INMAA031000000 CHENNAI SOUTH 9200 0 0
SQL Fiddle: http://sqlfiddle.com/#!18/42bfbc/3/0
If you edit the Fiddle data to include the missing TerminalID, you should get to your precise desired results.
EDIT: If you need this to be a rolling three months, create a variable, maybe #FinalMonth int, set that to the value of MONTH(#TODAY), then in the CASE expressions, use #FinalMonth-2, #FinalMonth-1, #FinalMonth rather than the hard coded values above. Your column names won't have month names in them without doing some dynamic SQL, but _CurrMnth, _PrevMnth, _2ndPrevMnth or something like that would get your point across.
This is not a homework question.
I'm trying to take the count of t-shirts in an order and see which price range the shirts fall into, depending on how many have been ordered.
My initial thought (I am brand new at this) was to ask another table if count > 1st price range's maximum, and if so, keep looking until it's not.
printing_range_max printing_price_by_range
15 4
24 3
33 2
So for example here, if the order count is 30 shirts they would be $2 each.
When I'm looking into how to do that, it looks like most people are using BETWEEN or IF and hard-coding the ranges instead of looking in another table. I imagine in a business setting it's best to be able to leave the range in its own table so it can be changed more easily. Is there a good/built-in way to do this or should I just write it in with a BETWEEN command or IF statements?
EDIT:
SQL Server 2014
Let's say we have this table:
DECLARE #priceRanges TABLE(printing_range_max tinyint, printing_price_by_range tinyint);
INSERT #priceRanges VALUES (15, 4), (24, 3), (33, 2);
You can create a table with ranges that represent the correct price. Below is how you would do this in pre-2012 and post-2012 systems:
DECLARE #priceRanges TABLE(printing_range_max tinyint, printing_price_by_range tinyint);
INSERT #priceRanges VALUES (15, 4), (24, 3), (33, 2);
-- post-2012 using LAG
WITH pricerange AS
(
SELECT
printing_range_min = LAG(printing_range_max, 1, 0) OVER (ORDER BY printing_range_max),
printing_range_max,
printing_price_by_range
FROM #priceRanges
)
SELECT * FROM pricerange;
-- pre-2012 using ROW_NUMBER and a self-join
WITH prices AS
(
SELECT
rn = ROW_NUMBER() OVER (ORDER BY printing_range_max),
printing_range_max,
printing_price_by_range
FROM #priceRanges
),
pricerange As
(
SELECT
printing_range_min = ISNULL(p2.printing_range_max, 0),
printing_range_max = p1.printing_range_max,
p1.printing_price_by_range
FROM prices p1
LEFT JOIN prices p2 ON p1.rn = p2.rn+1
)
SELECT * FROM pricerange;
Both queries return:
printing_range_min printing_range_max printing_price_by_range
------------------ ------------------ -----------------------
0 15 4
15 24 3
24 33 2
Now that you have that you can use BETWEEN for your join. Here's the full solution:
-- Sample data
DECLARE #priceRanges TABLE
(
printing_range_max tinyint,
printing_price_by_range tinyint
-- if you're on 2014+
,INDEX ix_xxx NONCLUSTERED(printing_range_max, printing_price_by_range)
-- note: second column should be an INCLUDE but not supported in table variables
);
DECLARE #orders TABLE
(
orderid int identity,
ordercount int
-- if you're on 2014+
,INDEX ix_xxy NONCLUSTERED(orderid, ordercount)
-- note: second column should be an INCLUDE but not supported in table variables
);
INSERT #priceRanges VALUES (15, 4), (24, 3), (33, 2);
INSERT #orders(ordercount) VALUES (10), (20), (25), (30);
-- Solution:
WITH pricerange AS
(
SELECT
printing_range_min = LAG(printing_range_max, 1, 0) OVER (ORDER BY printing_range_max),
printing_range_max,
printing_price_by_range
FROM #priceRanges
)
SELECT
o.orderid,
o.ordercount,
--p.printing_range_min,
--p.printing_range_max
p.printing_price_by_range
FROM pricerange p
JOIN #orders o ON o.ordercount BETWEEN printing_range_min AND printing_range_max
Results:
orderid ordercount printing_price_by_range
----------- ----------- -----------------------
1 10 4
2 20 3
3 25 2
4 30 2
Now that we have that we can
I have the rows below, and i want to access prior row and divide its value by current row. For every row, i need to calculate the Vi value, this Vi value is equal to Vi-1/Vi which means that:
Given the table
Table T
id value out
1 100
2 200
3 10
4 50
I want to generate these values
V1 = 100
V2= 100/200 = 0.5
V3 = 0.5/10 = 0.05
V4 = 0.05/50 = 0.001
So at the end i want the following output:
id value out
1 100 100
2 200 0.5
3 10 0.05
4 50 0.001
I tried using the aggregate function SUM with OVER(), but i do not know how to solve this problem as i need to divide and not sum the value
SELECT id, value, SUM(value) OVER(ORDER BY id ROWS BETWEEN
1 PRECEDING AND 1 PRECEDING ) / value as out
FROM T
Sample data:
CREATE TABLE t(
id INT,
value INT
);
INSERT INTO t VALUES
(1, 100), (2, 200), (3, 10), (4, 50);
Unfortunately, SQL do not have Product, but it should be simple to use cte. The performance should be not bad if id was indexed
DECLARE #T table (id int identity(1,1) primary key, value int)
INSERT #T VALUES (100), (200), (10), (50)
;WITH cte AS
(
SELECT id, value, CAST(value AS decimal(20,4)) AS out FROM #T WHERE id = 1
UNION ALL SELECT T.id, T.value, CAST(cte.out / T.value AS decimal(20,4)) FROM cte INNER JOIN #T T ON cte.id = T.id - 1
)
SELECT * FROM cte