Constructing single select statement that returns order depends on the value of a column in SQL Server - sql

Table1
Id bigint primary key identity(1,1)
Status nvarchar(20)
Insert dummy data
Insert into Table1 values ('Open') --1
Insert into Table1 values ('Open') --2
Insert into Table1 values ('Grabbed') --3
Insert into Table1 values ('Closed') --4
Insert into Table1 values ('Closed') --5
Insert into Table1 values ('Open') --6
How would I construct a single select statement which orders the data where records with 'Grabbed' status is first, followed by 'Closed', followed by 'Open' in SQL Server
Output:
Id Status
3 Grabbed
4 Closed
5 Closed
1 Open
2 Open
6 Open

I think you need something like this:
select *
from yourTable
order by case when Status = 'Grabbed' then 1
when Status = 'Closed' then 2
when Status = 'Open' then 3
else 4 end
, Id;
[SQL Fiddle Demo]
Another way is to using CTE like this:
;with cte as (
select 'Grabbed' [Status], 1 [order]
union all select 'Closed', 2
union all select 'Open', 3
)
select t.*
from yourTable t
left join cte
on t.[Status] = cte.[Status]
order by cte.[order], Id;
[SQL Fiddle Demo]

This could be done much better with a properly normalized design:
Do not store your Status as a textual content. Just imagine a typo (a row with Grabed)...
Further more a lookup table allows you to add side data, e.g. a sort order.
CREATE TABLE StatusLookUp(StatusID INT IDENTITY PRIMARY KEY /*you should name your constraints!*/
,StatusName VARCHAR(100) NOT NULL
,SortRank INT NOT NULL)
INSERT INTO StatusLookUp VALUES
('Open',99) --ID=1
,('Closed',50)--ID=2
,('Grabbed',10)--ID=3
CREATE TABLE Table1(Id bigint primary key identity(1,1) /*you should name your constraints!*/
,StatusID INT FOREIGN KEY REFERENCES StatusLookUp(StatusID));
Insert into Table1 values (1) --1
Insert into Table1 values (1) --2
Insert into Table1 values (3) --3
Insert into Table1 values (2) --4
Insert into Table1 values (2) --5
Insert into Table1 values (1) --6
SELECT *
FROM Table1 AS t1
INNER JOIN StatusLookUp AS s ON t1.StatusID=s.StatusID
ORDER BY s.SortRank;

I find that the simplest method uses a string:
order by charindex(status, 'Grabbed,Closed,Open')
or:
order by charindex(',' + status + ',', ',Grabbed,Closed,Open,')
If you are going to put values in the query, I think the easiest way uses values():
select t1.*
from t1 left join
(values ('Grabbed', 1), ('Closed', 2), ('Open', 3)) v(status, priority)
on t1.status = v.status
order by coalesce(v.priority, 4);
Finally. This need suggests that you should have a reference table for statuses. Rather than putting the string name in other tables, put an id. The reference table can have the priority as well as other information.

Try this:
select Id,status from tablename where status='Grabbed'
union
select Id,status from tablename where status='Closed'
union
select Id,status from tablename where status='Open'

Related

Looping over the insert data into multiple tables

I have a query where I need to run to do manual inserts
I can do it but there are many records and was looking if I can build something.
I have a structure somewhat like this:
Have 4 id of a table - primary key values as:
var ids = "1,2,3,4";
loop over ids {
insert into table1(col1,col2,col3)
select col1,newid(),getdate() from table1 where id = ids - 1 at a time
var selectedID = get the id of the inserted row and then insert into anotehr table as:
insert into table2(col1,col2,col3,col4)
select selectedID, getdate(),getdate(),4 from table2 where fkID = ids - one at a time
}
You can use both loops and cursors but often they can be avoided.
Is there a specific reason you note you want them inserted one at a time? An alternative would be to have the IDs staged, in a temp table, or CTE, e.g.
;WITH [Ids] AS
(
SELECT '1' AS [ID]
UNION
SELECT '2'
UNION
SELECT '3'
UNION
SELECT '4'
)
INSERT INTO [Table1]
(
[Col1],
[Col2],
[Col3]
)
SELECT [Col1],
NEWID(),
GETDATE()
FROM [Table1] T
INNER JOIN [Ids] I ON I.[ID] = T.[Id];
Which avoids the need for any loops, and should perform much better.
Edit
The way I would structure this, to make the query reusable would be as follows:
IF OBJECT_ID('tempdb..#IDS') IS NOT NULL
BEGIN
DROP TABLE #IDS
END
IF OBJECT_ID('tempdb..#Inserted_IDS') IS NOT NULL
BEGIN
DROP TABLE #Inserted_IDS
END
CREATE TABLE #IDS
(
ID INT
);
CREATE TABLE #Inserted_IDS
(
ID INT,
);
INSERT INTO #IDS
(
ID
)
SELECT 1 UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4;
INSERT INTO [Table1]
(
[Col1],
[Col2],
[Col3]
)
OUTPUT Inserted.ID
INTO #Inserted_IDS
SELECT [Col1],
NEWID(),
GETDATE()
FROM [Table1] T
INNER JOIN #IDS I ON I.[ID] = T.[Id];
INSERT INTO [table2]
(
[col1],
[col2],
[col3],
[col4]
)
SELECT I.[ID],
getdate(),
getdate(),
4
FROM [#Inserted_IDS] I
DROP TABLE #IDS;
DROP TABLE #Inserted_IDS;
Therefore you only need to amend the IDs being entered into the temp table each time you need to do the inserts.

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;

Deleting records that are similar with previous one SQL Server

I am looking for a query which fetches me the data that is different compared to the previous row,
A sample code (with table creation and data)
create table #temp
(id int, eid int, name char(10),estid int, ecid int, epid int, etc char(5) )
insert into #temp values (1,1,'a',1,1,1,'a')
insert into #temp values (2,1,'a',1,1,1,'a')
insert into #temp values (3,1,'a',2,1,1,'a')
insert into #temp values (4,1,'a',1,1,1,'a')
insert into #temp values (5,1,'a',1,1,1,'a')
insert into #temp values (6,1,'a',1,2,1,'a')
insert into #temp values (7,1,'a',1,1,1,'a')
insert into #temp values (8,1,'a',2,1,1,'a')
insert into #temp values (9,1,'a',1,1,1,'a')
insert into #temp values (10,1,'a',1,1,1,'a')
insert into #temp values (11,2,'a',1,1,1,'a')
insert into #temp values (12,2,'a',1,1,1,'a')
insert into #temp values (13,2,'a',2,1,1,'a')
insert into #temp values (14,2,'a',1,1,1,'a')
insert into #temp values (15,2,'a',1,1,1,'a')
insert into #temp values (16,2,'a',1,2,1,'a')
insert into #temp values (17,2,'a',1,1,1,'a')
insert into #temp values (18,2,'a',2,1,1,'a')
insert into #temp values (19,2,'a',1,1,1,'a')
insert into #temp values (20,2,'a',1,1,1,'a')
I tried with some ways of getting the data as the way that i expected
SELECT * INTo #Temp_Final
FROM #temp
WHERE #temp.%%physloc%%
NOT IN (SELECT Min(b.%%physloc%%)
FROM #temp b
GROUP BY eid,name,estid,ecid,epid,etc)
ORDER BY id
SELECT * FROM #temp WHERE id not in (SELECT id FROM #Temp_Final) ORDER BY id
But i wasn't getting the result as i expected...
This is how the result needs to be
select * from #temp where id in (1,3,4,6,7,8,9,11,13,14,16,17,18,19)
You can do this with a simple self-join and appropriate comparison:
select t.*
from #temp t left outer join
#temp tprev
on t.id = tprev.id + 1
where tprev.id is null or
t.name <> tprev.name or
t.estid <> tprev.estid or
t.ecid <> tprev.ecid or
t.epid <> tprev.epid or
t.etc <> tprev.etc;
This assumes that the ids are sequential with no gaps. If the ids are not, you can get the previous id using a correlated subquery or the lag() function.
Your title says "delete" but the question seems to just want the list of such rows. You can phrase this as a delete query if you need to.
For SQL Server 2012 (SQL Fiddle)
WITH CTE
AS (SELECT *,
LAG(eid) OVER (ORDER BY id) AS prev_eid,
LAG(name) OVER (ORDER BY id) AS prev_name,
LAG(estid) OVER (ORDER BY id) AS prev_estid,
LAG(ecid) OVER (ORDER BY id) AS prev_ecid,
LAG(epid) OVER (ORDER BY id) AS prev_epid,
LAG(etc) OVER (ORDER BY id) AS prev_etc
FROM #temp)
DELETE FROM CTE
WHERE EXISTS (SELECT eid,
name,
estid,
ecid,
epid,
etc
INTERSECT
SELECT prev_eid,
prev_name,
prev_estid,
prev_ecid,
prev_epid,
prev_etc)
select
t.id,
t.eid,
t.name,
t.estid,
t.ecid,
t.epid,
t.etc
from #temp t
left join #temp d
on d.id = t.id-1
and d.eid = t.eid
and d.name = t.name
and d.estid = t.estid
and d.ecid = t.ecid
and d.epid = t.epid
and d.etc = t.etc
where d.id is null

SQL Command to get all rows from a specific set of groups

Let say I have the following table (The ID is self incremental)
ID Name Serial Status
0 Pie A Fail
1 Pie A Fail
2 Pie A Pass
3 Pie B Fail
4 Pie B Pass
5 Pie C Pass
6 Pie C Fail
How can I get all the rows where the last row of each Group By (Name, Serial) is Pass?
This is the result I should get from the query. The serial C is removed since the last entry of the group by (Name, Serial) is 'Fail'
ID Name Serial Status
0 Pie A Fail
1 Pie A Fail
2 Pie A Pass
3 Pie B Fail
4 Pie B Pass
Thanks!
I would try something like this (assuming SQL Server):
DECLARE #myTable AS TABLE(
ID INT,
Name VARCHAR(10),
Serial VARCHAR(1),
[Status] VARCHAR(10))
INSERT INTO #myTable VALUES(0, 'Pie', 'A', 'Fail')
INSERT INTO #myTable VALUES(1, 'Pie', 'A', 'Fail')
INSERT INTO #myTable VALUES(2, 'Pie', 'A', 'Pass')
INSERT INTO #myTable VALUES(3, 'Pie', 'B', 'Fail')
INSERT INTO #myTable VALUES(4, 'Pie', 'B', 'Pass')
INSERT INTO #myTable VALUES(5, 'Pie', 'C', 'Pass')
INSERT INTO #myTable VALUES(6, 'Pie', 'C', 'Fail')
SELECT *
FROM #myTable
WHERE Serial NOT IN
(
--Get all Serial that end with a 'Fail'
SELECT T1.Serial
FROM #myTable T1
JOIN (
--Get Max ID for a serial
SELECT MAX(ID) as [ID] FROM #myTable GROUP BY Serial
) T2 ON T1.[ID] = T2.[ID]
WHERE T1.[Status] = 'Fail'
)
ORDER BY [ID]
or if you prefer NOT EXISTS (which is usually faster than NOT IN):
SELECT *
FROM #myTable T
WHERE NOT EXISTS
(
SELECT
T1.Serial
FROM #myTable T1
JOIN (
--Get Max ID for a serial
SELECT MAX(ID) as [ID] FROM #myTable GROUP BY Serial
) T2 ON T1.[ID] = T2.[ID]
WHERE
T1.[Status] = 'Fail'
AND T1.[Serial] = T.[Serial]
)
ORDER BY [ID]
We can use CTE to improve readability by implementing as a series of sequential steps:
Get max ids
Get serials for max ids that have status 'Fail'
Remove those rows that match the serials
It would look like this:
with maxIds as ( --Get max Ids
SELECT MAX(ID) as [ID] FROM myTable GROUP BY Serial
),
serials as ( -- Get serials for max ids that have status 'Fail'
SELECT T1.Serial FROM myTable T1 JOIN maxIds ON T1.[ID] = maxIds.[ID] WHERE [Status] = 'Fail'
)
select * from myTable where serial not in (select * from serials) -- Remove serials that match

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).