Is it possible to write single query for following scenario? - sql-server

Is it possible to write single query for following scenario?
Scenario -
Table -
column name - id date isPaid
values - 1 1/1/2011 1
2 1/2/2011 1
3 1/3/2011 0
4 1/4/2011 0
5 1/5/2011 0
I want a result set which contains (all ispaid = 1) and (only 1 row of ispaid = 0 whose date is smaller).
Result set:
column name - id date isPaid
values - 1 1/1/2011 1
2 1/2/2011 1
3 1/3/2011 0
Thanks

You can use UNIONdocs
SELECT
[id],
[date],
[isPaid]
FROM
[tablename]
WHERE
[ispaid] = 1
UNION ALL
SELECT TOP 1
[id],
[date],
[isPaid]
FROM
[tablename]
WHERE
[ispaid] = 0
ORDER BY
[date] ASC

This should do what you need in SQL Server 2005 and higher.
select
[id],
[date],
isPaid
from (
select
[id],
[date],
isPaid,
ROW_NUMBER() over (partition by ispaid order by date) as row
from table_name t ) a
where ispaid = 1
or row = 1
order by [date]

Assuming date will be provided to the query by user, for which data is to be retrieved
select t.*
from table t,
(select Top 1 id, date, ispaid
from table
where ispaid = 0 and date<?) np
where (t.ispaid=1 and t.date = ? ) OR (t.id = np.id)

Related

SQL query to get start and end date from a result set

I am working on one of requirement the raw data is in following format
Requirement - Startdate should be the date when status changed to 1 and enddate should be the 1st date after the record status changed from 1 to any other number.
Customer
Status
Date
A123
0
7/2/2021
A123
0
7/15/2021
A123
0
7/22/2021
A123
1
8/18/2021
A123
1
9/8/2021
A123
0
12/1/2021
A123
0
1/21/2022
A123
1
3/6/2022
A123
1
3/7/2022
A123
0
3/15/2022
B123
1
1/1/2022
B123
0
1/6/2022
C123
1
1/2/2022
C123
2
1/8/2022
C123
0
1/9/2022
expected output
Customer
StartDate
EndDate
A123
8/18/2021
12/1/2021
A123
9/8/2021
12/1/2021
A123
3/6/2022
3/15/2022
A123
3/7/2022
3/15/2022
B123
1/1/2022
1/6/2022
C123
1/2/2022
1/8/2022
Query I tried to get the output is below, I am getting the output for Customer B123 and C123, but not for A123 as expected.
Query Explanation - In 1st part of query I am taking all the records with status = 1 and in next part taking only those records where status is not equal to 1, and joining these 2 datasets based on Customer and row number generated.
SELECT A.[Customer],A.StartDate,B.EndDate
from
(
SELECT [Customer],MIN(Date) AS STARTDATE,[Status],RANK() OVER (PARTITION BY [STATUS] ORDER BY Date ASC) AS ROWNUM
FROM table1
WHERE [STATUS] = 1
GROUP BY Customer,Date,[Status]
) A
LEFT JOIN
(
SELECT [Customer],MIN(Date) AS ENDDATE,[Status],RANK() OVER (PARTITION BY [STATUS] ORDER BY Date ASC) AS ROWNUM
FROM table1
WHERE [STATUS] != 1
AND Date>(
SELECT MIN(Date) AS STARTDATE
FROM table1
WHERE [STATUS] = 1
)
GROUP BY Customer,Date,[Status]
) B
ON
(
A.[Customer] = B.[Customer]
AND A.RowNum = B.RowNum
)
ORDER BY A.Startdate
First you list the rows where Status = 1 and then use CROSS APPLY to get the corresponding minimum Date where the Status is not equal to 1
select s.[Customer],
StartDate = s.[Date],
EndDate = e.[Date]
from Table1 s
cross apply
(
select [Date] = min(e.[Date])
from Table1 e
where e.[Customer] = s.[Customer]
and e.[Date] > s.[Date]
and e.[Status] <> 1
) e
where s.[Status] = 1
order by s.[Customer], s.[Date]
Here is a more efficient way to do this without a self-join.
WITH cte01only AS
( SELECT *, CASE Status WHEN 1 THEN 1 ELSE 0 END AS Status1 FROM table1 ),
cteDifference AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY Date, Status1)
- ROW_NUMBER() OVER (PARTITION BY Customer, Status1 ORDER BY Date) AS StatusGroup
FROM cte01only
),
cteGroup AS
(
SELECT Customer, StatusGroup, Status1, MIN(Date) As StartDate
FROM cteDifference
GROUP BY Customer, StatusGroup, Status1
),
cteNextDate AS
(
SELECT Customer, StatusGroup, Status1, StartDate,
LEAD(StartDate, 1, NULL) OVER (PARTITION BY Customer ORDER BY StatusGroup) AS EndDate
FROM cteGroup
)
SELECT Customer, StartDate, EndDate
FROM cteNextDate
WHERE Status1 = 1
ORDER BY Customer, StateDate
The key trick here is the second CTE which uses the difference of two ROW_NUMBER() functions to tag the customer records (with the StatusGroup column) into separate partitions by contiguous runs of records whose status is 1 or not 1. After that they can be grouped according to that tag to get the start dates, and then use the LEAD() function to get the following group's StartDate as the current groupings EndDate.
(There may be a more compact way to express this, but I like to layout each stage as a separate CTE.)

In T-SQL, how can I create a new partition every time I hit a certain criteria, and keep that partition until the next time I see that criteria?

Here's the output I'm hoping to achieve:
I'd like to start with the most recent ReportMonth working backwards and start a new partition every time there's a MonthsBetween > 2. I'd like to avoid loops if possible.
EDIT: Here's the create statement for the table
CREATE TABLE #temp (
MonthsBetween int,
ReportMonth date)
INSERT INTO #temp VALUES(0, '2019-12-01')
INSERT INTO #temp VALUES(1, '2019-11-01')
INSERT INTO #temp VALUES(1, '2019-10-01')
INSERT INTO #temp VALUES(3, '2019-07-01')
INSERT INTO #temp VALUES(1, '2019-06-01')
INSERT INTO #temp VALUES(3, '2019-03-01')
You may try with the following approach using LAG() and windowed SUM():
Statement (for groups based on difference between each two values for MonthsBetween > 2):
SELECT
MonthsBetween,
ReportMonth,
SUM(GroupID) OVER (ORDER BY ReportMonth DESC) AS PartitionID
FROM (
SELECT
*,
CASE
WHEN LAG(MonthsBetween) OVER (ORDER BY ReportMonth DESC) IS NULL THEN 1
WHEN MonthsBetween - LAG(MonthsBetween) OVER (ORDER BY ReportMonth DESC) >= 2 THEN 1
ELSE 0
END AS GroupId
FROM #temp
) cte
Statement (for groups based on value for MonthsBetween > 2):
SELECT
MonthsBetween,
ReportMonth,
SUM(CASE WHEN MonthsBetween > 2 THEN 1 ELSE 0 END) OVER (ORDER BY ReportMonth DESC) + 1 AS PartitionID
FROM #temp
Result:
MonthsBetween ReportMonth PartitionID
0 2019-12-01 1
1 2019-11-01 1
1 2019-10-01 1
3 2019-07-01 2
1 2019-06-01 2
3 2019-03-01 3
Looks like your MonthsBetween uses a Lag or Lead function already(or precalculated)
create table #temp (
MonthsBetween int null,
ReportMonth date null
)
insert into #temp (MonthsBetween,ReportMonth)
Values ('0','2019-12-01'),
('1','2019-11-01'),
('1','2019-10-01'),
('3','2019-07-01'),
('1','2019-06-01'),
('3','2019-03-01')
select t.*,
sum(case when MonthsBetween <= 2 then 0 else 1 end) over (order by t.[ReportMonth] DESC) +1 as [PartitionID]
from #temp t

MSSQL: calculate time be many starts and stops

Is it possible to make this in sql:
Have table with records:
ID LaborID OrderNr OrderStatusID OrderStatusDate
12990 3731573 OPT1814378 2 2018-05-28 09:35:30.123
13105 230687389 OPT1814378 1 2018-05-29 10:32:14.850
13106 230687389 OPT1814378 2 2018-05-29 10:52:14.403
13123 230480202 OPT1814378 1 2018-05-29 13:18:05.233
13130 230480202 OPT1814378 0 2018-05-29 13:29:17.360
12837 3731573 OPT1814089 2 2018-05-25 20:28:24.817
12906 10138504 OPT1814089 1 2018-05-26 10:41:18.680
12909 10138504 OPT1814089 2 2018-05-26 10:57:40.733
12913 10138504 OPT1814089 1 2018-05-26 11:41:48.387
12920 10138504 OPT1814089 0 2018-05-26 12:15:48.590
where
OrderStatusID
0 - End
1 - Begin
2 - pause
Need calculate working time from begin to pause (1->2) or from begin to end (1->0).
My problem is that there are some conditions that I have to adhere to:
If first record is 2 then ignore
Work begin always with 1
But can have more pause (1->2)
The last work end record everytime with 0
The result in this case will be:
OPT1814378 230687389 00:20:00
OPT1814378 230480202 00:11:12
OPT1814089 10138504 00:16:12
OPT1814089 10138504 00:34:00
Hopefully this is not that ugly.
; with
cte as
(
-- CTE for generating a sequence no
select *, rn = row_number() over (partition by OrderNr
order by OrderStatusDate)
from #table
),
cte2 as
(
-- Clean up invalid any rows and regenerate new sequence no
select ID, LaborID, OrderNr, OrderStatusID, OrderStatusDate,
rn = row_number() over (partition by OrderNr
order by OrderStatusDate)
from cte
where (rn = 1 and OrderStatusID = 1)
or rn >= 2
)
select OrderNr, LaborID,
convert(varchar(10),
dateadd(second,
datediff(second,
min(OrderStatusDate),
max(OrderStatusDate)),
0),
108)
from cte2
group by OrderNr,
LaborID,
(rn - 1) / 2
(rn - 1) / 2 will gives the value 0, 0, 1, 1, 2, 2 etc for grouping the rows two by two.
This one also works
;WITH CTE
AS (
SELECT row_number() OVER ( PARTITION BY ordernr ORDER BY id ) RN ,* FROM test_ti
)
,cte2
AS (
SELECT *
FROM cte c1
WHERE NOT EXISTS (
SELECT *
FROM cte c2
WHERE c1.id = c2.id
AND c2.rn = 1
AND c2.orderstatusid = 2
)
)
SELECT OrderNr
,LaborId
,TimeInterval
FROM ( SELECT DateDiff(MI, TIME, NxtTm) TimeInterval ,*
FROM (
SELECT * ,lead(TIME) OVER ( ORDER BY id ) NxtTm
FROM cte2
) x
WHERE orderstatusid <> 0
) y
WHERE orderstatusid = 1

Update null values by value in same column

I have a table in MS SQL Server, where are some null values in column "value"
Group ID Value
A 1 10
A 2
A 3
A 4 40
B 1
B 2 20
B 3 30
B 4
I want to update null values by not null in the same group with with the first higher ID, or if there is not any higher in same group, first lower. So the result should look like this.
Group ID Value
A 1 10
A 2 40
A 3 40
A 4 40
B 1 20
B 2 20
B 3 30
B 4 30
Thanks!
You can use windowed version of SUM function in order to determine islands of NULL valued records along with the record having the higher ID in the same group:
SELECT [Group], ID, Value,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID DESC) AS grp
FROM mytable
Output:
Group ID Value grp
-----------------------
A 4 40 1
A 3 30 2
A 2 NULL 2
A 1 NULL 2
B 4 40 1
B 3 NULL 1
B 2 20 2
B 1 10 3
You can now wrap the above query in a CTE and use another CTE to do the update:
;WITH CTE AS (
SELECT [Group], ID, Value,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID DESC) AS grp
FROM mytable
), ToUpdate AS (
SELECT [Group], ID, Value,
MAX(Value) OVER (PARTITION BY [Group], grp) AS group_value
FROM CTE
)
UPDATE ToUpdate
SET Value = group_value
WHERE Value IS NULL
Demo here
Edit:
The above query doesn't handle the edge case where the very last record within a Group slice is NULL. To handle this case as well you can use the following query:
;WITH CTE AS (
SELECT [Group], ID, Value,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID DESC) AS grp,
SUM(CASE WHEN Value IS NULL THEN 0 ELSE 1 END) OVER
(PARTITION BY [Group] ORDER BY ID) AS grp2
FROM mytable
), ToUpdate AS (
SELECT [Group], ID, Value,
MAX(Value) OVER (PARTITION BY [Group], grp) AS group_value,
MAX(Value) OVER (PARTITION BY [Group], grp2) AS group_value2
FROM CTE
)
UPDATE ToUpdate
SET Value = COALESCE(group_value, group_value2)
WHERE Value IS NULL
Demo here
Please try this-
DATA GENERATION
DECLARE #T TABLE
(
GroupCd CHAR(1),
Id INT,
Value INT
)
INSERT INTO #T
VALUES('A',1,10),
('A',2,NULL),
('A',3,NULL),
('A',4,40),
('B',1,NULL),
('B',2,20),
('B',3,30),
('B',4,NULL)
SOLUTION
UPDATE a
SET a.Value = b.Value
FROM #T a
INNER JOIN
(
SELECT a.GroupCd,a.Id,Coalesce(a.Value,z.Value,z1.Value) Value
FROM #T a
OUTER APPLY
(
SELECT TOP 1 Value
FROM #T b
WHERE a.GroupCd = b.GroupCd
AND b.Value IS NOT NULL AND a.Id < b.Id
ORDER BY Id
)z
OUTER APPLY
(
SELECT TOP 1 Value
FROM #T b
WHERE a.GroupCd = b.GroupCd
AND b.Value IS NOT NULL AND a.Id > b.Id
ORDER BY Id DESC
)z1
)b ON a.GroupCd = b.GroupCd AND a.Id = b.Id
SELECT * FROM #T
OUTPUT
GroupCd Id Value
------- ----------- -----------
A 1 10
A 2 40
A 3 40
A 4 40
B 1 20
B 2 20
B 3 30
B 4 30
(8 rows affected)
You Can try This simple Method
DECLARE #T TABLE
(
GroupCd CHAR(1),
Id INT,
Value INT
)
INSERT INTO #T
VALUES('A',1,NULL),
('A',2,NULL),
('A',3,30),
('A',4,40),
('B',1,10),
('B',2,20),
('B',3,NULL),
('B',4,40)
SELECT
*,
NewVal = COALESCE(Value,(SELECT TOP 1 Value FROM #T WHERE GroupCd = T.GroupCd AND Id > T.Id AND Value IS NOT NULL ORDER BY Id ASC))
FROM #T T
My Result
update MY_TABLE set [value] = [newValue] from (
select [Group] [newGroup],
[Value] [newValue]
from (
select [Group], [Value],
row_number() over (partition by [group] order by [Id] desc) [rn]
from MY_TABLE
where [Value] is not null
) [a] where [rn] = 1
) where [Group] = [newGroup] and [Value] is null

retrieve quantity data from two tables by id and date range

I have two table like
ItemTable_One
id itemID Date qty
===================================================
1 1 2015-07-1 10
2 1 2015-07-3 20
3 2 2015-07-5 30
4 2 2015-07-7 40
ItemTable_Two
id itemID Date qty
===================================================
1 1 2015-07-2 50
2 1 2015-07-4 60
3 3 2015-07-6 70
4 3 2015-07-8 80
I want to retrieve data where itemID is equal to 1 in a date range.
For example ( date range between 2015-07-1 and 2015-07-30 )
itemID Date ItemTableOne_qty ItemTableTwo_qty
============================================================================
1 2015-07-1 10 0
1 2015-07-3 20 0
1 2015-07-2 0 50
1 2015-07-4 0 60
I've tried union join and subquery to do it, but I'm very weak in sql query.
You can use UNION ALL to get all the data you need:
SELECT ItemTable_One.itemID, ItemTable_One.Date, ItemTable_One.qty as ItemTableOne_qty, 0 as ItemTableTwo_qty
FROM ItemTable_One
WHERE ItemTable_One.itemID = 1 AND
ItemTable_One.Date BETWEEN '2015-07-01' AND '2015-08-01'
UNION ALL
SELECT ItemTable_Two.itemID, ItemTable_Two.Date, 0 as ItemTableOne_qty, ItemTable_Two.qty as ItemTableTwo_qty
FROM ItemTable_One
WHERE ItemTable_Two.itemID = 1 AND
ItemTable_Two.Date BETWEEN '2015-07-01' AND '2015-08-01'
You can use a FULL OUTER JOIN:
SELECT COALESCE(t1.itemID, t2.itemID) AS itemID,
COALESCE(t1.[Date], t2.[Date]) AS [Date],
COALESCE(t1.qty, 0) AS ItemTableOne_qty,
COALESCE(t2.qty, 0) AS ItemTableTwo_qty
FROM ItemTable_One AS t1
FULL OUTER JOIN ItemTable_Two AS t2 ON t1.itemID = t2.itemID AND t1.[Date] = t2.[Date]
WHERE COALESCE(t1.itemID, t2.itemID) = 1 AND
COALESCE(t1.[Date], t2.[Date]) BETWEEN '2015-07-01' AND '2015-07-31'
ORDER BY COALESCE(t1.[Date], t2.[Date])
This will put records having the same [Date] value in the source tables, into the same row of the output table.
If records of ItemTable_One always have separate [Date] values from records of ItemTable_Two, then the UNION solution proposed in other answers is preferable.
Demo here
Try this:
select itemID, Date, qty as ItemTableOne_qty, 0 as ItemTableTwo_qty
from ItemTable_One
where ItemID = 1
and date >= '20150701'
and date < '20150731'
union all
select itemID, Date, 0 as ItemTableOne_qty, qty as ItemTableTwo_qty
from ItemTable_Two
where ItemID = 1
and date >= '20150701'
and date < '20150731'
The upper limit for date is intentionally < than the wanted date +1 so that in case it's a datetime that has the time in it, the last day will be included too.
You do it with a FULL JOIN or a UNION ALL (depending on exact required output)
FULL JOIN
A FULL JOIN will allow you to get results like you show as long as there are no identical dates between the 2 tables. If such dates are present in both tables, you'd get a single row per date with both values filled.
The query to use is:
SELECT COALESCE(t1.itemID, t2.itemID) itemID, COALESCE(t1.Date, t2.Date) Date,
ISNULL(t1.qty, 0) ItemTableOne_qty, ISNULL(t2.qty, 0) ItemTableTwo_qty
FROM ItemTable_One t1 FULL JOIN ItemTable_Two t2
ON t1.itemID = t2.itemID AND t1.Date = t2.Date
WHERE COALESCE(t1.itemID, t2.itemID) = 1 AND
COALESCE(t1.Date, t2.Date) BETWEEN '2015-07-01' AND '2015-08-01'
UNION ALL
A UNION ALL will allow you to get resutls like you show and will create duplicate rows where the same date exists in both tables. There will always be at least 1 '0' value in any row.
The query to use is:
SELECT itemID, Date, qty ItemTableOne_qty, 0 ItemTableTwo_qty
FROM ItemTable_One
WHERE itemID = 1 AND Date BETWEEN '2015-07-01' AND '2015-08-01'
UNION ALL
SELECT itemID, Date, 0 ItemTableOne_qty, qty ItemTableTwo_qty
FROM ItemTable_Two
WHERE itemID = 1 AND Date BETWEEN '2015-07-01' AND '2015-08-01'
this will be the other way we can achieve the same result basing on your sample data
declare #ItemTable_One table (id int, itemID int, Date date, qty int)
insert into #ItemTable_One values
(1, 1, '2015-07-1', 10),
(2, 1, '2015-07-3', 20),
(3, 2, '2015-07-5', 30),
(4, 2, '2015-07-7', 40)
declare #ItemTable_Two table (id int, itemID int, Date date, qty int)
insert into #ItemTable_Two values
(1, 1, '2015-07-2', 50),
(2, 1, '2015-07-4', 60) ,
(3, 3, '2015-07-6', 70) ,
(4, 3, '2015-07-8', 80)
;with CTE AS (
select i.itemID As ItemID1,ii.itemID As ItemID2,i.Date As Dated1,ii.Date As Dated2,i.qty as qty,ii.qty As qty1 from #ItemTable_One i
CROSS APPLY (select * from #ItemTable_Two )ii
where i.id = ii.id AND i.itemID = ii.itemID
)
select * from (
Select ItemID1 As item,Dated1 AS Date, qty,'' as qty1 from CTE
UNION
Select ItemID2 As item,Dated2 AS Date,'' as qty,qty1 from CTE)T
--ORDER BY t.qty desc ,t.qty1
Use inline view to simplify horrid sql used in prior answers:
SELECT *
FROM (
SELECT COALESCE(t1.itemID, t2.itemID) AS itemID,
COALESCE(t1.[Date], t2.[Date]) AS [Date],
COALESCE(t1.qty, 0) AS ItemTableOne_qty,
COALESCE(t2.qty, 0) AS ItemTableTwo_qty
FROM ItemTable_One AS t1 FULL OUTER JOIN ItemTable_Two AS t2
ON t1.itemID = t2.itemID AND t1.[Date] = t2.[Date]
) AS v
WHERE v.itemID = 1 AND
v.[Date] BETWEEN '2015-07-01' AND '2015-07-31'
ORDER BY v.[Date]

Resources