TSQL Select Max - tsql

Userid FirstName LastName UserUpdate
1 Dan Kramer 1/1/2005
1 Dan Kramer 1/1/2007
1 Dan Kramer 1/1/2009
2 Pamella Slattery 1/1/2005
2 Pam Slattery 1/1/2006
2 Pam Slattery 1/1/2008
3 Samamantha Cohen 1/1/2008
3 Sam Cohen 1/1/2009
I need to extract the latest updated for all these users, basically here's what I'm looking for:
Userid FirstName LastName UserUpdate
1 Dan Kramer 1/1/2009
2 Pam Slattery 1/1/2008
3 Sam Cohen 1/1/2009
Now when I run the following:
SELECT Userid, FirstName, LastName, Max(UserUpdate) AS MaxDate
FROM Table
GROUP BY Userid, FirstName, LastName
I still get duplicates, something like this:
Userid FirstName LastName UserUpdate
1 Dan Kramer 1/1/2009
2 Pamella Slattery 1/1/2005
2 Pam Slattery 1/1/2008
3 Samamantha Cohen 1/1/2008
3 Sam Cohen 1/1/2009

try:
declare #Table table (userid int,firstname varchar(10),lastname varchar(20), userupdate datetime)
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2005')
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2007')
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2009')
INSERT #Table VALUES (2, 'Pamella' ,'Slattery' ,'1/1/2005')
INSERT #Table VALUES (2, 'Pam' ,'Slattery' ,'1/1/2006')
INSERT #Table VALUES (2, 'Pam' ,'Slattery' ,'1/1/2008')
INSERT #Table VALUES (3, 'Samamantha' ,'Cohen' ,'1/1/2008')
INSERT #Table VALUES (3, 'Sam' ,'Cohen' ,'1/1/2009')
SELECT
dt.Userid,dt.MaxDate
,MIN(a.FirstName) AS FirstName, MIN(a.LastName) AS LastName
FROM (SELECT
Userid, Max(UserUpdate) AS MaxDate
FROM #Table GROUP BY Userid
) dt
INNER JOIN #Table a ON dt.Userid=a.Userid and dt.MaxDate =a.UserUpdate
GROUP BY dt.Userid,dt.MaxDate
OUTPUT:
Userid MaxDate FirstName LastName
----------- ----------------------- ---------- --------------------
1 2009-01-01 00:00:00.000 Dan Kramer
2 2008-01-01 00:00:00.000 Pam Slattery
3 2009-01-01 00:00:00.000 Sam Cohen

You aren't getting duplicates. 'Pam' is not equal to 'Pamella' from the perspective of the database; the fact that one is a colloquial shortening of the other doesn't mean anything to the database engine. There really is no reliable, universal way to do this (since there are names that have multiple abbreviations, like "Rob" or "Bob" for "Robert", as well as abbreviations that can suit multiple names like "Kel" for "Kelly" or "Kelsie", let alone the fact that names can have alternate spellings).
For your simple example, you could simply select and group by SUBSTRING(FirstName, 1, 3) instead of FirstName, but that's just a coincidence based upon your sample data; other name abbreviations would not fit this pattern.

Or use a subquery...
SELECT
a.userID,
a.FirstName,
a.LastName,
b.MaxDate
FROM
myTable a
INNER JOIN
( SELECT
UserID,
Max(ISNULL(UserUpdate,GETDATE())) as MaxDate
FROM
myTable
GROUP BY
UserID
) b
ON
a.UserID = b.UserID
AND a.UserUpdate = b.MaxDate
The subquery (named "b") returns the following:
Userid UserUpdate
1 1/1/2009
2 1/1/2008
3 1/1/2009
The INNER JOIN between the subquery and the original table causes the original table to be filtered for matching records only -- i.e., only records with a UserID/UserUpdate pair that matches a UserID/MaxDate pair from the subquery will be returned, giving you the unduplicated result set you were looking for:
Userid FirstName LastName UserUpdate
1 Dan Kramer 1/1/2009
2 Pam Slattery 1/1/2008
3 Sam Cohen 1/1/2009
Of course, this is just a work-around. If you really want to solve the problem for the long-term, you should normalize your original table by splitting it into two.
Table1:
Userid FirstName LastName
1 Dan Kramer
2 Pam Slattery
3 Sam Cohen
Table2:
Userid UserUpdate
1 1/1/2007
2 1/1/2007
3 1/1/2007
1 1/1/2008
2 1/1/2008
3 1/1/2008
1 1/1/2009
2 1/1/2009
3 1/1/2009
This would be a more standard way to store data, and would be much easier to query (without having to resort to a subquery). In that case, the query would look like this:
SELECT
T1.UserID,
T1.FirstName,
T1.LastName,
MAX(ISNULL(T2.UserUpdate,GETDATE()))
FROM
Table1 T1
LEFT JOIN
Table2 T2
ON
T1.UserID = T2.UserID
GROUP BY
T1.UserID,
T1.FirstName,
T1.LastName

Another alternative if you have SQL 2005(I think ?) or later would be to use a Common Table Expression and pull out the user id and max date from the table then join against that to get the matching firstname and lastname on the max date. NOTE - this assumes that userid + date would always be unique, the query will break if you get 2 rows with same userid and date. As others have already pointed out this is pretty awful database design - but sometimes thats life, the problem must still be solved. e.g.
declare #Table table (userid int,firstname varchar(10),lastname varchar(20), userupdate datetime)
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2005')
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2007')
INSERT #Table VALUES (1, 'Dan' ,'Kramer' ,'1/1/2009')
INSERT #Table VALUES (2, 'Pamella' ,'Slattery' ,'1/1/2005')
INSERT #Table VALUES (2, 'Pam' ,'Slattery' ,'1/1/2006')
INSERT #Table VALUES (2, 'Pam' ,'Slattery' ,'1/1/2008')
INSERT #Table VALUES (3, 'Samamantha' ,'Cohen' ,'1/1/2008')
INSERT #Table VALUES (3, 'Sam' ,'Cohen' ,'1/1/2009');
with cte ( userid , maxdt ) as
(select userid,
max(userupdate)
from #table
group by userid)
SELECT dt.Userid,
dt.firstname,
dt.lastname,
cte.maxdt
FROM
#Table dt
join cte on cte.userid = dt.userid and dt.userupdate = cte.maxdt
Output
Userid firstname lastname maxdt
----------- ---------- -------------------- -----------------------
3 Sam Cohen 2009-01-01 00:00:00.000
2 Pam Slattery 2008-01-01 00:00:00.000
1 Dan Kramer 2009-01-01 00:00:00.000

Related

tsql max and group by not working properly

I got the following table articles:
ID
category
price
1
category1
10
2
category1
55
3
category2
15
4
category3
20
5
category4
25
I would like to get the highest price of each category.
The result would be:
ID
category
price
2
category1
55
3
category2
15
4
category3
20
5
category4
25
select Max(price), ID, category from article
group by ID,category
returns:
ID
category
price
1
category1
10
2
category1
55
3
category2
15
4
category3
20
5
category4
25
Unfortunately I get both rows for category 1. But I only would like to have the highest price in category 1 which is 55.
Can someone help me?
see above
Try this...
I've reproduced your sample data and then added a rnk column which ranks by price descending witin each category, used this in the subquery and just returned anything where rank is 1.
DECLARE #articles TABLE (ID int, Category varchar(20), Price float)
INSERT INTO #articles VALUES
(1, 'category1', 10),
(2, 'category1', 55),
(3, 'category2', 15),
(4, 'category3', 20),
(5, 'category4', 25)
SELECT
ID, Category, Price
FROM (
SELECT
ID, Category, Price
, RANK() OVER(PARTITION BY Category ORDER BY Price DESC) as rnk
FROM #articles
) a
WHERE a.rnk = 1
Which gives these results
Bote If you have two articles for the same category with the same price, both will be returned.
--===== This is NOT a part of the solution.
-- We're just making "Readily Consumable Test Data" here.
-- This is how you should post sample data to help those
-- that would help you. You'll get more thumbs up on your
-- questions, as well
SELECT *
INTO #Articles
FROM (VALUES
(1, 'category1', 10)
,(2, 'category1', 55)
,(3, 'category2', 15)
,(4, 'category3', 20)
,(5, 'category4', 25)
)d(ID,category,price)
;
--===== One possible easy solution that will also display "ties".
WITH cteRankByCategory AS
(
SELECT *,DR = DENSE_RANK() OVER (PARTITION BY Category ORDER BY Category, Price DESC)
FROM #Articles
)
SELECT ID,Category,MaxPrice = Price
FROM cteRankByCategory
WHERE DR = 1
ORDER BY Category
;

Need to combine the sales of 2 records with different ID's and the record with highest sale id should be in the result set

I have a table with records belonging to same person but the person was assigned with 2 different id's.
I need to combine the sales and then hold on to the id having highest sales.
For Example:
ID Name Sales
1 ABC 10
4 ABC 60
5 xyz 100
6 xyz 10
I need result as
ID Name Sales
4 ABC 70
5 XYZ 110
Please help me with a sql query for the above.
try this:
create table #mytable
(id int,
name nvarchar(20),
Sales int)
insert into #mytable
values
(1,'ABC',10),
(4,'ABC',60),
(5,'xyz',100),
(6,'xyz',10)
select (select top(1) ID
from #mytable r2
where r2.name=r1.name
and r2.Sales=MAX(r1.Sales))as ID,
name,
sum(Sales)
from #mytable r1
group by name
drop table #mytable

How do I produce a report to show the number of occurrences an employee has been absent from work

I have been asked to generate a report to show the number of occurrences an employee is absent from work sick.
If an employee is absent from work for 3 consecutive days this will be counted as 1 occurrence. If they then return to work and are then absent again for another 2 consecutive days this will be recorded as 2 occurrences.
I need to generate a report to show the number of occurrences an employee is away from work sick within a 6 month period.
I have set out an example below of the data showing an employee's absence records and how i need the report to look.
How data shows in database:
enter image description here
Name Absence Dates
John Smith 01-Sep-19
John Smith 02-Sep-19
John Smith 03-Sep-19
John Smith 10-Sep-19
John Smith 11-Sep-19
How i wish for the report to look:
Name Occurrences
John Smith 2
I would be grateful for any assistance with writing to code to achieve this result.
Not a full answer, as you should really do some of this yourself, however, based on what you have detailed in your quesiton, you could use the approach below to count up any spells of absence, within a 6 month period.
Assumes you would be compiling this using SQL Server
declare #absences table (empid nvarchar(10), [abs date] date, [ret date] date);
declare #staff table ([empid] int, [name1] nvarchar(50), [name2] nvarchar(50), [surname] nvarchar(50));
-- put some test values in the staff table to work with
insert into #staff
values
(1, 'John', 'Lewis', 'Smith'), -- using a unique ID here, in any good system this should be an incremental number for each new staff member added to the table
(2, 'James', 'Thomas', 'Brown')
-- put some test values in the absences table to work with
insert into #absences
values
(1, '2019-07-01', '2019-07-04'), -- userid, absence date & return date
(1, '2019-08-04', '2019-08-06'),
(2, '2019-07-02', '2019-07-05'),
(2, '2019-08-05', '2019-08-07')
select count(*) spellsoff, empid, name1, name2, surname, [days absent]
from
(
select
s.empid,
s.name1,
s.name2,
s.surname,
a.[abs date],
a.[ret date],
datediff(d,a.[abs date], a.[ret date]) [days absent]
from #staff s
left join #absences a
on s.empid = a.empid
where [abs date] >= DATEADD(M,-6,GETDATE()) -- pull back those employeess that have been absent in the last 6 months from today's date
)doff
group by empid, name1, name2, surname, [days absent]
Gives you the following breakdown:
spellsoff empid name1 name2 surname days absent
1 1 John Lewis Smith 2
1 1 John Lewis Smith 3
1 2 James Thomas Brown 2
1 2 James Thomas Brown 3

TSQL, Pivot rows into single columns

Before, I had to solve something similar:
Here was my pivot and flatten for another solution:
I want to do the same thing on the example below but it is slightly different because there are no ranks.
In my previous example, the table looked like this:
LocationID Code Rank
1 123 1
1 124 2
1 138 3
2 999 1
2 888 2
2 938 3
And I was able to use this function to properly get my rows in a single column.
-- Check if tables exist, delete if they do so that you can start fresh.
IF OBJECT_ID('tempdb.dbo.#tbl_Location_Taxonomy_Pivot_Table', 'U') IS NOT NULL
DROP TABLE #tbl_Location_Taxonomy_Pivot_Table;
IF OBJECT_ID('tbl_Location_Taxonomy_NPPES_Flattened', 'U') IS NOT NULL
DROP TABLE tbl_Location_Taxonomy_NPPES_Flattened;
-- Pivot the original table so that you have
SELECT *
INTO #tbl_Location_Taxonomy_Pivot_Table
FROM [MOAD].[dbo].[tbl_Location_Taxonomy_NPPES] tax
PIVOT (MAX(tax.tbl_lkp_Taxonomy_Seq)
FOR tax.Taxonomy_Rank in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15])) AS pvt
-- ORDER BY Location_ID
-- Flatten the tables.
SELECT Location_ID
,max(piv.[1]) as Tax_Seq_1
,max(piv.[2]) as Tax_Seq_2
,max(piv.[3]) as Tax_Seq_3
,max(piv.[4]) as Tax_Seq_4
,max(piv.[5]) as Tax_Seq_5
,max(piv.[6]) as Tax_Seq_6
,max(piv.[7]) as Tax_Seq_7
,max(piv.[8]) as Tax_Seq_8
,max(piv.[9]) as Tax_Seq_9
,max(piv.[10]) as Tax_Seq_10
,max(piv.[11]) as Tax_Seq_11
,max(piv.[12]) as Tax_Seq_12
,max(piv.[13]) as Tax_Seq_13
,max(piv.[14]) as Tax_Seq_14
,max(piv.[15]) as Tax_Seq_15
-- JOIN HERE
INTO tbl_Location_Taxonomy_NPPES_Flattened
FROM #tbl_Location_Taxonomy_Pivot_Table piv
GROUP BY Location_ID
So, then here is the data I would like to work with in this example.
LocationID Foreign Key
2 2
2 670
2 2902
2 5389
3 3
3 722
3 2905
3 5561
So I have some data that is formatted like this:
I have used pivot on data like this before--But the difference was it had a rank also. Is there a way to get my foreign keys to show up in this format using a pivot?
locationID FK1 FK2 FK3 FK4
2 2 670 2902 5389
3 3 722 2905 5561
Another way I'm looking to solve this is like this:
Another way I could look at doing this is I have the values in:
this form as well:
LocationID Address_Seq
2 670, 5389, 2902, 2,
3 722, 5561, 2905, 3
etc
is there anyway I can get this to be the same?
ID Col1 Col2 Col3 Col4
2 670 5389, 2902, 2
This, adding a rank column and reversing the orders, should gives you what you require:
SELECT locationid, [4] col1, [3] col2, [2] col3, [1] col4
FROM
(
SELECT locationid, foreignkey,rank from #Pivot_Table ----- temp table with a rank column
) x
PIVOT (MAX(x.foreignkey)
FOR x.rank in ([4],[3],[2],[1]) ) pvt

TSQL passing 2 values array to stored procedure

I'm using SQL Server 2012 and C#.
Imagine have something similar to a shopping cart and now need to create an order with the following items:
productA - 4 (qty)
productB - 1 (qty)
productC - 9 (qty)
In my C# code I have a list that looks like this:
id : "productA" , qty : "4"
id : "productB" , qty : "1"
id : "productV" , qty : "9"
Questions:
How can I pass the list of 2 values to the stored procedure?
How can I have the stored procedure run 3 while loops each one running 4 times, then once then 9 times in order to physically create one record x request?
Note: In my case I don't have a QTY column in the table, I need to specifically create one record x item on the order.
You can done this by Table Value Parameter in SQL.
Sql Authority
MSDN
You can done this by passing TVP as #table format
declare #table table(product varchar(10), qty int)
insert into #table
select 'product1', 4 union
select 'product2', 2
;WITH cte AS (
SELECT product, qty FROM #table
UNION ALL
SELECT product, qty-1 FROM cte WHERE qty > 1
)
SELECT t.product, t.qty
FROM cte c
JOIN #table t ON c.product = t.product
ORDER BY 1
Reference for the CTE : Creating duplicate records for a given table row
To pass a table into the stored procedure use table-valued parameter.
At first create a type:
CREATE TYPE [dbo].[ProductsTableType] AS TABLE(
[ID] [varchar](50) NOT NULL,
[qty] [int] NOT NULL
)
Then use this type in the stored procedure. The #ParamProducts is a table and can be used in all queries where a table can be used.
CREATE PROCEDURE [dbo].[AddProducts]
#ParamProducts ProductsTableType READONLY
AS
BEGIN
...
END
To actually insert required number of rows I would use a table of numbers , http://web.archive.org/web/20150411042510/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-numbers-table.html
In my database I have a table called Numbers with a column Number that contains numbers from 1 to 100,000. Once you have such table it is trivial to get the set that you need.
DECLARE #T TABLE (ID varchar(50), qty int);
INSERT INTO #T (ID, qty) VALUES ('productA', 4);
INSERT INTO #T (ID, qty) VALUES ('productB', 1);
INSERT INTO #T (ID, qty) VALUES ('productV', 9);
SELECT *
FROM
#T AS Products
INNER JOIN dbo.Numbers ON Products.qty >= dbo.Numbers.Number
;
Result set
ID qty Number
productA 4 1
productA 4 2
productA 4 3
productA 4 4
productB 1 1
productV 9 1
productV 9 2
productV 9 3
productV 9 4
productV 9 5
productV 9 6
productV 9 7
productV 9 8
productV 9 9
This is an example. In your case you would have this SELECT inside INSERT INTO YourFinalTable.