T-SQL, Omit Unwanted Results - tsql

CREATE TABLE #t(LocationCode varchar(10), ResourceId int, TransType char(3))
INSERT #t
SELECT 'STORE 001', 1, 'In' UNION ALL
SELECT 'STORE 002', 2, 'In' UNION ALL
SELECT 'STORE 003', 3, 'In' UNION ALL
SELECT 'STORE 001', 1, 'Out' UNION ALL
SELECT 'STORE 004', 1, 'In' UNION ALL
SELECT 'STORE 004', 4, 'In' UNION ALL
SELECT 'STORE 004', 4, 'Out' UNION ALL
SELECT 'STORE 004', 1, 'Out' UNION ALL
SELECT 'STORE 001', 1, 'In'
DROP TABLE #t
How to show only the items with the corresponding location having maximum number of "Ins" when compared with "Outs" (sorry for my bad english).
LocationCode ResourceId
STORE 001[edited] 1
STORE 002 2
STORE 003 3

Assuming you only want Ins where there isn't a matching Out.
SELECT *
FROM #t AS a
WHERE a.TransType = 'In'
AND NOT EXISTS (
SELECT *
FROM #t AS b
WHERE b.TransType = 'Out'
AND b.LocationCode = a.LocationCode
AND b.ResourceId = a.ResourceId
)
You'd need more data in your schema to be able to match an Out with an In by time.
Try something simpler like this:
SELECT LocationCode, ResourceID
FROM #t
GROUP BY LocationCode, ResourceID
HAVING COUNT(*) % 2 = 1
Here's an example where the transactions are sequenced and two ways to use that sequence:
CREATE TABLE #t(LocationCode varchar(10), ResourceId int, TransType char(3), Seq int UNIQUE NOT NULL)
INSERT #t
SELECT 'STORE 001', 1, 'In', 1 UNION ALL
SELECT 'STORE 002', 2, 'In', 2 UNION ALL
SELECT 'STORE 003', 3, 'In', 3 UNION ALL
SELECT 'STORE 001', 1, 'Out', 4 UNION ALL
SELECT 'STORE 004', 1, 'In', 5 UNION ALL
SELECT 'STORE 004', 4, 'In', 6 UNION ALL
SELECT 'STORE 004', 4, 'Out', 7 UNION ALL
SELECT 'STORE 004', 1, 'Out', 8 UNION ALL
SELECT 'STORE 001', 1, 'In', 9
;WITH Ins AS (
SELECT * FROM #t
WHERE TransType = 'In'
)
,Outs AS (
SELECT * FROM #t
WHERE TransType = 'Out'
)
,Matched AS (
SELECT *,
(SELECT MIN(Seq)
FROM Outs
WHERE Outs.LocationCode = Ins.LocationCode
AND Outs.ResourceID = Ins.ResourceID
AND Outs.Seq > Ins.Seq) AS OutSeq
FROM Ins
)
SELECT *
FROM Matched
WHERE OutSeq IS NULL
;WITH LastIn AS (
SELECT ResourceID, MAX(Seq) AS Seq
FROM #t
WHERE TransType = 'In'
GROUP BY ResourceID
)
SELECT *
FROM LastIn
WHERE NOT EXISTS (
SELECT *
FROM #t outs
WHERE outs.TransType = 'Out'
AND Outs.ResourceID = LastIn.ResourceID
AND outs.Seq > LastIn.Seq)
DROP TABLE #t​​​​​​​​​​​

Related

Create New Rows based on valid to and valid from dates

I have a table that has account number, end of month valid from and end of month valid to columns.
What I need is a table that has account number and a column that has all the end of month dates of when the account was live, inclusive of end of month valid to. The Current Table looks like this
New table will need to be like this
I have tried using a calendar table and an CTE table type query but have had no success.
Any help would be great.
This can be achieved using Using multiple comma separated CTEs in a statement
Query
with t0 (i) AS (select 0 union all select 0 union all select 0 union all select 0 union all select 0 union all select 0),
t1 (i) AS (select 0 from t0 a inner join t0 b on a.i = b.i),
n (i) AS (select row_number()over(order by i) from t1),
Account_details (Account_number,valid_from,valid_to,mth,Live_date)As(
select Account_number,valid_from,valid_to, datediff(month,valid_from,valid_to ) mth, valid_from"Live_date"
from tbl1
union all
select Account_number,valid_from,valid_to, datediff(month,valid_from,valid_to ) mth, EOMONTH (dateadd(month,n.i,valid_from)) "Live_date"
from tbl1
inner join n on 1=1 and n.i between 1 and datediff(month,valid_from,valid_to )
)
select *
from Account_details
where Account_details.Account_number =1
order by Account_details.Account_number
Output
CTE Table t0, t1 and n will generate numbers. This is a best way to generate rows without any data.
Then the CTE table Account_details is used to pull data from the table.
Based on sql on the msdn thread how to get month end date between two dates.
DECLARE #Old AS Table (AccountNumber INT, ValidFrom DATE, ValidTo DATE)
DECLARE #New AS Table (AccountNumber INT, LiveDate DATE)
INSERT INTO #old
SELECT 1, '20130630', '20131130' UNION ALL
SELECT 2, '20130630', '20131231' UNION ALL
SELECT 3, '20120430', '20120531' UNION ALL
SELECT 4, '20170331', '20171130'
SELECT TOP 100 * FROM #old
DECLARE #AccountNumber INT, #ValidFrom DATE, #ValidTo DATE
DECLARE #Cursor CURSOR
SET #Cursor = CURSOR FOR
SELECT AccountNumber, ValidFrom, ValidTo
FROM #old
OPEN #Cursor
FETCH NEXT INTO #Cursor FROM #AccountNumber, #ValidFrom, #ValidTo
WHILE ##FETCH_STATUS = 0
BEGIN
;WITH cteEndMonthDates (MonthEndDate)
AS
(
SELECT eomonth(#ValidFrom) AS MonthEndDate
UNION ALL
SELECT eomonth( dateadd(day, 1, MonthEndDate)) AS MonthEndDate
FROM cteEndMonthDates
WHERE MonthEndDate < eomonth(#ValidTo)
)
INSERT INTO #new (AccountNumber, LiveDate)
SELECT #AccountNumber, MonthEndDate
FROM cteEndMonthDates
FETCH NEXT FROM #Cursor INTO #AccountNumber, #ValidFrom, #ValidTo
END
CLOSE #Cursor
DEALLOCATE #Cursor
SELECT * FROM #New
Edit: Or without the cursor
DECLARE #Old AS Table (AccountNumber INT, ValidFrom DATE, ValidTo DATE)
DECLARE #New AS Table (AccountNumber INT, LiveDate DATE)
INSERT INTO #old
SELECT 1, '20130630', '20131130' UNION ALL
SELECT 2, '20130630', '20131231' UNION ALL
SELECT 3, '20120430', '20120531' UNION ALL
SELECT 4, '20170331', '20171130' UNION ALL
SELECT 5, '20180430', '20190131' UNION ALL
SELECT 6, '20160430', '20180531'
SELECT TOP 100 * FROM #old
;WITH cteEndMonthDates (AccountNumber, MonthEndDate)
AS
(
SELECT AccountNumber, eomonth(ValidFrom) AS MonthEndDate
FROM #Old
UNION ALL
SELECT x.AccountNumber, eomonth( dateadd(day, 1, MonthEndDate)) AS MonthEndDate
FROM cteEndMonthDates x
JOIN #Old o ON o.AccountNumber = x.AccountNumber
WHERE MonthEndDate < eomonth(ValidTo)
)
SELECT AccountNumber, MonthEndDate
FROM cteEndMonthDates
order by AccountNumber, MonthEndDate
This should work.
;WITH Span AS (
SELECT
AccountNumber,
ValidFrom AS Valid
FROM dbo.Input
UNION ALL
SELECT
AccountNumber,
DATEADD(DAY, 1, Span.Valid) AS Valid
FROM Span
WHERE DATEADD(DAY, 1, Span.Valid) <= (SELECT ValidTo FROM dbo.Input WHERE AccountNumber = Span.AccountNumber)
)
SELECT * FROM Span
ORDER BY Span.AccountNumber, Span.Valid
OPTION (MAXRECURSION 0);

Assigning new group for every n records in each category

I need to create groups for every n records in each categories. For example I have student table with StdId, Gender and Subject, now I want to divide all students in groups by their gender and subject and each group can not have more than two.
Here is code for sample data
declare #Students table (StdId int, Gender char(1), Subj varchar(10))
insert into #students select 1, 'F', 'Math'
insert into #students select 2, 'M', 'Math'
insert into #students select 3, 'M', 'Math'
insert into #students select 4, 'F', 'Math'
insert into #students select 5, 'F', 'Math'
insert into #students select 6, 'F', 'History'
insert into #students select 7, 'M', 'History'
insert into #students select 8, 'F', 'English'
insert into #students select 9, 'F', 'English'
insert into #students select 10, 'M', 'English'
insert into #students select 11, 'F', 'English'
insert into #students select 12, 'M', 'English'
And I need output like this:
GroupNo Subj Gender StdId
1 English F 8
1 English F 9
2 English F 11
3 English M 10
3 English M 12
4 History F 6
5 History M 7
6 Math F 1
6 Math F 4
7 Math F 5
8 Math M 2
8 Math M 3
Ok found my solution with the help of this
;with FirstRank as
(select DENSE_RANK() over (order by Subj, Gender) as rnk, * from #students),
SecondRank AS
(select (ROW_NUMBER() OVER (PARTITION BY rnk ORDER by gender)-1)
/ 2 as rn,*
from FirstRank
)
select DENSE_RANK() OVER (ORDER BY rnk,rn) as GrpNo, Subj, Gender, StdId from SecondRank

Dynamic column names in dynamic pivot query

I have created a report for our Sales department, and they have what (on the surface) appears to be a simple request: if a column name contains "date_WeeklySales2017", drop the "date_" portion of the column name.
Given the following setup, how do I create a dynamic column name for COL_A in the dynamic pivot? (I'm using SQL Server 2005 sp3)
CREATE TABLE #CombinedSales(
WeekNumber int,
WeekStart varchar(11),
StoreNumber int,
Address nvarchar(255),
City nvarchar(255),
Province varchar(2),
WeeklySales2017 decimal(13,2),
WeeklySales2018 decimal(13,2)
)
INSERT INTO #CombinedSales
SELECT 1, '19-Feb-2018', 1234, '123 Any Street', 'Calgary', 'AB', 12345.67, 23456.78
UNION ALL
SELECT 1, '19-Feb-2018', 2345, '555 First Street', 'Toronto', 'ON', 33.25, 746.52
UNION ALL
SELECT 2, '26-Feb-2018', 1234, '123 Any Street', 'Calgary', 'AB', 12345.67, 23456.78
UNION ALL
SELECT 2, '26-Feb-2018', 2345, '555 First Street', 'Toronto', 'ON', 33.25, 746.52
UNION ALL
SELECT 3, '05-Mar-2018', 1234, '123 Any Street', 'Calgary', 'AB', 12345.67, 23456.78
UNION ALL
SELECT 3, '05-Mar-2018', 2345, '555 First Street', 'Toronto', 'ON', 33.25, 746.52
UNION ALL
SELECT 4, '12-Mar-2018', 1234, '123 Any Street', 'Calgary', 'AB', 12345.67, 23456.78
UNION ALL
SELECT 4, '12-Mar-2018', 2345, '555 First Street', 'Toronto', 'ON', 33.25, 746.52
UNION ALL
SELECT 5, '19-Mar-2018', 1234, '123 Any Street', 'Calgary', 'AB', 12345.67, 23456.78
UNION ALL
SELECT 5, '19-Mar-2018', 2345, '555 First Street', 'Toronto', 'ON', 33.25, 746.52
-- create the dynamic columns
DECLARE #COLUMNS nvarchar(MAX)
SELECT #COLUMNS = SUBSTRING(
(SELECT ', ' + QUOTENAME(WeekStart + '_WeeklySales2017'),
+ ', ' + QUOTENAME(WeekStart + '_WeeklySales2018')
FROM (SELECT DISTINCT TOP 100 PERCENT WeekNumber, WeekStart FROM #CombinedSales WHERE WeekNumber IS NOT NULL ORDER BY WeekNumber) R
ORDER BY WeekNumber DESC -- most-recent date displayed first in pivot table report
For XML PATH ('')
), 2, 10000)
PRINT #COLUMNS
-- build the dynamic pivot
DECLARE #PIVOT_SQL nvarchar(MAX)
SET #PIVOT_SQL = N'SELECT *
FROM (
SELECT StoreNumber,
Address,
City,
Province,
COL_A = CAST(WeekStart as varchar(11)) + ''_'' + COL_A,
VALUE_A
FROM #CombinedSales
CROSS APPLY( SELECT ''WeeklySales2017'', ISNULL(WeeklySales2017, 0)
UNION ALL
SELECT ''WeeklySales2018'', ISNULL(WeeklySales2018, 0)
) c (COL_A, VALUE_A)
) d
PIVOT(MAX(VALUE_A) FOR COL_A IN(' + #COLUMNS + ')
) PIV'
PRINT #PIVOT_SQL
EXEC sp_executesql #PIVOT_SQL
DROP TABLE #CombinedSales

JOIN vs. IN vs. EXISTS

I was reading an article that explained the difference between join and in and exists clause but I got confused with the explanation of different results when using NOT IN vs. NOT EXISTS clause. Can someone clarify why there is a difference between the output for NOT EXISTS clause vs. NOT IN clause? I tried after deleting the NULL row (t2.id = 8) from the table t2 and still got the same result.
Here's the SQL script from the article:
CREATE TABLE t1 (id INT, title VARCHAR(20), someIntCol INT)
GO
CREATE TABLE t2 (id INT, t1Id INT, someData VARCHAR(20))
GO
INSERT INTO t1
SELECT 1, 'title 1', 5 UNION ALL
SELECT 2, 'title 2', 5 UNION ALL
SELECT 3, 'title 3', 5 UNION ALL
SELECT 4, 'title 4', 5 UNION ALL
SELECT null, 'title 5', 5 UNION ALL
SELECT null, 'title 6', 5
INSERT INTO t2
SELECT 1, 1, 'data 1' UNION ALL
SELECT 2, 1, 'data 2' UNION ALL
SELECT 3, 2, 'data 3' UNION ALL
SELECT 4, 3, 'data 4' UNION ALL
SELECT 5, 3, 'data 5' UNION ALL
SELECT 6, 3, 'data 6' UNION ALL
SELECT 7, 4, 'data 7' UNION ALL
SELECT 8, null, 'data 8' UNION ALL
SELECT 9, 6, 'data 9' UNION ALL
SELECT 10, 6, 'data 10' UNION ALL
SELECT 11, 8, 'data 11'
And here's the SQL queries and their explanation:
-- IN doesn't get correct results.
-- That's because of how IN treats NULLs and the Three-valued logic
-- NULL is treated as an unknown, so if there's a null in the t2.t1id
-- NOT IN will return either NOT TRUE or NOT UNKNOWN. And neither can be TRUE.
-- when there's a NULL in the t1id column of the t2 table the NOT IN query will always return an empty set.
SELECT t1.*
FROM t1
WHERE t1.id NOT IN (SELECT t1id FROM t2)
-- NOT EXISTS gets correct results
SELECT t1.*
FROM t1
WHERE NOT EXISTS (SELECT * FROM t2 WHERE t1.id = t2.t1id)
GO
DROP TABLE t2
DROP TABLE t1
Here's the link to the article: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
Thank you!
As I can see, you can use them as the same thing in a lot of cases, but you can't forget the details behind them.
Probably you can get the same results applying both NOT IN and NOT EXISTS, but you could see differences in query which involve the NULL value. Because NOT EXISTS is the only way to obtain those rows with the NULL value.
You can see it better in this example:
update cars set c_owner = NULL where c_id = BMW03444
Well... Let's try to see if we have any car in stock that has not been sold yet.
select count(*) from cars where c_owner not it (select c_name from customers);
Output:
COUNT(*): 0
Where's the failure? Quite simple. You're not requesting a group of cars whose buyers has not been included in the list. You are simply asking for a car without owner. Anybody, even if he's not in the list. The correct form is:
select count(*)
from cars c1
where not exists (
select c_owner
from customers c2
where c1.c_owner=c2.customer_id
);
COUNT(*): 1
This is because NOT IN needs specific values to check in. So NULL values are set as FALSE and not counted.
NOT EXISTS checks the non existence of an element in a set, so NULL values are set as TRUE and are included.

tsql - Self reference

I have a CTE that returns the below records. How should i proceed with the new query so that all records with GID = NULL will get the previous last GID?
ID GID VALUE
1 1 Some Value
2 NULL Some Value
3 2 Some Value
4 3 Some Value
5 NULL Some Value
6 NULL Some Value
Eg. Records with ID 5 and 6 will have GID = 3
with C(ID, GID, VALUE) as
(
select 1, 1, 'Some Value' union all
select 2, NULL, 'Some Value' union all
select 3, 2, 'Some Value' union all
select 4, 3, 'Some Value' union all
select 5, NULL, 'Some Value' union all
select 6, NULL, 'Some Value'
)
select C1.ID,
C3.GID,
C1.VALUE
from C as C1
cross apply
(select top 1 C2.ID, C2.GID
from C as C2
where C2.ID <= C1.ID and
C2.GID is not null
order by C2.ID desc) as C3
;WITH C(ID, GID, VALUE) as
(
select 1, 1, 'Some Value' union all
select 2, NULL, 'Some Value' union all
select 3, 2, 'Some Value' union all
select 4, 3, 'Some Value' union all
select 5, NULL, 'Some Value' union all
select 6, NULL, 'Some Value'
)
select c.id, d.GID, c.value from c
cross apply
(select max(GID) GID from c d where c.id >= id) d