optimizing SQL query with multiple keys - sql

I have a table in a database with a primary key, and a 'second' key as well. This second key can have the same value occur more than once in the table, but often i only want to return the most recent row for that second key. I have an existing query that works below, but I feel like it's very ugly and there should be a simpler way to do this instead of creating a table variable, going through a loop, and inserting 1 row into the table variable on each pass through the loop. Am i making this too hard?
declare #RowCnt int
declare #MaxRows int
declare #secondID as uniqueidentifier
DECLARE #retList TABLE(
firstGUID uniqueidentifier,
secondGUID uniqueidentifier,
name nvarchar(50),
DateCreated datetime
)
select #RowCnt = 1
declare #Import table (rownum int IDENTITY (1, 1) Primary key NOT NULL , secondGUID uniqueidentifier)
insert into #Import (secondGUID) SELECT DISTINCT dbo.TestTable.secondGUID FROM dbo.TestTable
select #MaxRows=count(*) from #Import
while #RowCnt <= #MaxRows
begin
select #secondID=secondGUID from #Import where rownum = #RowCnt
INSERT INTO #retList
SELECT TOP (1) firstGUID,secondGUID,name,datecreated
FROM dbo.TestTable
WHERE dbo.TestTable.secondGUID = #secondID
ORDER BY DateCreated Desc
Set #RowCnt = #RowCnt + 1
END
select * from #retList
EDIT:
for example, imagine the table has these values
firstGUID secondGUID Name DateCreated
EAD50999-E9B1-43F0-9FA6-615405FA5A9A 6163B6ED-6AF4-494E-ACE6-184F4804847B Test1 2014-04-11 15:12:36.303
A9645486-1021-4E98-92AC-1205CC3FB9D3 6163B6ED-6AF4-494E-ACE6-184F4804847B Test2 2014-04-10 15:21:46.087
DEE375BB-BFAF-44BE-AC64-06D7702E2ACB 3BD0A2F0-4E44-43B9-BD24-003B518609C7 Test3
2014-04-11 15:22:37.097
I only want the Test1 and Test3 rows to be returned.

You could use SQLServer's analytical functions:
select firstGUID, secondGUID, name, datecreated
from (select t.*,
rank() over (partition by secondGUID order by datecreated desc) r
from TestTable t) ilv
where r=1

I'm not 100% sure I understand what you're asking, but it sounds like you want to select only the rows containing the max DateCreated. The normal way of doing that is to join with a subselect that uses a group by clause, eg.:
select tt.*
from TestTable tt
join (
select firstguid, max(DateCreated) as maxdate
from TestTable
group by firstguid
) gtmp on tt.firstguid = gtmp.firstguid and tt.dateCreated = gtmp.maxdate

Related

How do I limit the number of ROWS being returned in my SQL Server Function?

CREATE FUNCTION [dbo].[GET_CUSTOMER_DATA]
(
-- Add the parameters for the function here
#customerID bigint,
#maxRows int,
#offset int,
#rows int
)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
SELECT *
FROM SS_CustomerCard AS SS_CC
INNER JOIN SS_PersonalRepresentative AS SS_PR
ON SS_PR.customerID = SS_CC.ID
INNER JOIN SS_ApplicationStatus AS SS_AS
ON SS_AS.CustomerID = SS_CC.ID
WHERE
SS_CC.ID='#customerID'
ORDER BY SS_AS.EventDateTime DESC, SS_CC.FirstName DESC, SS_CC.LastName DESC
OFFSET #offset ROWS
FETCH NEXT #rows ROWS ONLY
)
GO
I want to pass a parameter to limit the number of rows being returned (maxRows)
You can use the TOP() clause in the SELECT statement.
Here is a conceptual example.
SQL
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, vehicleMake VARCHAR(20));
INSERT INTO #tbl (vehicleMake) VALUES
('Chevrolet'),
('Tesla'),
('Audi'),
('Nissan');
DECLARE #tableVariable TABLE (ID INT, vehicleMake VARCHAR(20));
DECLARE #topRows INT = 2;
INSERT INTO #tableVariable
SELECT *
FROM #tbl
ORDER BY id;
SELECT TOP(#topRows) *
FROM #tableVariable
ORDER BY id;
I think you are trying to limit the total number of rows BEFORE the OFFSET/FETCH. If so, you need to put it in a subquery/derived table, and add another ORDER BY:
CREATE FUNCTION [dbo].[GET_CUSTOMER_DATA]
(
-- Add the parameters for the function here
#customerID bigint,
#maxRows int,
#offset int,
#rows int
)
RETURNS TABLE
AS
RETURN
(
SELECT *
FROM (
SELECT TOP (#maxRows) all_needed_columns_here_aliased_if_necessary
FROM SS_CustomerCard AS SS_CC
INNER JOIN SS_PersonalRepresentative AS SS_PR
ON SS_PR.customerID = SS_CC.ID
INNER JOIN SS_ApplicationStatus AS SS_AS
ON SS_AS.CustomerID = SS_CC.ID
WHERE
SS_CC.ID=#customerID
ORDER BY SS_AS.EventDateTime DESC, SS_CC.FirstName DESC, SS_CC.LastName DESC
) t
ORDER BY EventDateTime DESC, FirstName DESC, LastName DESC
OFFSET #offset ROWS
FETCH NEXT #rows ROWS ONLY
)
GO

How to get the each record with some condition

I have following data:
DECLARE #temp TABLE (
ID int
,sn varchar(200)
,comment varchar(2000)
,rownumber int
)
insert into #temp values(1,'sn1',NULL,1)
insert into #temp values(2,'sn1','aaa',2)
insert into #temp values(3,'sn1','bbb',3)
insert into #temp values(4,'sn1',NULL,4)
insert into #temp values(5,'sn2',NULL,1)
insert into #temp values(6,'sn2',NULL,2)
insert into #temp values(7,'sn2',NULL,3)
select * from #temp
And I want to output like this:
2 sn1 aaa 2
5 sn2 NULL 1
same sn, if comment have value, get this lower rownumber's record. For sn1, have two records with comment value, so here, get the the record with rownumber=2
If comment doesn't have value, get the lower rownumber's record. For sn2, get the record with rownumber=1
May I know how to write this SQL?
This is a prioritization query. I think row_number() is the simplest method:
select t.*
from (select t.*,
row_number() over (partition by sn
order by (case when comment is not null then 1 else 2 end),
rownumber
) as seqnum
from #temp t
) t
where seqnum = 1;
Here is a db<>fiddle.

How I prevent the bottom Order By from effecting everything in #Result?

I need to prevent the Order By SettingID at the bottom of this SQL from re-ordering everything. I want to Order my first select by UserID and my second select by OrderID. How do put a bound on the bottom Order By? I tried parentheses but I'm a T-SQL beginner so couldn't get that figured out. I tried using #ResultA and #ResultB and then Select them into #Result but the end result is the same the bottom Order By applies to the entire #Result. Would temp tables be the way to go and if so how?
DECLARE #Result TABLE (SettingID INT, GroupID INT, UserID INT)
INSERT INTO #Result
SELECT
SettingID,
GroupID,
UserID
FROM Table1
WHERE (GroupID = #GroupID AND UserID = #UserID)
ORDER BY UserID
INSERT INTO #Result
SELECT
SettingID,
GroupID,
UserID
FROM Tabel1
WHERE (GroupID IS NULL)
ORDER BY SettingID DESC -- This Order By is reordering everything in #Result.
SELECT * FROM #Result
You should not depend on the ordering of rows in a table, even table variables. In SQL, tables represent unordered sets. You can do what with a single query:
SELECT SettingID, GroupID, UserID
FROM Table1
ORDER BY (CASE WHEN (GroupID = #GroupID AND UserID = #UserID) THEN 1 ELSE 0 END) DESC,
GroupID
You can put this into a table variable:
INSERT INTO #Result
SELECT SettingID, GroupID, UserID
FROM Table1
ORDER BY (CASE WHEN (GroupID = #GroupID AND UserID = #UserID) THEN 1 ELSE 0 END) DESC,
GroupID;
However, you cannot assume that:
SELECT *
FROM #Result
will be in any particular order, such as insert order.
There is a trick if you want to do that. Create an identity column in the temporary table and order by that:
DECLARE #Result TABLE (
ResultId INT IDENTITY(1, 1) PRIMARY KEY,
SettingID INT,
GroupID INT,
UserID INT
);
INSERT INTO #Result(SettingID, GroupID, UserID)
SELECT SettingID, GroupID, UserID
FROM Table1
ORDER BY (CASE WHEN (GroupID = #GroupID AND UserID = #UserID) THEN 1 ELSE 0 END) DESC,
GroupID;
SELECT *
FROM #Result
ORDER BY ResultId;
Use commas to add ORDER BY layers:
SELECT * FROM #Result ORDER BY UserID, OrderID
You can and should remove the order by in the queries that populate your table variable.
Tables don't have a concept of record order as you perceive it. It just doesn't exist.
If you need to guarantee that results are returned in a specific order, you must use an ORDER BY on the select query that produces those results. With that in mind, you should probably just omit the ORDER BY clause on your INSERT statements, and only include it on the final select.
More than that, if controlling the order is the whole reason for the temp table, you should really just do this:
SELECT
SettingID,
GroupID,
UserID
FROM Table1
WHERE (GroupID = #GroupID AND UserID = #UserID) OR #GroupID IS NULL
ORDER BY CASE WHEN GroupID IS NOT NULL THEN 0 ELSE 1 END
, CASE WHEN GroupID IS NOT NULL THEN UserID ELSE SettingID END
ORDER BY
CASE WHEN (SettingID between x and y)
THEN UserID
else OrderID END

How can I order data and add a record to the first position of the data set?

I know I can create a temp table, insert records, order it and then use union afterwards, but I'm looking for alternative routes. I tried a cte, but I had to order the entire thing which doesn't work as my unioned record doesn't stay "on top".
Basically, I have at able with Id INT, Name VARCHAR(MAX) fields and I want to ORDER BY Name before I add an entry at the row[0] position in the return set. If I order after the union, the row I wanted at row[0] gets ordered with it.
Any ideas?
You were on the right track with a union query. Force the sort with static values.
select 0 sortfield, '' name, etc
union
select 1 sortfield, name, etc
from etc
order by sortfield, name.
CREATE TABLE #temp (
idnt INT IDENTITY(2) NOT NULL --This begins the identity col with a value of 2
,Id INT
,Name VARCHAR(MAX)
)
INSERT INTO #temp
SELECT
...
FROM myTable
ORDER BY Name
CREATE TABLE #tempAPPEND (
idnt INT IDENTITY(1) NOT NULL --This begins the identity col with a value of 1
,Id INT
,Name VARCHAR(MAX)
)
INSERT INTO #tempAPPEND (Id, Name)
VALUES ('34384','Pinal Dave') -- SAMPLE VALUES
SELECT * FROM #temp
UNION
SELECT * FROM #tempAPPEND
ORDER BY idnt

SQL Server 2012 Sequence Object

Here is my sample table, the primary key is a composite key of Akey+Bkey
Akey Bkey ItemSequence
---- ---- ------------
1 1 1
1 5 2
1 7 3
2 7 1
3 2 1
3 3 2
Akey is generated from a SQL 2012 Sequence object ASequence. In most cases I insert one row at a time and when necessary I call NEXT VALUE FOR ASequence. However I need to do an insert from a statement like:
SELECT DENSE_RANK() OVER ( ORDER BY Something) as AKey,
Bkey, Sequence
FROM TABLEB
The OVER clause of the NEXT VALUE does not work this way as I need to be able to insert records as a SET but only increment the Sequence once per DENSE_RANK set.
So we have the ALTER SEQUENCE command and with this I am able to set the sequence to what I want. The caveat to this is that it must be a constant and will not accept a variable. My workaround to this was:
DECLARE #startingID INT
DECLARE #sql VARCHAR(MAX)
DECLARE #newSeed INT
SET #startingID = NEXT VALUE FOR ASequence
INSERT TABLEA
SELECT DENSE_RANK() OVER ( ORDER BY Something) + #startingID as AKey,
Bkey, Sequence
FROM TABLEB
SELECT #newSeed = MAX(Akey) FROM TABLEA
SET #sql = ‘ALTER SEQUENCE ASEQUENCE RESTART WITH ‘ + cast(#newSeed+1 as varchar(10))
EXEC(#sql)
Seems terrible to have DML statements in Dynamic SQL like this. Is there a better way to do this?
This should do it:
INSERT TABLEA
SELECT NEXT VALUE FOR ASequence OVER(ORDER BY Something) as AKey,
Bkey, Seq
FROM TABLEB
Or, how about this:
CREATE TABLEA
(
GroupID INT,
AKey INT,
BKey INT,
ItemSequence INT,
CONSTRAINT PK_TABLEA PRIMARY KEY CLUSTERED
(
GroupID,
AKey,
BKey
)
)
DECLARE #GroupID INT
SET #GroupID = NEXT VALUE FOR ASequence
INSERT TABLEA
SELECT #GroupID, DENSE_RANK() OVER ( ORDER BY Something) as AKey,
Bkey, Sequence
FROM TABLEB
and if you need the value of AKey as it is in your example, you can do GroupID+AKey here.