Excluding groups from a resultset using criteria in that resultset - sql

Using SQL Server 2008.
We are given a Code, say 020286 that gives us a starting resultset.
Starting data:
Code L R G
020286 2 703 1
030383 3 6 0
031847 4 5 0
021932 7 10 0
022499 8 9 0
020068 229 310 1
020866 231 306 1
020524 232 241 0
030772 233 234 0
031787 235 236 0
031859 237 238 0
031947 239 240 0
020964 242 323 1
021215 253 342 1
030728 343 344 0
020990 345 346 0
022521 347 354 0
Now I want to exclude rows whose L is between L and R of any rows whose G=1 (in the same resultset) excepting the given Code (essentially do "L between L and R" for all G=1 except the given Code), while still keeping all G=1.
Expected results:
Code L R G
020286 2 703 1
030383 3 6 0
031847 4 5 0
021932 7 10 0
022499 8 9 0
020068 229 310 1
020866 231 306 1
020964 242 323 1
021215 253 342 1
030728 343 344 0
020990 345 346 0
022521 347 354 0
Here is a table var with starting data.
declare #t table (Code nvarchar(10),L int, R int, G int)
insert into #t (Code, L, R, G)
select '020286',2,703,1 union
select '030383',3,6,0 union
select '031847',4,5,0 union
select '021932',7,10,0 union
select '022499',8,9,0 union
select '020068',229,610,1 union
select '020866',231,396,1 union
select '020524',232,241,0 union
select '030772',233,234,0 union
select '031787',235,236,0 union
select '031859',237,238,0 union
select '031947',239,240,0 union
select '020964',242,383,1 union
select '021215',253,342,1 union
select '030728',343,344,0 union
select '020990',345,346,0 union
select '022521',347,354,0
select * from #t

Presumably the given code is identifyable somehow, so I'll assume there's a variable available containing it, which I'm calling #given:
DECLARE #given nvarchar(10)
SELECT #given = '020286'
First identify the L of rows where G=1 and which are not the given code:
SELECT L INTO #L FROM #t WHERE G=1 AND Code <> #given;
Now we need to join each pair of consecutive values in #L into rows in a new table which can then be used to work out which rows in #t we dont want. There's a few ways of doing this, but this one is pretty easy and simple to follow:
SELECT L as [Low], Cast(0 as int) as [High]
INTO #Pairs
FROM #L
WHERE L < (SELECT Max(L) FROM #L);
UPDATE #Pairs SET [High] = (SELECT Min(L) FROM #L WHERE L > [Low]);
Now remove the unwanted rows in #t:
DELETE t
FROM #t t CROSS JOIN #Pairs p
WHERE L > p.[Low] AND L < p.[High]
What's left should be correct:
SELECT * FROM #t ORDER BY L

Using your stated criteria:
Exclude rows whose L is between L and R of any rows whose G=1 (in the same resultset) excepting the given Code (essentially do "L between L and R" for all G=1 except the given Code), while still keeping all G=1:
SELECT *
FROM #t
WHERE G = 1
OR NOT(L >= (SELECT MIN(L) FROM #t WHERE G = 1 AND NOT Code = '020286')
AND L <= (SELECT MAX(R) FROM #t WHERE G = 1 AND NOT Code = '020286'))
However, your expected results do not appear to match your stated criteria. You appear to be saying (for this data set and criteria) that you want to exclude G<>1 rows with L between 229 (MIN(L) where Code <> '020286') and 610 (MAX(R) where Code <> '020286'), but these rows don't appear to match the apparent intent of your stated criteria:
CODE L R G
030728 343 344 0
020990 345 346 0
022521 347 354 0
In fact, it would appear that your criteria is actually:
Exclude rows whose L is between L and R of any previous rows whose G=1 (in the same resultset) excepting the given Code (essentially do "L between L and R" for the previous G=1 except the given Code), while still keeping all G=1:
WITH Criteria
AS (SELECT Code, L, R, G
FROM #t
WHERE G = 1)
SELECT T.Code, T.L, T.R, T.G
FROM #t AS T
INNER JOIN Criteria
ON T.L >= Criteria.L
AND T.L <= Criteria.R
AND T.G <> 1
AND Criteria.Code <> '020286'
UNION ALL
SELECT Code, L, R, G
FROM Criteria
ORDER BY 1

Related

Scalar Function Coalesce Rows

I am trying to get a scalar function to work that combines all the rows of display values.
When I run my distinct query I get results I expect:
SELECT DISTINCT [tblMSC_Notes].[Display]
FROM [tblMSC_Notes]
INNER JOIN [tblUnits_Notes] ON [tblUnits_Notes].[NotesID] = [tblMSC_Notes].[NoteID]
WHERE (([tblMSC_Notes].[Display] IS NOT NULL) AND ([tblUnits_Notes].[UnitID] = 15))
ORDER BY [tblMSC_Notes].[Display]
Rows Displayed:
♦
ML
No Cloak
What I am trying to do now is output the Display as a single field "♦, ML, No Cloak"
Looking at the various examples I think I am close but I keep getting errors with the query:
BEGIN
-- Declare the return variable here
DECLARE #Result nvarchar(Max)
SET #Result = ''
WITH Display_CTE (Display)
AS
(
SELECT DISTINCT [tblMSC_Notes].[Display]
FROM [tblMSC_Notes]
INNER JOIN [tblUnits_Notes] ON [tblUnits_Notes].[NotesID] = [tblMSC_Notes].[NoteID]
WHERE (([tblMSC_Notes].[Display] IS NOT NULL) AND ([tblUnits_Notes].[UnitID] = #UnitID))
ORDER BY [tblMSC_Notes].[Display]
)
SET #Result = (SELECT COALESCE(#Result + ', ','') as DisplayResult
FROM (
SELECT [tblMSC_Notes].[Display]
FROM [tblMSC_Notes]
WHERE [Display] IN (SELECT Display FROM Display_CTE)
ORDER BY [tblMSC_Notes].[Display]
)
)
-- Return the result of the function
RETURN #Result
END
I was going to make this a scalar function in another query.
Here is some sample data from the tblMSC_Notes table:
ID UnitID NotesID
1 1513 154
2 1513 154
3 5032 152
4 5032 155
5 5033 152
6 5033 155
7 5033 43
8 5034 43
9 5034 152
10 5034 155
11 5035 152
12 5035 155
13 5035 43
Here is join using the Note ID
ID UnitID NotesID Display
1 1513 154 LB
2 1513 154 LB
3 5032 152 AL
4 5032 155 PL
5 5033 152 AL
6 5033 155 PL
7 5033 43 N
8 5034 43 N
9 5034 152 AL
10 5034 155 PL
Mohd - Thanks for the help so I would just need to change my create function to:
CREATE FUNCTION [dbo].[fnAnnex03_MasterShipChart_Notes] (
-- Add the parameters for the function here
#UnitID int
)
RETURNS nvarchar(Max)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result nvarchar(Max)
SET #Result = ''
SET #Result = (SELECT string_agg(DISTINCT [tblMSC_Notes].[Display], ',') FROM [tblMSC_Notes] INNER JOIN [tblUnits_Notes] ON [tblUnits_Notes].[NotesID] = [tblMSC_Notes].[NoteID] WHERE (([tblMSC_Notes].[Display] IS NOT NULL) AND ([tblUnits_Notes].[UnitID] = #UnitID)) ORDER BY [tblMSC_Notes].[Display])
-- Return the result of the function
RETURN #Result
END
GO
Could you please provide a FIDDLE for this example with data.
You just need to use string_agg in this query as below:
SELECT string_agg(DISTINCT [tblMSC_Notes].[Display], ',')
FROM [tblMSC_Notes]
INNER JOIN [tblUnits_Notes] ON [tblUnits_Notes].[NotesID] = [tblMSC_Notes].[NoteID]
WHERE (([tblMSC_Notes].[Display] IS NOT NULL) AND ([tblUnits_Notes].[UnitID] = #UnitID))
ORDER BY [tblMSC_Notes].[Display]

Aggregate and count disctinct values with a join

This question is a mix of those duplicates:
Get unique values using STRING_AGG in SQL Server
SQL/mysql - Select distinct/UNIQUE but return all columns?
I just can't manage to make it work all at once.
I have two tables :
TABLE A
IntervalId Starts Ends
-------------------------
1 0 10
2 10 25
3 25 32
4 32 40
TABLE B
Id ErrorType Starts Ends
----------------------------------------
1 666 0 25
2 666 10 32
3 777 0 32
4 666 25 40
5 777 10 25
Starting from the time intervals in table B, I'm trying to count and list, in each interval, the error types that might have happened during that interval. And remove duplicates.
Please note that there isn't any Start or End in table B that does not exist in Table A (Table A is generated from them).
the result with duplicates would be this :
Starts Ends ErrorsCount Errors
-----------------------------------------------
0 10 2 666, 777
10 25 4 666, 666, 777, 777
25 32 3 666, 777, 666
32 40 1 666
The result I'm looking for is without duplicates:
Starts Ends DistinctErrorsCnt DistinctErrors
-----------------------------------------------
0 10 2 666, 777
10 25 2 666, 777
25 32 2 666, 777
32 40 1 666
Here is my attemot, but I can't understand how to get ErrorType out of the bit that does the "distinct" without SQL server complaining that it's not in an aggregate or group by. Or, as soon as I put it into a Group by, then all the different Error Types are wiped by the first one that comes around. I end up with only 666 everywhere.
SELECT
IntervalId,
Starts,
Ends,
COUNT([TableB].ErrorType) as DistinctErrorsCnt,
DistinctErrors= STRING_AGG([TableB].ErrorType, ',')
FROM
(
SELECT DISTINCT
[TableA].IntervalId,
FROM TableB LEFT JOIN TableA ON
(
[TableA].Starts= [TableB].Starts
OR [TableA].Ends = [TableB].Ends
OR ([TableA].Starts >= [TableB].Starts AND [TableA].Ends <= [TableB].Ends)
)
GROUP BY
[TableA].IntervalId,
[TableA].Starts,
[TableA].Ends,
) NoDuplicates
GROUP BY
NoDuplicates.IntervalId,
NoDuplicates.Starts,
NoDuplicates.Starts
Again: This is not syntactically correct, for the reason I explained above.
You can use aggregation:
select
a.starts,
a.ends,
count(distinct b.errorType) DistinctErrorsCnt,
string_agg(b.errorType, ', ') within group(order by b.starts) DistinctErrors
from tablea a
inner join tableb b on b.starts >= a.ends and b.ends <= a.start
group by a.intervalId, a.start, a.end
If you want to avoid duplicates, you could use a subquery, or better yet, cross apply:
select
a.starts,
a.ends,
count(*) DistinctErrorsCnt,
string_agg(b.errorType, ', ') within group(order by b.starts) DistinctErrors
from tablea a
cross apply (
select distinct errorType from tableb b where b.starts >= a.ends and b.ends <= a.start
) b
group by a.intervalId, a.start, a.end

SQL subselect queries with use of the table outside subselect

I'm trying to retrieve the count the number of times that a teammate has beat his teammate based on DRIVERPOSITION, however i keep getting that invalid select-list in subselect i guess this is because i use the b and a table inside the subselect query?
Sample Data
RACEID CONSTRUCTORID DRIVERID DRIVERPOSITION
970 4 826 3
970 4 807 7
960 4 826 4
960 4 807 7
970 3 820 10
970 3 810 12
960 3 820 13
960 3 810 11
DESIRED RESULT
RACEID CONSTRUCTORID DRIVERID WINS
970 4 826 2
970 4 807 0
960 3 820 1
960 3 810 1
What i tried so far
SELECT
(
SELECT COUNT(
CASE
WHEN b.DRIVERPOSITION > a.DRIVERPOSITION THEN 1
ELSE 0 END
)
FROM QUALIFYING b
WHERE RACEYEAR = to_char(NOW(), 'YYYY')
AND a.CONSTRUCTORID = b.CONSTRUCTORID
AND a.RACEID = b.RACEID
AND a.DRIVERID != b.DRIVERID
)
FROM QUALIFYING a
INNER JOIN RACES
ON a.RACEID = RACES.RACEID
INNER JOIN DRIVERS
ON a.DRIVERID = DRIVERS.DRIVERID
INNER JOIN CONSTRUCTORS
ON a.CONSTRUCTORID = CONSTRUCTORS.CONSTRUCTORID
WHERE RACEYEAR = to_char(NOW(), 'YYYY');
I think this does what you want:
select raceid, constructorid, driverid,
sum(case when seqnum = 1 then 1 else 0 end) as numwins
from (select d.*,
row_number() over (partition by raceid, constructorid order by driverposition) as seqnum
from data d
) d
group by raceid, constructorid;
However, I have no idea how this fits into your query. Your sample data refers to one table. Your query has multiple table references.

Calculating difference in values in one table based on another table in SQL

I have 2 tables. MarketAction has the value I am finding for called 'B1', which will match to the 'Ticks' field in the TickTable.
(The MarketAction table is 25 million rows and the TickTable is 300 rows)
The tick table looks like this:
TickID Ticks MinorMagnet
140 2.80 1
141 2.82 0
142 2.84 0
143 2.86 1
144 2.88 0
145 2.90 0
146 2.92 0
147 2.94 0
I need to find 'TickDiff', and insert 'B1' and 'TickDiff' into a new temporary table.
If we look up the TickID for 'B1' from the TickTable, I need it to find the difference in the TickID's between the B1 TickID and the nearest MinorMagnet = 1 TickID value (the first higher (or zero), and the first lower (or zero)).
This works for the high:
WITH T1 AS
(
SELECT TOP 20 *,
144 - TickID AS TickDiff
FROM TickTable
WHERE MinorMagnet = 1
ORDER BY ABS( Ticks - 2.88 )
)
SELECT TOP 1 *
FROM T1
WHERE TickDiff >= 0
If I wanted the lower one I can modify for:
SELECT TOP 1 *, ABS(TickDiff)
FROM T1
WHERE TickDiff <= 0
But obviously works because I am explicitly stating the values from MarketAction. B1 = 2.88 and 144 is the TickID of B1.
I have tried all sorts of Joins and other things... Can't get my head around it...
How do I make it find the 'TickDiff' for each B1 value in MarketAction?
If I understand your question correctly then maybe this query might be what you want:
select tickid, ticks, minormagnet, min(abs(tickdiff)) tickdiff
from (
select ma.TickID, t.Ticks, t.MinorMagnet, tickdiff = t.TickID - oa.TickID
from MarketAction ma
join ticktable t on ma.TickID = t.TickID
outer apply (
select top 2 TickID
from ticktable
where MinorMagnet = 1
order by abs(t.Ticks-Ticks)
) oa
) x
group by tickid, ticks, minormagnet;

SELECT clause with SUM condition

Have this table :
//TEST
NUMBER TOTAL
----------------------------
1 158
2 355
3 455
//TEST1
NUMBER QUANTITY UNITPRICE
--------------------------------------------
1 3 5
1 3 6
1 3 4
2 4 8
3 5 4
I used following query:
SELECT t.NUMBER,sum(t.TOTAL),NVL(SUM(t2.quantity*t2.unitprice),0)
FROM test t INNER JOIN test1 t2 ON t.NUMBER=t2.NUMBER
GROUP BY t.NUMBER;
OUTPUT:
NUMBER SUM(TOTAL) SUM(t2.quantity*t2.unitprice)
-----------------------------------------------------------
1 474 45 <--- only this wrong
2 355 32
It seem like loop for three times so 158*3 in the record.
EXPECTED OUTPUT:
NUMBER SUM(TOTAL) SUM(t2.quantity*t2.unitprice)
-----------------------------------------------------------
1 158 45
2 355 32
You have to understand that the result of your join is something like this:
//TEST1
NUMBER QUANTITY UNITPRICE TOTAL
--------------------------------------------------------------
1 3 5 158
1 3 6 158
1 3 4 158
2 4 8 355
3 5 4 455
It means you don't need to apply a SUM on TOTAL
SELECT t.NUMBER,t.TOTAL,NVL(SUM(t2.quantity*t2.unitprice),0)
FROM test t INNER JOIN test1 t2 ON t.NUMBER=t2.NUMBER
GROUP BY t.NUMBER, t.TOTAL;
Something like this should work using a subquery separating the sums:
select t.num,
sum(t.total),
test1sum
from test t
join (
select num, sum(qty*unitprice) test1sum
from test1
group by num
) t2 on t.num = t2.num
group by t.num, test1sum
SQL Fiddle Demo
In regards to your sample data, you may not even need the additional group by on the test total field. If that table only contains distinct ids, then this would work the same:
select t.num,
t.total,
sum(qty*unitprice)
from test t
join test1 t2 on t.num = t2.num
group by t.num, t.total