Select additional columns for a SELECT MIN() statement - sql-server

I have this current select:
SELECT MIN(CONVERT(DateTime,SUBSTRING(NameAndDate,14,8))),
SUBSTRING(NameAndDate,1,12)
FROM MyData
WHERE pName IN (SELECT SUBSTRING(NameAndDate,1,12)
FROM MyData
GROUP BY SUBSTRING(NameAndDate,1,12)
HAVING COUNT(*) > 1)
GROUP BY SUBSTRING(NameAndDate,1,12)
Where SUBSTRING(NameAndDate,1,60) is the person's ID name and SUBSTRING(NameAndDate,61,8) is the date they came in.
There are many times where this data shows up multiple times in the table which is why I want to select the MIN date.
The problem is that there is another column in the table (ID) that I need to be added, but I don't want to group by it because it adds duplicates to the Person's ID.
Is there a way I can do the following:
SELECT ID,
MIN(CONVERT(DateTime,SUBSTRING(NameAndDate,14,8))),
SUBSTRING(NameAndDate,1,12)
FROM MyData
WHERE pName IN (SELECT SUBSTRING(NameAndDate,1,12)
FROM MyData
GROUP BY SUBSTRING(NameAndDate,1,12)
HAVING COUNT(*) > 1)
GROUP BY SUBSTRING(NameAndDate,1,12)
EDIT:
There could be multiple times a person comes through:
ID | NameAndDate
----+-----------------------
1 | J60047238486 08162013
2 | J60047238486 08182013
3 | J60047238486 08242013
4 | J60047238486 09032013
5 | J60047238486 10102013
6 | C40049872351 05302013
7 | C40049872351 07212013
8 | C40049872351 07252013
My current select pulls:
Name | Date
--------------+---------------------
J60047238486 | 08/16/2013 00:00:00
C40049872351 | 05/30/2013 00:00:00
But I want to add the ID column for those specific rows:
ID | Name | Date
----+--------------+---------------------
1 | J60047238486 | 08/16/2013 00:00:00
6 | C40049872351 | 05/30/2013 00:00:00

Try this
SELECT * FROM (
SELECT id,
CONVERT(DateTime,right (SUBSTRING(NameAndDate,14,8),4)
+ SUBSTRING(NameAndDate,14,4)) D,
SUBSTRING(NameAndDate,1,12) N,
COUNT(*) OVER (PARTITION BY SUBSTRING(NameAndDate,1,12)) Cnt,
ROW_NUMBER() OVER (PARTITION BY SUBSTRING(NameAndDate,1,12)
ORDER By SUBSTRING(NameAndDate,14,8)) rn
FROM
mydata
) v WHERE CNT > 1 and rn = 1;
SQL DEMO HERE

You can do this, but it aint' pretty. You have to run your original query to get the minimum date for each name, and then join that back to your MyData table. It's particularly ugly because of how you store the data. Converting your MMDDYYYY string to a data was really fun.
SQL Fiddle
select
MyData.[ID],
t1.theName,
t1.theDate
from
Mydata
inner join
(
select
SUBSTRING(NameAndDate,1,12) as theName,
min (
convert(datetime,
right (SUBSTRING(NameAndDate,14,8),4) + '-' +
left (SUBSTRING(NameAndDate,14,8),2) + '-' +
SUBSTRING((SUBSTRING(NameAndDate,14,8)),3,2)
))as theDate
from
mydata
where
SUBSTRING(NameAndDate,1,12) in
(SELECT SUBSTRING(NameAndDate,1,12)
FROM MyData
GROUP BY SUBSTRING(NameAndDate,1,12)
HAVING COUNT(*) > 1)
group by
SUBSTRING(NameAndDate,1,12) ) t1
ON SUBSTRING(mydata.NameAndDate,1,12) = t1.theName
AND (convert(datetime,
right (SUBSTRING(NameAndDate,14,8),4) + '-' +
left (SUBSTRING(NameAndDate,14,8),2) + '-' +
SUBSTRING((SUBSTRING(NameAndDate,14,8)),3,2))) = t1.theDate

with cte as (
-- first cte - parsing data
select
ID,
left(NameAndDate, 12) as Name,
convert(date,
right(NameAndDate, 4) +
substring(NameAndDate, 14, 2) +
substring(NameAndDate, 16, 2),
112) as Date
from Table1
), cte2 as (
-- second cte - create row_number
select
ID, Name, Date,
row_number() over(partition by Name order by Date) as rn
from cte
)
select
ID, Name, Date
from cte2
where rn = 1
sql fiddle demo

Related

Get sum up of every 2nd day data between a selected date range

I having table like below in Sql Server. I need to get data within in a date range, for example -: StartDate = '2020-09-01' and EndDate = '2020-09-11'. Its quite simple to get data between a date range but complicated part is that,i need to Sum up data in every 2nd day in the selected date range.
For Example -:
As in the above image, i need to Sum up of SKU in every 2nd day in single column. Could anyone help me out with the query for this result output.
CREATE TABLE #Temp
(
Sku Nvarchar(50),
OrderDate DateTime,
Quantity Int,
)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-01 00:00:00.000',2)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-02 00:00:00.000',1)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-03 00:00:00.000',3)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-04 00:00:00.000',4)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-05 00:00:00.000',5)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-06 00:00:00.000',6)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-07 00:00:00.000',2)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-08 00:00:00.000',1)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-09 00:00:00.000',3)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-10 00:00:00.000',1)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#xyz','2020-09-11 00:00:00.000',10)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#abc','2020-09-01 00:00:00.000',1)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#abc','2020-09-02 00:00:00.000',10)
INSERT INTO #Temp(Sku,OrderDate,Quantity)Values('#abc','2020-09-03 00:00:00.000',10)
select * from #Temp
Use row_number() window function to generate a sequence number per Sku. Do a GROUP BY (rn - 1) / 2. HAVING COUNT(*) = 2 is to only consider those with 2 rows
; with
cte as
(
select *, rn = row_number() over (partition by Sku order by OrderDate)
from #Temp
)
select Sku, sum(Quantity)
from cte
group by Sku, (rn - 1) / 2
having count(*) = 2
order by Sku , (rn - 1) / 2
Use STRING_AGG if you want the result in CSV.
With ROW_NUMBER() and LAG() window functions:
select Sku, Quantity
from (
select Sku,
row_number() over (partition by Sku order by OrderDate) rn,
Quantity + lag(Quantity) over (partition by Sku order by OrderDate) Quantity
from #Temp
where OrderDate between '20200901' and '20200911'
) t
where rn % 2 = 0
order by Sku, rn;
See the demo.
Results:
> Sku | Quantity
> :--- | -------:
> #abc | 11
> #xyz | 3
> #xyz | 7
> #xyz | 11
> #xyz | 3
> #xyz | 4
Something like this
;with
string_cte(Sku, OrderDate, Quantity, rn_grp) as(
select *, (row_number() over (partition by Sku order by OrderDate)+1)/2
from #Temp),
sum_cte(Sku, rn_grp, sum_quantity) as (
select Sku, rn_grp, sum(quantity)
from string_cte
group by Sku, rn_grp
having count(*)>1)
select
Sku, string_agg(sum_quantity, ',') within group (order by rn_grp) SecondDaySumUp
from sum_cte
group by Sku
order by 1 desc;
Output
Sku SecondDaySumUp
#xyz 3,7,11,3,4
#abc 11

SQL Server how to sum max for specific category?

Got a problem when constructing a analysis SQL using SQL Server
The raw data as below
GameID | UsrRegID | Score_User
281 | 1 | 1
281 | 1 | 2
281 | 1 | 3
282 | 1 | 0
282 | 1 | 0
282 | 1 | 1
283 | 1 | 2
283 | 1 | 3
Below is the expect output result:
Distinct_Count_GameID | UsrRegID | Score_User
3 | 1 | 7
The logic for calculating the Score_user as below:
Sum(Max(Score_user) for each GemeID)
So the result need to be 3+1+3=7.
Can using the pure SQL to get the above expecting output?
I think we need to aggregate twice here. One option uses ROW_NUMBER:
WITH cte AS (
SELECT GameID, UsrRegID, Score_User,
ROW_NUMBER() OVER (PARTITION BY GameID, UsrRegID ORDER BY Score_User DESC) rn
FROM yourTable
)
SELECT
UsrRegID,
COUNT(DISTINCT GameID) AS Distinct_Count_GameID,
SUM(Score_User) AS Score_User
FROM cte
WHERE rn = 1
GROUP BY
UsrRegID;
You can't do an aggregate of an aggregate on the same SELECT, you can chain them together with CTE or subqueries.
;WITH Maxs AS
(
SELECT
T.GameID,
T.UsrRegID,
MaxScore = MAX(T.Score_User)
FROM
YourTable AS T
GROUP BY
T.GameID,
T.UsrRegID
)
SELECT
M.UsrRegID,
Distinct_Count_GameID = COUNT(DISTINCT(M.GameID)),
Score_User = SUM(M.MaxScore)
FROM
Maxs AS M
GROUP BY
M.UsrRegID
You can also try like following.
SELECT Count(DISTINCT [rgameid]) Distinct_Count_GameID,
Count(DISTINCT [usrregid]) UsrRegID,
(SELECT Sum(M)
FROM (SELECT Max([score_user]) M
FROM [TableName]
GROUP BY [rgameid])t) AS Score_User
FROM [TableName]
DEMO
First find maximum value of score for each GameId and UsrRegID and then find SUM() for the column, Score_User and group it by the columns, GameID and UsrRegID using GROUP BY clause.
Query
select count(distinct [t].[GameID]) as [GameID], [t].[UsrRegID],
sum([t].[Score_User]) as [Score_User] from(
select [GameID], [UsrRegID], max([Score_User]) as [Score_User]
from [your_table_name]
group by [GameID], [UsrRegID]
) as [t]
group by [t].[UsrRegID];
Or, give a row number based on the descending order of score value and group by GameID and UsrRegID. Then find the count of distinct GameId and sum of maximum score.
Query
;with cte as(
select [rn] = row_number() over(
partition by [GameID], [UsrRegID]
order by [Score_User] desc
), *
from [your_table_name]
)
select count(distinct [GameID]) as [GameID], [UsrRegID],
sum([Score_User]) as [Score_User] from cte
where [rn] = 1
group by [UsrRegID];
Aggregates and a COUNT(Distinct GameID):
declare #raw as table (GameID int, UsrRegID int, Score_user int)
insert into #raw values (281, 1, 1)
,(281, 1, 2)
,(281, 1, 3)
,(282, 1, 0)
,(282, 1, 0)
,(282, 1, 1)
,(283, 1, 2)
,(283, 1, 3)
select count(distinct GameID) as Distinct_Count_GameID, UsrRegID, sum(max_score_user)
from
(
select GameID
, UsrRegID
, max(score_user) as max_score_user
from #raw
group by GameID, UsrRegID
) a
group by a.UsrRegID

SQL Find pairs of data in rows and convert to columns

I'm trying to setup a query to pull employee tenure reports. I have an employee status table that tracks information for each employee (e.g. -Hire Date, Term Date, Salary Change, etc.) The table looks like this:
EmployeeID | Date | Event
1 | 1/1/99 | 1
2 | 1/2/99 | 1
1 | 1/3/99 | 2
1 | 1/4/99 | 1
I used a pivot table to move the table from a vertical layout to a horizontal layout
SELECT [FK_EmployeeID], MAX([1]) AS [Hire Date], ISNULL(MAX([2]), DATEADD(d, 1, GETDATE())) AS [Term Date]
FROM DT_EmployeeStatusEvents PIVOT (MAX([Date]) FOR [EventType] IN ([1], [2])) T
GROUP BY [FK_EmployeeID]
I get a result like this:
EmployeeID | 1 | 2
1 | 1/4/99 | 1/3/99
2 | 1/2/99 | *null*
However, the problem I run into is that I need both sets of values for each employee. (We hire a lot of recurring seasonals) What I would like is a way to convert the columns to rows selecting the hire date (1) and the very next term date (2) for each employee like this:
EmployeeID | 1 | 2
1 | 1/1/99 | 1/3/99
2 | 1/2/99 | *null*
1 | 1/4/99 | *null*
Is this possible? I've looked at a lot of the PIVOT examples and they all show an aggregate function.
The problem is that you are attempting to pivot a datetime value so you are limited to using either max or min as the aggregate function. When you use those you will only return one row for each employeeid.
In order to get past this you will need to have some value that will be used during the grouping of your data - I would suggest using a windowing function like row_number(). You can make your subquery:
select employeeid, date, event
, row_number() over(partition by employeeid, event
order by date) seq
from DT_EmployeeStatusEvents
See SQL Fiddle with Demo. This creates a unique value for each employeeId and event combination. This new number will then be grouped on so you can return multiple rows. You full query will be:
select employeeid, [1], [2]
from
(
select employeeid, date, event
, row_number() over(partition by employeeid, event
order by date) seq
from DT_EmployeeStatusEvents
) d
pivot
(
max(date)
for event in ([1], [2])
) piv
order by employeeid;
See SQL Fiddle with Demo
This should get you started...
DECLARE #EMP TABLE (EMPID INT, dDATE DATETIME, EVENTTYPE INT)
INSERT INTO #EMP
SELECT 1,'1/1/99',1 UNION ALL
SELECT 2,'1/2/99',1 UNION ALL
SELECT 1,'1/3/99',2 UNION ALL
SELECT 1,'1/4/99',1
SELECT EMPID, HIRE, TERM
FROM (SELECT EMPID, dDATE, 'HIRE' AS X, ROW_NUMBER() OVER(PARTITION BY EMPID, EVENTTYPE ORDER BY DDATE) AS INSTANCE FROM #EMP WHERE EVENTTYPE=1
UNION ALL
SELECT EMPID, dDATE, 'TERM' AS X, ROW_NUMBER() OVER(PARTITION BY EMPID, EVENTTYPE ORDER BY DDATE) AS INSTANCE FROM #EMP WHERE EVENTTYPE=2) DATATABLE
PIVOT (MIN([DDATE])
FOR X IN ([HIRE],[TERM])) PIVOTTABLE

"Transpose-esque" - T/SQL

DECLARE #TABLE TABLE (NAME varchar(10), DOB Datetime2, Location varchar(50), Phone int)
INSERT INTO #TABLE (NAME, DOB, Location, Phone)
SELECT 'Name1','2000-01-01','USA',1234567890
UNION ALL
SELECT 'Name2','2000-01-02','CAN',0987654321
SELECT * FROM #TABLE
/*
Current Output
NAME DOB Location Phone
Name1 2000-01-01 00:00:00.0000000 USA 1234567890
Name2 2000-01-02 00:00:00.0000000 CAN 987654321
Desired Output
Catagory N1 N2 ...Nn
'NAME1' 'Name2'
DOB '2000-01-01' '2000-01-02'
Location 'USA' 'CAN'
Phone 1234567890 0987654321
Catagory, N1, N2,...Nn are column names (Nn = there can be dynamica number of "Name"
There is no catagory name for 'Name1,'Name2',...'Namen'
Not sure how to do this properly...XML maybe? Please help!
*/
Thank you
You can use the PIVOT function to get the result but you will need to use a few other functions first to get the final product.
First, you will want to create a unique sequence for each row (it doesn't look like you have one), this value is going to be used to create your final list of new columns. You can use row_number() to create this value:
select name, dob, location, phone,
row_number() over(order by name) seq
from yourtable
See SQL Fiddle with Demo. Once you have created this unique value then you can unpivot the multiple columns of data name, dob, location and phone. Depending on your version of SQL Server you can use the unpivot function or CROSS APPLY:
select 'N'+cast(seq as varchar(10)) seq,
category, value, so
from
(
select name, dob, location, phone,
row_number() over(order by name) seq
from yourtable
) src
cross apply
(
select 'name', name, 1 union all
select 'DOB', convert(varchar(10), dob, 120), 2 union all
select 'Location', location, 3 union all
select 'Phone', cast(phone as varchar(15)), 4
) c (category, value, so);
See SQL Fiddle with Demo. This will get your data in the format:
| SEQ | CATEGORY | VALUE | SO |
|-----|----------|------------|----|
| N1 | name | Name1 | 1 |
| N1 | DOB | 2000-01-01 | 2 |
| N1 | Location | USA | 3 |
| N1 | Phone | 1234567890 | 4 |
Now you can easily apply the PIVOT function:
SELECT category, n1, n2
FROM
(
select 'N'+cast(seq as varchar(10)) seq,
category, value, so
from
(
select name, dob, location, phone,
row_number() over(order by name) seq
from yourtable
) src
cross apply
(
select 'name', name, 1 union all
select 'DOB', convert(varchar(10), dob, 120), 2 union all
select 'Location', location, 3 union all
select 'Phone', cast(phone as varchar(15)), 4
) c (category, value, so)
) d
pivot
(
max(value)
for seq in (N1, N2)
) piv
order by so;
See SQL Fiddle with Demo. The above works great if you have a limited number of values but if you will have an unknown number of names, then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME('N'+cast(seq as varchar(10)))
from
(
select row_number() over(order by name) seq
from yourtable
)d
group by seq
order by seq
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT category, ' + #cols + '
from
(
select ''N''+cast(seq as varchar(10)) seq,
category, value, so
from
(
select name, dob, location, phone,
row_number() over(order by name) seq
from yourtable
) src
cross apply
(
select ''name'', name, 1 union all
select ''DOB'', convert(varchar(10), dob, 120), 2 union all
select ''Location'', location, 3 union all
select ''Phone'', cast(phone as varchar(15)), 4
) c (category, value, so)
) x
pivot
(
max(value)
for seq in (' + #cols + ')
) p
order by so'
execute sp_executesql #query;
See SQL Fiddle with Demo. They both give a result of:
| CATEGORY | N1 | N2 |
|----------|------------|------------|
| name | Name1 | Name2 |
| DOB | 2000-01-01 | 2000-01-02 |
| Location | USA | CAN |
| Phone | 1234567890 | 987654321 |

Converting multiple rows to columns with specified heading

I have a table that holds details of activities carried out by individuals - contents of this table is similar to the following:
| Person | Category | Activity |
--------------------------------------
| Username1 | X | X1 |
| Username1 | Y | Y1 |
| Username1 | Z | Z1 |
I need a SQL query that can produce something like the following and any help would be appreciated:
| Person | Cat1 | Cat1_Act|Cat2 | Cat2_Act| Cat3 | Cat3_Act |
---------------------------------------------------------------
| Username1 | X | X1 | Y | Y1 | Z | Z1 |
I understand reading through a number of posts that PIVOT can be used to achieve this but I have not been to find a solution close to what I need as most solutions are often to use values e.g 'X', 'Y', 'Z' (in my example table) as table headers but I want to ideally be able to specify name for the table headers holding the new columns (Hope this all makes sense and someone can help :-) )
There are several ways that you can get the desired result. If you have a limited number of values that you want to PIVOT into columns, then you can hard-code the query a few different ways.
Aggregate function with CASE:
select
person,
max(case when seq = 1 then category end) Cat1,
max(case when seq = 1 then activity end) Cat1_Act,
max(case when seq = 2 then category end) Cat2,
max(case when seq = 2 then activity end) Cat2_Act,
max(case when seq = 3 then category end) Cat3,
max(case when seq = 3 then activity end) Cat3_Act
from
(
select person, category, activity,
row_number() over(partition by person
order by category) seq
from yourtable
) d
group by person;
See SQL Fiddle with Demo. By assigning a sequence or row_number to each category per user, you can use this row number to convert the rows into columns.
Static PIVOT:
If you want to apply the PIVOT function, then I would first suggest unpivoting the category and activity columns into multiple rows and then apply the pivot function.
;with cte as
(
select person, category, activity,
row_number() over(partition by person
order by category) seq
from yourtable
)
select person,
cat1, cat1_act,
cat2, cat2_act,
cat3, cat3_act
from
(
select t.person,
col = case
when c.col = 'cat' then col+cast(seq as varchar(10))
else 'cat'+cast(seq as varchar(10))+'_'+col
end,
value
from cte t
cross apply
(
select 'cat', category union all
select 'act', activity
) c (col, value)
) d
pivot
(
max(value)
for col in (cat1, cat1_act, cat2, cat2_act,
cat3, cat3_act)
) piv;
See SQL Fiddle with Demo
Dynamic PIVOT: Finally if you have an unknown number of values then you can use dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ','
+ QUOTENAME(case
when d.col = 'cat' then col+cast(seq as varchar(10))
else 'cat'+cast(seq as varchar(10))+'_'+col end)
from
(
select row_number() over(partition by person
order by category) seq
from yourtable
) t
cross apply
(
select 'cat', 1 union all
select 'act', 2
) d (col, so)
group by col, so, seq
order by seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT person, ' + #cols + '
from
(
select t.person,
col = case
when c.col = ''cat'' then col+cast(seq as varchar(10))
else ''cat''+cast(seq as varchar(10))+''_''+col
end,
value
from
(
select person, category, activity,
row_number() over(partition by person
order by category) seq
from yourtable
) t
cross apply
(
select ''cat'', category union all
select ''act'', activity
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. All versions give a result:
| PERSON | CAT1 | CAT1_ACT | CAT2 | CAT2_ACT | CAT3 | CAT3_ACT |
| Username1 | X | X1 | Y | Y1 | Z | Z1 |
this is a simple example
SELECT
Person,
MAX(CASE Category WHEN 'X' THEN Activity ELSE 0 END) AS 'X'
MAX(CASE Category WHEN 'Y' THEN Activity ELSE 0 END) AS 'Y'
MAX(CASE Category WHEN 'Z' THEN Activity ELSE 0 END) AS 'Z'
FROM mytable
GROUP BY Person

Resources