Populate a GroupID - sql

Please see the DDL below:
CREATE TABLE #Test (ID)
INSERT INTO #Test values (1)
INSERT INTO #Test values (2)
INSERT INTO #Test values (3)
INSERT INTO #Test values (0)
INSERT INTO #Test values (4)
INSERT INTO #Test values (5)
INSERT INTO #Test values (6)
INSERT INTO #Test values (8)
INSERT INTO #Test values (12)
INSERT INTO #Test values (19)
INSERT INTO #Test values (0)
INSERT INTO #Test values (44)
I am looking for a query that outputs the following (GroupID, ID).
1,1
1,2
1,3
2,4
2,5
2,6
2,8
2,12
2,19
3,44
A row with an ID of 0 splits the groups.
The query will look something like this:
select GroupID = row_number() over (order by (select null)),ID from #Test
The GroupID needs to be split using the rows that have a zero value. The query above just creates a new GroupID for each row.

SQL tables represent unordered sets. So, you cannot do what you want with your data structure, because it has no ordering. However, that is easily fixed by adding an identity column:
CREATE TABLE #Test (
TestId int identity(1, 1) primary key, -- the primary key is not strictly necessary, just my habit with identity columns.
Val int
);
(I took the liberty of renaming your column to val instead of id to avoid confusion.) An identity column captures insertion order.
The the group is calculated as the number of zeros before a given value. In SQL Server 2012+, this is easily done using a cumulative sum:
select t.*,
1 + sum(case when val = 0 then 1 else 0 end) over (order by testid) as groupid
from #test t;
I notice that you don't want zeros in the output, for this purpose, use a subquery after the cumulative sum:
select *
from (select t.*,
1 + sum(case when val = 0 then 1 else 0 end) over (order by testid) as groupid
from #test t
) t
where val <> 0;

Related

Count number of records returned by temp table - SQL Server

My script is as below
CREATE TABLE #t (Id int, Name varchar(10))
INSERT INTO #t VALUES (1, 'A')
INSERT INTO #t VALUES (1, 'B')
INSERT INTO #t VALUES (1, 'C')
INSERT INTO #t VALUES (1, 'D')
INSERT INTO #t VALUES (2, 'E')
SELECT COUNT(0)FROM (SELECT COUNT(0) FROM #t GROUP BY Id) a
but I am getting an error
Msg 8155, Level 16, State 2, Line 5
No column name was specified for column 1 of 'A'.
When you use a subquery, all the columns need to given names:
SELECT COUNT(0)
FROM (SELECT COUNT(0) as cnt FROM #t GROUP BY Id
) a;
However, a simpler way to write this is:
SELECT COUNT(DISTINCT id)
FROM #t;
Actually, this isn't exactly the same. Your version will count NULL values but this does not. The exact equivalent is:
SELECT COUNT(DISTINCT id) + MAX(CASE WHEN id IS NULL THEN 1 ELSE 0 END)
FROM #t;

Get records with more than one value and at least one of them is zero

Create table #Tbl
(
ID int not null,
Keyword nvarchar(max)
)
Insert into #Tbl Values ('0','Cryptography')
Insert into #Tbl Values ('1','Cryptography')
Insert into #Tbl Values ('4','Cryptography')
Insert into #Tbl Values ('0','SQL')
Insert into #Tbl Values ('0','SQL')
Insert into #Tbl Values ('3','Cloud Computing')
Insert into #Tbl Values ('6','Recursion')
Insert into #Tbl Values ('8','Recursion')
Insert into #Tbl Values ('0','Universe')
Insert into #Tbl Values ('0','Universe')
Insert into #Tbl Values ('7','Universe')
I need to get the titles which has more than one ID and at least one of the ID is zero.
So the expected result will be:
Cryptography
Universe
I tried below query but not able to add "at least one id is zero" condition
select Keyword,COUNT(distinct id) from #Tbl
group by Keyword
having COUNT(distinct id)>1
How can I proceed here ? Thanks for your help.
Assuming your IDs start from 0, the below should work
select Keyword,COUNT(distinct id) from #Tbl
group by Keyword
having COUNT(distinct id)>1 and MIN(id) = 0
There are many ways to do this, one example:
SELECT DISTINCT Keyword
FROM #Tbl T
WHERE EXISTS (SELECT 1 FROM #Tbl WHERE Keyword = T.Keyword
AND ID = 0)
AND EXISTS (SELECT 1 FROM #Tbl WHERE Keyword = T.Keyword
AND ID != 0)
Here is a sqlfiddle with a demo.
This should do it:
SELECT Keyword
FROM #Tbl
WHERE Keyword IN (SELECT DISTINCT Keyword FROM #Tbl WHERE ID = 0)
GROUP BY Keyword
HAVING COUNT(DISTINCT id) > 1
Here's yet another approach:
SELECT Keyword, COUNT(DISTINCT ID)
FROM #Tbl
GROUP BY Keyword
HAVING COUNT(DISTINCT ID) > ALL (SELECT COUNT(DISTINCT NULLIF(ID, 0)) UNION ALL SELECT 1)
;

Comparing value to that of previous row depending on different field

I have SQL which outputs rows of a date time stamp and a status change flag (0 or 1)
I need to get the timespan from the first record, where the flag will be 0, to when the status flag changes to 1, ignore records when the flag is still at 1, then get the time span after it changes back to 0 till the last record. The status change flag may flip between 0 and 1 any number of times.
So i need to be able to compare the status change flag to the previous row, and decide whether I need to keep accumulating the difference in the date time stamps.
I have been looking into writing a cursor but keep reading about how cursors are horribly inefficient.
Hopes this make any sense.
DECLARE #test TABLE ([group] int,t DateTime,[status] bit)
INSERT INTO #test values (1,'20130101 11:11:11',0)
INSERT INTO #test values (1,'20130101 11:11:12',0)
INSERT INTO #test values (1,'20130101 11:11:13',0)
INSERT INTO #test values (1,'20130101 11:11:14',1)
INSERT INTO #test values (1,'20130101 11:11:15',1)
INSERT INTO #test values (1,'20130101 11:11:16',1)
INSERT INTO #test values (1,'20130101 11:11:17',0)
INSERT INTO #test values (1,'20130101 11:11:18',0)
INSERT INTO #test values (1,'20130101 11:11:19',0)
Select [Group],MIN(t)
,(Select MAX(t) from #test t2 where [status]=0 and t2.[group]=t.[group] and Exists(Select * from #test t3 where [status]=1 and t3.[group]=t.[group] and t3.t<t2.t))
,DateDiff(ss,MIN(t)
,(Select MAX(t) from #test t2 where [status]=0 and t2.[group]=t.[group] and Exists(Select * from #test t3 where [status]=1 and t3.[group]=t.[group] and t3.t<t2.t))
) as Seconds
from #test t where Status=0
group by [group]
I think something like this will work. But I might need more info on the table structure
WITH FirstFlag(FlagType, FlagTime)
AS
(
SELECT
FlagType
, min(DateCreated) as FlagTime
FROM TheTable
WHERE Flag = 0
)
, SecondFlag(FlagTime1, FlagTime2)
AS
(
SELECT
F.FlagTime as FlagTime
, min(T.DateCreated) as FlagTime
FROM TheTable as T
INNER JOIN FirstFlag as F
ON T.FlagType = F.FlagType
WHERE Flag = 1
AND T.DateCreated > F.FlagTime
)
SELECT datediff(min, FlagTime1, FlagTime2)
FROM SecondFlag

Order guarantee for identity assignment in multi-row insert in SQL Server

When using a Table Value Constructor (http://msdn.microsoft.com/en-us/library/dd776382(v=sql.100).aspx) to insert multiple rows, is the order of any identity column populated guaranteed to match the rows in the TVC?
E.g.
CREATE TABLE A (a int identity(1, 1), b int)
INSERT INTO A(b) VALUES (1), (2)
Are the values of a guaranteed by the engine to be assigned in the same order as b, i.e. in this case so they match a=1, b=1 and a=2, b=2.
Piggybacking on my comment above, and knowing that the behavior of an insert / select+order by will guarantee generation of identity order (#4: from this blog)
You can use the table value constructor in the following fashion to accomplish your goal (not sure if this satisfies your other constraints) assuming you wanted your identity generation to be based on category id.
insert into thetable(CategoryId, CategoryName)
select *
from
(values
(101, 'Bikes'),
(103, 'Clothes'),
(102, 'Accessories')
) AS Category(CategoryID, CategoryName)
order by CategoryId
It depends as long as your inserting the records in one shot . For example after inserting if you delete the record where a=2 and then again re insert the value b=2 ,then identity column's value will be the max(a)+1
To demonstrate
DECLARE #Sample TABLE
(a int identity(1, 1), b int)
Insert into #Sample values (1),(2)
a b
1 1
2 2
Delete from #Sample where a=2
Insert into #Sample values (2)
Select * from #Sample
a b
1 1
3 2

Select records with order of IN clause

I have
SELECT * FROM Table1 WHERE Col1 IN(4,2,6)
I want to select and return the records with the specified order which i indicate in the IN clause
(first display record with Col1=4, Col1=2, ...)
I can use
SELECT * FROM Table1 WHERE Col1 = 4
UNION ALL
SELECT * FROM Table1 WHERE Col1 = 6 , .....
but I don't want to use that, cause I want to use it as a stored procedure and not auto generated.
I know it's a bit late but the best way would be
SELECT *
FROM Table1
WHERE Col1 IN( 4, 2, 6 )
ORDER BY CHARINDEX(CAST(Col1 AS VARCHAR), '4,2,67')
Or
SELECT CHARINDEX(CAST(Col1 AS VARCHAR), '4,2,67')s_order,
*
FROM Table1
WHERE Col1 IN( 4, 2, 6 )
ORDER BY s_order
You have a couple of options. Simplest may be to put the IN parameters (they are parameters, right) in a separate table in the order you receive them, and ORDER BY that table.
The solution is along this line:
SELECT * FROM Table1
WHERE Col1 IN(4,2,6)
ORDER BY
CASE Col1
WHEN 4 THEN 1
WHEN 2 THEN 2
WHEN 6 THEN 3
END
select top 0 0 'in', 0 'order' into #i
insert into #i values(4,1)
insert into #i values(2,2)
insert into #i values(6,3)
select t.* from Table1 t inner join #i i on t.[in]=t.[col1] order by i.[order]
Replace the IN values with a table, including a column for sort order to used in the query (and be sure to expose the sort order to the calling application):
WITH OtherTable (Col1, sort_seq)
AS
(
SELECT Col1, sort_seq
FROM (
VALUES (4, 1),
(2, 2),
(6, 3)
) AS OtherTable (Col1, sort_seq)
)
SELECT T1.Col1, O1.sort_seq
FROM Table1 AS T1
INNER JOIN OtherTable AS O1
ON T1.Col1 = O1.Col1
ORDER
BY sort_seq;
In your stored proc, rather than a CTE, split the values into table (a scratch base table, temp table, function that returns a table, etc) with the sort column populated as appropriate.
I have found another solution. It's similar to the answer from onedaywhen, but it's a little shorter.
SELECT sort.n, Table1.Col1
FROM (VALUES (4), (2), (6)) AS sort(n)
JOIN Table1
ON Table1.Col1 = sort.n
I am thinking about this problem two different ways because I can't decide if this is a programming problem or a data architecture problem. Check out the code below incorporating "famous" TV animals. Let's say that we are tracking dolphins, horses, bears, dogs and orangutans. We want to return only the horses, bears, and dogs in our query and we want bears to sort ahead of horses to sort ahead of dogs. I have a personal preference to look at this as an architecture problem, but can wrap my head around looking at it as a programming problem. Let me know if you have questions.
CREATE TABLE #AnimalType (
AnimalTypeId INT NOT NULL PRIMARY KEY
, AnimalType VARCHAR(50) NOT NULL
, SortOrder INT NOT NULL)
INSERT INTO #AnimalType VALUES (1,'Dolphin',5)
INSERT INTO #AnimalType VALUES (2,'Horse',2)
INSERT INTO #AnimalType VALUES (3,'Bear',1)
INSERT INTO #AnimalType VALUES (4,'Dog',4)
INSERT INTO #AnimalType VALUES (5,'Orangutan',3)
CREATE TABLE #Actor (
ActorId INT NOT NULL PRIMARY KEY
, ActorName VARCHAR(50) NOT NULL
, AnimalTypeId INT NOT NULL)
INSERT INTO #Actor VALUES (1,'Benji',4)
INSERT INTO #Actor VALUES (2,'Lassie',4)
INSERT INTO #Actor VALUES (3,'Rin Tin Tin',4)
INSERT INTO #Actor VALUES (4,'Gentle Ben',3)
INSERT INTO #Actor VALUES (5,'Trigger',2)
INSERT INTO #Actor VALUES (6,'Flipper',1)
INSERT INTO #Actor VALUES (7,'CJ',5)
INSERT INTO #Actor VALUES (8,'Mr. Ed',2)
INSERT INTO #Actor VALUES (9,'Tiger',4)
/* If you believe this is a programming problem then this code works */
SELECT *
FROM #Actor a
WHERE a.AnimalTypeId IN (2,3,4)
ORDER BY case when a.AnimalTypeId = 3 then 1
when a.AnimalTypeId = 2 then 2
when a.AnimalTypeId = 4 then 3 end
/* If you believe that this is a data architecture problem then this code works */
SELECT *
FROM #Actor a
JOIN #AnimalType at ON a.AnimalTypeId = at.AnimalTypeId
WHERE a.AnimalTypeId IN (2,3,4)
ORDER BY at.SortOrder
DROP TABLE #Actor
DROP TABLE #AnimalType
ORDER BY CHARINDEX(','+convert(varchar,status)+',' ,
',rejected,active,submitted,approved,')
Just put a comma before and after a string in which you are finding the substring index or you can say that second parameter.
And first parameter of CHARINDEX is also surrounded by , (comma).