I have the following values and I want to sort this
These are the following values:
12:23PM IN | 12:26PM OUT
2:10PM IN
11:05AM IN
10:58AM IN | 11:00AM OUT
1:02PM IN | 1:05PM OUT
2:12PM IN | 2:25PM OUT
Collection Remarks: counter
11:47AM IN | 11:49AM OUT
12:42PM IN
12:58PM IN
12:55PM IN
12:54PM IN
12:49PM IN | 2:45PM OUT
I need to sort it like this
10:58AM IN | 11:00AM OUT
11:05AM IN
11:47AM IN | 11:49AM OUT
12:23PM IN | 12:26PM OUT
12:42PM IN
12:49PM IN | 2:45PM OUT
12:54PM IN
12:55PM IN
12:58PM IN
1:02PM IN | 1:05PM OUT
2:12PM IN | 2:25PM OUT
Collection Remarks: counter
How can this be achieved?
Parse a portion of your strings to time and order by the result.
;WITH ParsedValues AS
(
SELECT
T.*,
ParsedTime = TRY_PARSE(SUBSTRING(T.YourColumn, 1, 7) AS TIME)
FROM
YourTable AS T
)
SELECT
T.*
FROM
ParsedValues AS T
ORDER BY
CASE WHEN T.ParsedTime IS NOT NULL THEN 1 ELSE 999 END,
T.ParsedTime ASC
Although I strongly suggest you correctly store these values as 2 different columns with time data type.
I used a custom sql split string function to split two times (In and Out) from each other using "|" as seperator
/*
create table TimeEntries (
InOut varchar(100)
)
insert into TimeEntries values
('10:58AM IN | 11:00AM OUT'),
('11:05AM IN'),
('11:47AM IN | 11:49AM OUT'),
('12:23PM IN | 12:26PM OUT'),
('12:42PM IN'),
('12:49PM IN | 2:45PM OUT'),
('12:54PM IN'),
('12:55PM IN'),
('12:58PM IN'),
('1:02PM IN | 1:05PM OUT'),
('2:12PM IN | 2:25PM OUT')
*/
;with cte as (
select
ROW_NUMBER() over (order by InOut) as rn,
InOut
from TimeEntries
)
select
max(InTime) InTime,
max(OutTime) OutTime
from (
select
rn,
case when id = 1 then convert(time, ltrim(rtrim(replace(val, 'IN', ''))) ) end as InTime,
case when id = 2 then convert(time, ltrim(rtrim(replace(val, 'OUT', ''))) ) end as OutTime
from cte
cross apply dbo.split(InOut, '|')
) t
group by rn
order by InTime, OutTime
Related
I have a table that looks like this:
And I want to group the data order by the daydifference=1, the rows where the daydifference is bigger than 1, will be grouped between the days that have difference=1. So this will be like this:
So I tried this way:
SELECT
InicialData
,FinalDate
FROM #ValueAnalysis
WHERE WorkDays=1
GROUP BY InicialData, FinalDate
And now I want to join the orders where the daydifference is bigger than 1.
Can someone help me please?
If you have SQL Server 2017 or newer, you can use STRING_AGG. For example like this:
SELECT va.InitialDate,
va.FinalDate,
STRING_AGG(cc.Order_,',') WITHIN GROUP (ORDER BY cc.Order_ ASC) as Order_1
FROM ValueAnalysis va
OUTER APPLY (
SELECT Order_
FROM ValueAnalysis
WHERE va.InitialDate = InitialDate or va.FinalDate = FinalDate
) as cc
WHERE DayDiff = 1
GROUP BY va.InitialDate, va.FinalDate
Output:
| InitialDate | FinalDate | Order_1 |
|-------------|------------|---------|
| 2020-01-02 | 2020-01-02 | 21,23 |
| 2020-01-03 | 2020-01-03 | 22 |
| 2020-01-04 | 2020-01-04 | 23,24 |
EDIT#1
If you need generate new table and include dates that are not in original dataset, then you can use recursive CTE:
;WITH cte AS (
SELECT MIN(InitialDate) InitialDate,
MAX(FinalDate) FinalDate,
FROM ValueAnalysis
UNION ALL
SELECT DATEADD(day,1,InitialDate)
FinalDate
FROM cte
WHERE InitialDate < FinalDate
)
SELECT c.InitialDate InitialDate,
c.InitialDate FinalDate,
STRING_AGG(va.Order_,',') WITHIN GROUP (ORDER BY va.Order_ ASC) as Order_1
FROM cte c
LEFT JOIN ValueAnalysis va
ON va.InitialDate = c.InitialDate or va.FinalDate = c.InitialDate
GROUP BY c.InitialDate, c.FinalDate
OPTION (MAXRECURSION 100)
I have got a list of staff who are contractors and it includes duplicates as some work on multiple contracts at the same time. I need to find the row with the most hours for that person and secondly with the end date furthest away (if the hours is the same). I guess this is the Current main contract. I also need to make sure the Date From and the Date to is in between the current date - how can this be done?
+------------+----------+------+-------+------------+------------+
| ContractID | PersonID | Name | Hours | Date From | Date To |
+------------+----------+------+-------+------------+------------+
| 8 | 1 | John | 30 | 20/02/2018 | 26/02/2018 |
| 8 | 2 | Paul | 5 | 20/02/2018 | 26/02/2018 |
| 7 | 3 | John | 7 | 20/02/2018 | 26/02/2018 |
+------------+----------+------+-------+------------+------------+
In the above example, I would need to bring back the John – 30hours and the Paul 5 Hours row. PS - The PersonID is different for each row but the "Name" is the same for the person if on multiple contracts.
Thanks
One approach is simply to use exists with appropriate ordering logic:
select c.*
from contracts c
where c.contractid = (select top 1 c2.contractid
from contracts c2
where c2.name = c.cname and
getdate() >= c2.datefrom and
getdate() < c2.dateto
order by c2.hours desc, c2.dateto desc
);
You can put similar logic into a window function:
select c.*
from (select c.*,
row_number() over (partition by c.name order by c.hours desc, c.dateto desc) as seqnum
from contracts c
where getdate() >= c.dateto and getdate() < c.datefrom
) c
where seqnum = 1;
If you need the full row, I'd do somehthing like this:
with
rankedByHours as (
select
ContractID,
PersonID,
Name,
Hours,
[Date From],
[Date To],
row_number() over (partition by PersonID order by Hours desc) as RowID
from
Contracts
)
select
ContractID,
PersonID,
Name,
Hours,
[Date From],
[Date To],
case
when getdate() between [Date From] and [Date To] then 'Current'
when getdate() < [Date From] then 'Not Started'
else 'Expired'
end as ContractStatus
from
RankedByHours
where
RowID = 1;
Use the CTE to inject a row_number() sorting all rows by your sort criteria, then select out the top one in the main body. It can be easily extended to also capture your farthest-out end date.
I have a cost record and I would like to create N records from it.
The children records have some different parameters.
For example:
The parents record:
date | amount | duration
20170201 | 5000 | 5 months
The children records:
date | amount | duration
20170301 | 1000 | 1 months
20170401 | 1000 | 1 months
20170501 | 1000 | 1 months
20170601 | 1000 | 1 months
20170701 | 1000 | 1 months
How can I do this without iteration? Without cursor or while?
Following SQL CTE query could be used based on Abdul's solution
/*
Create Table PARENT (PARENT_DATE DATE, PARENT_AMOUNT DECIMAL(18,2),PARENT_MONTH INT)
INSERT INTO PARENT SELECT '20170201',5000 ,5
INSERT INTO PARENT SELECT '20180601',120 ,3
*/
;WITH CTE_CHILD
AS (
SELECT
Parent_Date,
Parent_Amount,
Parent_Month,
DateAdd(Month, 1, Parent_Date) as Child_Date,
Parent_Amount/Parent_Month AS Child_Amount,
1 AS Child_Duration
FROM Parent
UNION ALL
SELECT
Parent_Date,
Parent_Amount,
Parent_Month,
DateAdd(Month, 1, Child_Date) as Child_Date,
Child_Amount,
Child_Duration
FROM CTE_CHILD
WHERE
DateAdd(Month, 1, Child_Date) <= DateAdd(Month, Parent_Month, Parent_Date)
)
SELECT
Child_Date,
Child_Amount,
Child_Duration
FROM CTE_CHILD
assuming you have a table like below:
create table tblRecords ( date int, amount money, duration int);
insert into tblRecords values
(20170201,5000,5),
(20180101,9000,3);
you can use a query like below:
select
date= date + r*100
,amount= amount/duration
,duration =1
from tblRecords
cross apply
(
select top (select duration)
r= row_number() over(order by (select null))
from
sys.objects s1
cross join
sys.objects s2
) h
see working demo
One method is CTE.
DECLARE #PARENT AS TABLE
(PARENT_DATE DATE, PARENT_AMOUNT DECIMAL(18,2),PARENT_MONTH INT)
INSERT INTO #PARENT
SELECT '20170201',5000 ,5
;WITH CTE_CHILD
AS (
SELECT DATEADD(MONTH,1,PARENT_DATE) AS CHILD_DATE
,PARENT_AMOUNT/PARENT_MONTH AS CHILD_AMOUNT
,1 AS CHILD_DURATION
FROM #PARENT
WHERE DATEADD(MONTH,1,PARENT_DATE) <= DATEADD(MONTH,PARENT_MONTH,PARENT_DATE)
UNION ALL
SELECT DATEADD(MONTH,1,CHILD_DATE)
,PARENT_AMOUNT/PARENT_MONTH
,1
FROM CTE_CHILD
INNER JOIN #PARENT ON DATEADD(MONTH,1,CHILD_DATE) <= DATEADD(MONTH,PARENT_MONTH,PARENT_DATE)
)
SELECT * FROM CTE_CHILD
option (maxrecursion 0)
Output:-
CHILD_DATE CHILD_AMOUNT CHILD_DURATION
2017-03-01 1000.0000000000000 1
2017-04-01 1000.0000000000000 1
2017-05-01 1000.0000000000000 1
2017-06-01 1000.0000000000000 1
2017-07-01 1000.0000000000000 1
My exact requirement is that if the output of the query 'select amount, quantity from temp_table where type = 5;' is:
amount | quantity
10 | 5
20 | 7
12 | 10
Then, the output should be displayed as:
amount1 | amount2 | amount3 | quantity1 | quantity2 | quantity3
10 | 20 | 12 | 5 | 7 | 10
A possible solution might be:
SELECT LISTAGG(amount, '|') WITHIN GROUP (order by amount)
|| LISTAGG(quantity, '|') WITHIN GROUP (order by amount) as result
FROM temp_table where type = 5;
*Have in mind, that the values of the columns amount and quantity are separated by spaces, thus the ' ' in the listagg() expression. You can change it to '|' or anything else if you like.
Cheers
Use a PIVOT:
SELECT "1_AMOUNT" AS Amount1,
"2_AMOUNT" AS Amount2,
"3_AMOUNT" AS Amount3,
"4_AMOUNT" AS Amount4,
"5_AMOUNT" AS Amount5,
"1_QUANTITY" AS Quantity1,
"2_QUANTITY" AS Quantity2,
"3_QUANTITY" AS Quantity3,
"4_QUANTITY" AS Quantity4,
"5_QUANTITY" AS Quantity5
FROM ( SELECT amount, quantity, ROWNUM rn FROM temp_table WHERE type = 5 )
PIVOT ( MAX( amount ) AS amount,
MAX( quantity ) AS quantity
FOR rn IN ( 1, 2, 3, 4, 5 ) );
I want to convert some line of code from Oracle query to MSSQL.
WITH DATE_MONTHS AS
(
SELECT TO_CHAR(ADD_MONTHS(TO_DATE(TRUNC(TO_DATE(P_REQUIRED_DATE),'MON')), - LEVEL
+1),'DD-MON-YYYY') MONTHS FROM DUAL
CONNECT BY LEVEL <= P_MONTH_RANG
)
SELECT * from DATE_MONTHS
Parameters:
P_REQUIRED_DATE i.e sysdate
P_MONTH_RANG i.e 4
Result:
01-05-2017
01-04-2017
01-03-2017
01-02-2017
One simple way is to use tally table and generate like below:
declare #P_Required_Date date = '2015-05-01'
declare #P_Month_Rang int = 4
Select top (#P_Month_Rang) Dts = DateAdd(month, -(Row_Number() over(order by (Select NULL))-1), #P_Required_Date) from
master..spt_values s1, master..spt_values s2
Output as below:
+------------+
| Dts |
+------------+
| 2015-05-01 |
| 2015-04-01 |
| 2015-03-01 |
| 2015-02-01 |
+------------+
Your CTE approach:
declare #P_Required_Date date = '2015-05-01'
declare #P_Month_Rang int = 4
;with Date_Months as
(
Select #P_Required_Date as Dates, 1 as Levl
Union all
Select DateAdd(MONTH,-1, Dates), Levl+1 as Levl from Date_Months
where Levl < #P_Month_Rang
)
Select convert(varchar(10), dates, 103) from Date_Months
For converting to your dd-mm-yyyy format one way is to do convert with option 103 or use Format.
Another option using Stacked Ctes
declare #fromdate date = '20150501';
declare #months int = 4;
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top (#months)
[Date]=convert(date,dateadd(month,-(row_number() over(order by (select 1))-1),#fromdate))
from n as deka cross join n as hecto cross join n as kilo cross join n as tenK
order by [Date] desc
)
select [Date] = convert(char(10),[date],105)
from dates;
rextester demo: http://rextester.com/UUW2271
returns:
+------------+
| Date |
+------------+
| 01-05-2015 |
| 01-04-2015 |
| 01-03-2015 |
| 01-02-2015 |
+------------+
Benchmarks & Performance testing: Generate a set or sequence without loops - 2 - Aaron Bertrand