On this SQL Server 2008 database I have a table of attendance, students come into school every day and check in, the table looks something like this:
SchoolID | StudentID | Date
There will be a record for every day for every student on this table. What I want to find out is, given a start date, an end date, and a number of days (gap), find any student that has not checked into school for that number of days. So for example, I need to know which students missed 3 days straight during the month of December, and spit out the list of StudentIDs.
How can I accomplish something like that?
You can produce date ranges from startdate to enddate
then outer join this data with your table, if the student wouldn't have come consider it as 1 then summarize this data.
for generating date range you can use this function as below
CREATE FUNCTION [dbo].[DateRange]
(
#Increment CHAR(1),
#StartDate DATETIME,
#EndDate DATETIME
)
RETURNS
#SelectedRange TABLE
(IndividualDate DATETIME)
AS
BEGIN
;WITH cteRange (DateRange) AS (
SELECT #StartDate
UNION ALL
SELECT
CASE
WHEN #Increment = 'd' THEN DATEADD(dd, 1, DateRange)
WHEN #Increment = 'w' THEN DATEADD(ww, 1, DateRange)
WHEN #Increment = 'm' THEN DATEADD(mm, 1, DateRange)
END
FROM cteRange
WHERE DateRange <=
CASE
WHEN #Increment = 'd' THEN DATEADD(dd, -1, #EndDate)
WHEN #Increment = 'w' THEN DATEADD(ww, -1, #EndDate)
WHEN #Increment = 'm' THEN DATEADD(mm, -1, #EndDate)
END)
INSERT INTO #SelectedRange (IndividualDate)
SELECT DateRange
FROM cteRange
OPTION (MAXRECURSION 3660);
RETURN
END
GO
then
select sum(isAbsent) absentDays, s.studentid from
(
select case when studentid is null then 1 else 0 end isAbsent,individualDate,s.studentid from DateRange('d', '01/11/2014', '30/11/2014') d
cross join tblstudent s
left outer join yourtable on yourtable.Date = d.IndividualDate and yourtable.studentid = s.studentid
) x
group by s.studentid
having sum(isAbsent) > 3
Just look at this. I think you will be able to figure out you own answer from that.This solution take care of the weekend days and holidays :
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE attendance
([SchoolID] int, [StudentID] int, [Date] datetime)
;
INSERT INTO attendance
([SchoolID], [StudentID], [Date])
VALUES
(1, 1, '2014-12-01 00:00:00'),
(1, 1, '2014-12-02 00:00:00'),
(1, 1, '2014-12-03 00:00:00'),
(1, 1, '2014-12-04 00:00:00'),
(1, 1, '2014-12-05 00:00:00'),
(1, 1, '2014-12-08 00:00:00'),
(1, 1, '2014-12-09 00:00:00'),
(1, 1, '2014-12-10 00:00:00'),
(1, 1, '2014-12-11 00:00:00'),
(1, 1, '2014-12-12 00:00:00'),
(1, 1, '2014-12-15 00:00:00'),
(1, 1, '2014-12-16 00:00:00'),
(1, 1, '2014-12-17 00:00:00'),
(1, 1, '2014-12-18 00:00:00'),
(1, 1, '2014-12-19 00:00:00'),
(1, 2, '2014-12-01 00:00:00'),
(1, 2, '2014-12-02 00:00:00'),
(1, 2, '2014-12-08 00:00:00'),
(1, 2, '2014-12-09 00:00:00'),
(1, 2, '2014-12-10 00:00:00'),
(1, 2, '2014-12-11 00:00:00'),
(1, 2, '2014-12-12 00:00:00'),
(1, 2, '2014-12-15 00:00:00'),
(1, 2, '2014-12-16 00:00:00'),
(1, 2, '2014-12-17 00:00:00'),
(1, 2, '2014-12-18 00:00:00'),
(1, 2, '2014-12-19 00:00:00')
;
CREATE TABLE holidays
([Date] datetime)
;
INSERT INTO holidays
([Date])
VALUES
('2014-12-22 00:00:00'),
('2014-12-23 00:00:00'),
('2014-12-24 00:00:00'),
('2014-12-25 00:00:00'),
('2014-12-26 00:00:00'),
('2014-12-29 00:00:00'),
('2014-12-30 00:00:00'),
('2014-12-31 00:00:00')
;
CREATE TABLE students
([StudentID] int, [Name] varchar(5))
;
INSERT INTO students
([StudentID], [Name])
VALUES
(1, 'John'),
(2, 'Peter')
;
Query 1:
DECLARE #start DATE, #end DATE
SELECT #start = '20141201', #end = '20141231'
;WITH tdate AS
(
SELECT TOP (DATEDIFF(DAY, #start, #end) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects
)
SELECT DISTINCT Name
FROM students s
INNER JOIN attendance a ON s.StudentID = a.StudentID
INNER JOIN tdate ON DATEADD(DAY, n-1, #start) = a.Date
GROUP BY NAME
HAVING
(SELECT count(*)
FROM tdate
LEFT OUTER JOIN holidays h ON DATEADD(DAY, n-1, #start) = h.Date
WHERE h.date is null
AND DATEPART(dw,DATEADD(DAY, n-1, #start)) not in (1,7))
- COUNT(*) >= 3
Results:
| NAME |
|-------|
| Peter |
UPDATE
SELECT s.StudentID, d.Date
FROM students s
INNER JOIN (
SELECT DATEADD(DAY, n-1, #start) as Date
FROM tdate
LEFT OUTER JOIN holidays h ON DATEADD(DAY, n-1, #start) = h.Date
WHERE h.date is null
AND DATEPART(dw,DATEADD(DAY, n-1, #start)) not in (1,7)) d ON 1 = 1
LEFT OUTER JOIN attendance a ON s.StudentID = a.StudentID AND d.Date = a.Date
WHERE a.StudentID IS NULL
ORDER BY s.StudentID, d.Date
Results:
| STUDENTID | DATE |
|-----------|------------|
| 2 | 2014-12-03 |
| 2 | 2014-12-04 |
| 2 | 2014-12-05 |
Related
I am using T-SQL for a few months and there is one thing I simply don't know how to do. Let's say we have a table created below
DECLARE #table TABLE
(
id int,
status_FK int,
[value] int
)
INSERT INTO #table (id, status_FK, [value])
VALUES (1, 1, 1000),
(2, 1, 2000),
(3, 2, 3000),
(4, 1, 2000),
(5, 2, 2000),
(6, 2, 3000),
(7, 5, 12000)
SELECT
(SELECT SUM(value) WHERE status_FK = 1) AS 'status1',
(SELECT SUM(value) WHERE status_FK = 2) AS 'status2'
FROM
#table
WHERE
value > 1500
What I'm trying to do is use condition value > 1500 for all subqueries but also each subquery have specific condition. Can someone please explain me how to do this simple thing without repeating whole main query in both subqueries with different conditions? Many thanks <3
Probably something like that:
SELECT
SUM(case when status_FK = 1 then value else 0 end) as `status1`,
SUM(case when status_FK = 2 then value else 0 end) as `status2`
FROM #table
WHERE value > 1500
Update 1:
You can have many as you like, best if you have small of column to display:
SELECT
SUM(case when status_FK = 1 then value else 0 end) as `status1`,
SUM(case when status_FK = 2 then value else 0 end) as `status2`,
...
SUM(case when status_FK = n then value else 0 end) as `statusn`,
FROM #table
WHERE value > 1500
For complex cases you can look at PIVOT.
You can try using Join also
SELECT Status_fk
,sum(value)
FROM #table t
INNER JOIN (
VALUES (1)
,(2)
) X(StatusID) ON t.status_FK = X.StatusID
WHERE value > 1500
GROUP BY status_FK
You can use PIVOT, a powerful operator.
CREATE TABLE #TEMP
(
id int,
status_FK int,
[value] int
)
INSERT INTO #TEMP (id, status_FK, [value])
VALUES (1, 1, 1000),
(2, 1, 2000),
(3, 2, 3000),
(4, 1, 2000),
(5, 2, 2000),
(6, 2, 3000),
(7, 5, 12000)
SELECT [1] AS status1, [2] AS status2
FROM (SELECT status_FK, value FROM #TEMP WHERE value > 1500) AS T
PIVOT(SUM([value]) FOR status_FK IN ([1], [2])) AS PT
I have a table called DimWorkerCode. It has a column called WorkerCode. This is our business key here. Changes that can happen to a WorkerCode is UnitCode and WindowsID as shown in figure below.
I want to ignore WindowsID, and just select WorkerCode, Unitcode and StartDate which will be minimum StartDate and EndDate which will be maximum EndDate.
I tried this query:
SELECT
WorkerCode, UnitCode,
MIN(StartDate) AS StartDate,
MAX(ISNULL(EndDate, '9999/12/31')) AS EndDate
FROM
dbo.DimWorkerCode
GROUP BY
WorkerCode, UnitCode
and got this result set:
But I am expecting result something like this.
How can I do it in T-SQL? Help please.
The issue is that you are trying to pull some data without grouping on it (enddate for min(startdate).
I'm not certain this is the best solution, but it should work. Using Row_Number(), we're listing the records by min(startdate) and max(enddate) without grouping them; then you pull the records at the beginning of both listings.
select
WorkerCode,
UnitCode,
StartDate,
EndDate
from
(
select
WorkerCode,
UnitCode,
StartDate,
EndDate
row_number() over (partition by WorkerCode, UnitCode order by StartDate) as MinStartDateRow,
row_number() over (partition by WorkerCode, UnitCode order by EndDate desc) as MaxEndDateRow
from
dbo.DimWorkerCode
) x
where
MinStartDateRow = 1
or MaxEndDateRow = 1
If I understand your question correctly and you want to get min and max dates, next approach may help. The important part here is to define groups (each new group begins when WorkerCode or Unitcode are changed).
Table:
CREATE TABLE DimWorkerCode (
ID int,
WorkerCode varchar(4),
UnitCode varchar(4),
WindowID int,
StartDate date,
EndDate date
)
INSERT INTO DimWorkerCode
(ID, WorkerCode, UnitCode, WindowID, StartDate, EndDate)
VALUES
(1, 'AA01', 'AA00', 2, '2007-01-01', '2008-01-01'),
(2, 'AA01', 'AA00', 5, '2008-01-01', '2008-01-01'),
(3, 'AA01', 'AA00', 3, '2009-01-01', '2010-01-01'),
(4, 'AA01', 'XYZ0', 9, '2010-01-01', '2011-01-01'),
(5, 'AA01', 'XYZ0', 12, '2011-01-01', '2012-01-01'),
(6, 'AA01', 'AA00', 13, '2012-01-01', '2013-01-01'),
(7, 'AA01', 'AA00', 24, '2013-01-01', '2014-01-01'),
(8, 'AA01', 'AA00', 17, '2014-01-01', '2015-01-01'),
(9, 'AA01', 'AA00', 18, '2015-01-01', '2016-01-01'),
(10, 'AA01', 'AA00', 22, '2016-01-01', NULL)
Statement:
;WITH ChangeCTE AS (
SELECT
*,
CASE
WHEN (UnitCode = LAG(UnitCode) OVER (ORDER BY ID)) AND (WorkerCode = LAG(WorkerCode) OVER (ORDER BY ID)) THEN 0
ELSE 1
END AS Change
FROM DimWorkerCode
), GroupCTE AS (
SELECT
*,
SUM(Change) OVER (ORDER BY ID) AS GroupID
FROM ChangeCTE
)
SELECT
MAX(WorkerCode) AS WorkerCode,
MAX(UnitCode) AS UnitCode,
MIN(StartDate) AS StartDate,
MAX(ISNULL(EndDate, '9999/12/31')) AS EndDate
FROM GroupCTE
GROUP BY GroupID
Output:
WorkerCode UnitCode StartDate EndDate
AA01 AA00 01/01/2007 00:00:00 01/01/2010 00:00:00
AA01 XYZ0 01/01/2010 00:00:00 01/01/2012 00:00:00
AA01 AA00 01/01/2012 00:00:00 31/12/9999 00:00:00
I got the idea from Zhorov and modifying it to meet my exact requirement.
CREATE TABLE DimWorkerCode (
ID int,
WorkerCode varchar(4),
UnitCode varchar(4),
WindowID int,
StartDate date,
EndDate date
);
INSERT INTO DimWorkerCode
(ID, WorkerCode, UnitCode, WindowID, StartDate, EndDate)
VALUES
(1, 'AA01', 'AA00', 2, '2007-01-01', '2008-01-01'),
(2, 'AA01', 'AA00', 5, '2008-01-01', '2008-01-01'),
(3, 'AA01', 'AA00', 3, '2009-01-01', '2010-01-01'),
(4, 'AA01', 'XYZ0', 9, '2010-01-01', '2011-01-01'),
(5, 'AA01', 'XYZ0', 12, '2011-01-01', '2012-01-01'),
(6, 'AA01', 'AA00', 13, '2012-01-01', '2013-01-01'),
(7, 'AA01', 'AA00', 24, '2013-01-01', '2014-01-01'),
(8, 'AA01', 'AA00', 17, '2014-01-01', '2015-01-01'),
(9, 'AA01', 'AA00', 18, '2015-01-01', '2016-01-01'),
(10, 'AA01', 'AA00', 22, '2016-01-01', NULL)
Here it goes
WITH CTE AS
(
SELECT
ID,
WorkerCode, LAG(WorkerCode, 1, WorkerCode) OVER (ORDER BY ID) AS PrevWorkerCode,
UnitCode, LAG(UnitCode, 1, UnitCode) OVER (ORDER BY ID) AS PrevUnitCode,
StartDate,
ISNULL(EndDate , '9999/12/31') AS EndDate
FROM DimWorkerCode
)
,
ChangedCTE AS
(
SELECT *, IIF(WorkerCode = PrevWorkerCode AND UnitCode = PrevUnitCode, 0, 1) AS Changed FROM CTE
)
,
GroupedCTE AS
(
SELECT *, SUM(Changed) OVER(ORDER BY ID) AS GroupID FROM ChangedCTE
)
,
MinMaxCTE As
(
SELECT MAX(WorkerCode) AS WorkerCode, MAX(UnitCode) AS UnitCode, MIN(StartDate) AS StartDate, MAX(EndDate) AS EndDate FROM GroupedCTE GROUP BY GroupID
)
SELECT WorkerCode, UnitCode, StartDate, IIF(EndDate = '9999-12-31', NULL, EndDate) AS EndDate FROM MinMaxCTE
Output:
I am trying to get the statement on fetching the previous and next rows of a selected row.
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
DECLARE
#ItemId int = 2,
#orderid int = 10
Required output:
Input for the procedure is order id =10 and item id =2 and i need to check item-2 is in the any other order i.e only previous and next item of matched record/order as per order date
Is this what your after? (Updated to reflect edit [OrderDate] to question)
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
declare #ItemId int=2 , #orderid int = 10;
Query
With cte As
(
Select ROW_NUMBER() OVER(ORDER BY OrderDate) AS RecN,
*
From #OderDetail Where ItemId=#ItemId
)
Select Id, OrderId, ItemId, [Lookup] From cte Where
RecN Between ((Select Top 1 RecN From cte Where OrderId = #orderid) -1) And
((Select Top 1 RecN From cte Where OrderId = #orderid) +1)
Order by id
Result:
Id OrderId ItemId Lookup
2 10 2 BE
4 2 2 D
5 3 2 DD
Another possible approach is to use LAG() and LEAD() functions, that return data from a previous and subsequent row form the same resul tset.
-- Table
DECLARE #OrderDetail TABLE (
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OrderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
-- Item and order
DECLARE
#ItemId int = 2,
#orderid int = 10
-- Statement
-- Get previois and next ID for every order, grouped by ItemId, ordered by OrderDate
;WITH cte AS (
SELECT
Id,
LAG(Id, 1) OVER (PARTITION BY ItemId ORDER BY OrderDate) previousId,
LEAD(Id, 1) OVER (PARTITION BY ItemId ORDER BY OrderDate) nextId,
ItemId,
OrderId,
Lookup
FROM #OrderDetail
)
-- Select current, previous and next order
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.Id) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
UNION ALL
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.previousId) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
UNION ALL
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.nextId) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
Output:
Id OrderId ItemId OrderDate Lookup
2 10 2 11/06/2018 00:00:00 BE
4 2 2 04/06/2018 00:00:00 D
5 3 2 14/06/2018 00:00:00 DD
Update to given this data set: I see where you are going with this. Note that in SOME cases there IS no row before the given one - so it only returns 2 not 3. Here I updated the CTE version. Un-comment the OTHER row to see 3 not 2 as there is then one before the selected row with that Itemid.
Added a variable to demonstrate how this is better allowing you to get 1 before and after or 2 before/after if you change that number (i.e. pass a parameter) - and if less rows, or none are before or after it gets as many as it can within that constraint.
Data setup for all versions:
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(9, 4, 2, '2018-06-14', 'DD'),
(6, 4, 2, '2018-06-14', 'R'),
--(10, 10, 2, '2018-06-02', 'BE'), -- un-comment to see one before
(23, 4, 2, '2018-06-14', 'R');
DECLARE
#ItemId int = 2,
#orderid int = 2;
CTE updated version:
DECLARE #rowsBeforeAndAfter INT = 1;
;WITH cte AS (
SELECT
Id,
OrderId,
ItemId,
OrderDate,
[Lookup],
ROW_NUMBER() OVER (ORDER BY OrderDate,Id) AS RowNumber
FROM #OderDetail
WHERE
ItemId = #itemId -- all matches of this
),
myrow AS (
SELECT TOP 1
Id,
OrderId,
ItemId,
OrderDate,
[Lookup],
RowNumber
FROM cte
WHERE
ItemId = #itemId
AND OrderId = #orderid
)
SELECT
cte.Id,
cte.OrderId,
cte.ItemId,
cte.OrderDate,
cte.[Lookup],
cte.RowNumber
FROM ctE
INNER JOIN myrow
ON ABS(cte.RowNumber - myrow.RowNumber) <= #rowsBeforeAndAfter
ORDER BY OrderDate, OrderId;
You probably want the CTE method (See an original at the end of this) however:
Just to point out, this gets the proper results but is probably not what you are striving for since it is dependent on the row order and the item id not the actual row with those two values:
SELECT TOP 3
a.Id,
a.OrderId,
a.ItemId,
a.Lookup
FROM #OderDetail AS a
WHERE
a.ItemId = #ItemId
To fix that, you can use an ORDER BY and TOP 1 with a UNION, kind of ugly. (UPDATED with date sort and != on the id.)
SELECT
u.Id,
u.OrderId,
u.OrderDate,
u.ItemId,
u.Lookup
FROM (
SELECT
a.Id,
a.OrderId,
a.OrderDate,
a.ItemId,
a.Lookup
FROM #OderDetail AS a
WHERE
a.ItemId = #ItemId
AND a.OrderId = #orderid
UNION
SELECT top 1
b.Id,
b.OrderId,
b.OrderDate,
b.ItemId,
b.Lookup
FROM #OderDetail AS b
WHERE
b.ItemId = #ItemId
AND b.OrderId != #orderid
ORDER BY b.OrderDate desc, b.OrderId
UNION
SELECT top 1
b.Id,
b.OrderId,
b.OrderDate,
b.ItemId,
b.Lookup
FROM #OderDetail AS b
WHERE
b.ItemId = #ItemId
AND b.OrderId != #orderid
ORDER BY b.OrderDate asc, b.OrderId
) AS u
ORDER BY u.OrderDate asc, u.OrderId
I think its simple, you can check with min(Id) and Max(id) with left outer join or outer apply
like
Declare #ItemID int = 2
Select * From #OderDetail A
Outer Apply (
Select MIN(A2.Id) minID, MAX(A2.Id) maxID From #OderDetail A2
Where A2.ItemId =#ItemID
) I05
Outer Apply(
Select * From #OderDetail Where Id=minID-1
Union All
Select * From #OderDetail Where Id=maxID+1
) I052
Where A.ItemId =#ItemID Order By A.Id
Let me know if this helps you or you face any problem with it...
Regards,
I have an example on sql fiddle. What I am trying to do is divide the overall COUNT(DISTINCT ID) by the weekly COUNT(DISTINCT ID). For example if I have the following conceptual setup of what the result should be.
year week id_set overall_distinct week_distinct result
2016 1 A,A,A,B,B,C 0 3 0
2016 2 A,B,C,C,D 1 4 .25
2016 3 A,B,C,E,F 2 5 .4
The table linked to on sql fiddle has the following schema. Also, in reality I do have multiple values for 'year'.
CREATE TABLE all_ids
([year] int, [week] int, [id] varchar(57))
;
INSERT INTO all_ids
([year], [week], [id])
VALUES
(2016, 1, 'A'),
(2016, 1, 'A'),
(2016, 1, 'A'),
(2016, 1, 'B'),
(2016, 1, 'B'),
(2016, 1, 'C'),
(2016, 2, 'A'),
(2016, 2, 'B'),
(2016, 2, 'C'),
(2016, 2, 'C'),
(2016, 2, 'D'),
(2016, 3, 'A'),
(2016, 3, 'B'),
(2016, 3, 'C'),
(2016, 3, 'E'),
(2016, 3, 'F')
;
Edit
I apologize for the confusion. The above table was just a conceptual example of the result. The actual result only needs to look like the following.
year week overall_distinct week_distinct result
2016 1 0 3 0
2016 2 1 4 .25
2016 3 2 5 .4
there is no need to include id_set
I used dense_rank and max() over () to simulate count (distinct ...) with window functions. You could try to do it with another subquery
select
year, week
, id_set = stuff((
select
',' + a.id
from
all_ids a
where
a.year = t.year
and a.week = t.week
order by a.id
for xml path('')
), 1, 1, '')
, overall_distinct = count(case when cnt = 1 then 1 end)
, week_distinct = count(distinct id)
, result = cast(count(case when cnt = 1 then 1 end) * 1.0 / count(distinct id) as decimal(10, 2))
from (
select
year, week, id, cnt = max(dr) over (partition by id)
from (
select
*, dr = dense_rank() over (partition by id order by year, week)
From
all_ids
) t
) t
group by year, week
Output
year week id_set overall_distinct week_distinct result
--------------------------------------------------------------------------
2016 1 A,A,A,B,B,C 0 3 0.00
2016 2 A,B,C,C,D 1 4 0.25
2016 3 A,B,C,E,F 2 5 0.40
This would be one way, probably not the best one:
;with weekly as
(
select year, week, count(distinct id) nr
from all_ids
group by year, week
),
overall as
(
select a.week, count(distinct a.id) nr
from all_ids a
where a.id not in (select id from all_ids where week <> a.week and id = a.id )
group by week
)
select distinct a.year
, a.week
, stuff((select ', ' + id
from all_ids
where year = a.year and week = a.week
for xml path('')), 1, 1, '') ids
, w.Nr weeklyDistinct
, isnull(t.Nr, 0) overallDistinct
from all_ids a join weekly w on a.year = w.year and a.week = w.week
left join overall t on t.week = a.week
One statement count only
declare #t table (y int, w int, id varchar(57));
INSERT #t (y, w, id)
VALUES
(2016, 1, 'A'),
(2016, 1, 'A'),
(2016, 1, 'A'),
(2016, 1, 'B'),
(2016, 1, 'B'),
(2016, 1, 'C'),
(2016, 2, 'A'),
(2016, 2, 'B'),
(2016, 2, 'C'),
(2016, 2, 'C'),
(2016, 2, 'D'),
(2016, 3, 'A'),
(2016, 3, 'B'),
(2016, 3, 'C'),
(2016, 3, 'E'),
(2016, 3, 'F');
select t1.w, count(distinct t1.id) as wk
, (count(distinct t1.id) - count(distinct t2.id)) as [all]
, (cast(1 as smallmoney) - cast(count(distinct t2.id) as smallmoney) / count(distinct t1.id)) as [frac]
from #t t1
left join #t t2
on t2.id = t1.id
and t2.w <> t1.w
group by t1.w
order by t1.w;
I have table like below,
Txn_Id Txn_Type
___________________
1 101
1 102
1 103
1 104
2 101
2 102
2 104
3 101
3 104
I want rows which has only txn_type 101 and 104. For eg., I should get only Txn_Id "3" for above data.
I tried like below and getting result. Is it possible to have single query to achive this.
Select txn_id from Txn where txn_id in (Select txn_id from Txn where txn_id = 101) and txn_id =104.
Select txn_id from Txn where txn_type in (101,104)
option 2
Select txn_id from Txn where (txn_type = 101 OR txn_type=104)
To get only "3"
Select distinct txn_id from Txn t1 where (txn_type = 101 OR txn_type=104)
and not exists(
select 1 from Txn t2 where t2.txn_type IN (102,103) and t2.txn_id = t1.txn_id
)
Hi As per your above comments you only need the txn_id =3(max)
Please Find the code below.
DECLARE #Table1 TABLE
(txn_id int, Txn_Type int)
;
INSERT INTO #Table1
(txn_id , Txn_Type )
VALUES
(1, 101),
(1, 102),
(1, 103),
(1, 104),
(2, 101),
(2, 102),
(2, 104),
(3, 101),
(3, 104)
;
Select max(txn_id ),Txn_Type
from #Table1 where item in (101,104)
group by Txn_Type
As balaji pointed out, #Ayush solution is not flexible, since will return incorrect results if you, for example, add another pair of records in the table (4,101) and (4,104). IMO, you have to join table to itself for some filtering, something like this:
DECLARE #Table1 TABLE
(txn_id int, Txn_Type int);
INSERT INTO #Table1
(txn_id , Txn_Type )
VALUES
(1, 101),
(1, 102),
(1, 103),
(1, 104),
(2, 101),
(2, 102),
(2, 104),
(3, 101),
(3, 104),
(4, 101),
(4, 104);
select t1.*
from #Table1 t1
inner join (select txn_id, count(*) as total
from #Table1
group by Txn_id
having count(*) < 3
) t2 on t2.txn_id = t1.txn_id
where t1.Txn_Type in (101,104)