Query with problem in the group by in SQL Server - sql

I have this query that works in MySQL:
SELECT
r.*, c.*
FROM
Rsv AS r
INNER JOIN
Clts AS c ON r.ClientID = c.ClientID
LEFT OUTER
Mes AS m ON r.MesaID = m.MesaID
WHERE
RHS IS NULL AND RC = 0
GROUP BY
r.ClientID;
I want this to work in SQL Server and I know that in SQL Server when you use GROUP BY, the elements in the SELECT need to be either in the GROUP BY or need to have an aggregate function. But I want to select more elements and I don't think I need aggregate functions on them because it doesn't matter which one it will retrieve the information from since only the MesaNum field is going to be different. How can I achieve this?
EDIT:
Rsv Table:
RsvID MesaID ClientID RsvTime RsvDate RHS RC
1 1 1 8:00 2018-09-17 null 0
2 2 1 8:00 2018-09-17 null 0
3 3 2 9:00 2018-09-17 null 0
Desired result:
RsvID MesaID ClientID RsvTime RsvDate RHS RC
1 1,2 1 8:00 2018-09-17 null 0
3 3 2 9:00 2018-09-17 null 0
(Sorry, couldn't figure out how to do tables here)

You can try to use STUFF function to do groupby_concat, then make row number by ClientID get rn = 1
SELECT * FROM (
SELECT RsvID,ClientID,RsvTime,RsvDate,RHS,RC,
STUFF((
SELECT ',' + CAST(tt.MesaID AS VARCHAR(5))
FROM Rsv tt
WHERE tt.ClientID = t1.ClientID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') MesaID,
ROW_NUMBER() OVER(PARTITION BY ClientID ORDER BY RsvID) rn
FROM Rsv t1
) t1
where rn = 1
sqlfiddle

Related

SQL get the closest two rows within duplicate rows

I have following table
ID Name Stage
1 A 1
1 B 2
1 C 3
1 A 4
1 N 5
1 B 6
1 J 7
1 C 8
1 D 9
1 E 10
I need output as below with parameters A and N need to select closest rows where difference between stage is smallest
ID Name Stage
1 A 4
1 N 5
I need to select rows where difference between stage is smallest
This query can make use of an index on (name, stage) efficiently:
WITH cte AS (
SELECT TOP 1
a.id AS a_id, a.name AS a_name, a.stage AS a_stage
, n.id AS n_id, n.name AS n_name, n.stage AS n_stage
FROM tbl a
CROSS APPLY (
SELECT TOP 1 *, stage - a.stage AS diff
FROM tbl
WHERE name = 'N'
AND stage >= a.stage
ORDER BY stage
UNION ALL
SELECT TOP 1 *, a.stage - stage AS diff
FROM tbl
WHERE name = 'N'
AND stage < a.stage
ORDER BY stage DESC
) n
WHERE a.name = 'A'
ORDER BY diff
)
SELECT a_id AS id, a_name AS name, a_stage AS stage FROM cte
UNION ALL
SELECT n_id, n_name, n_stage FROM cte;
SQL Server uses CROSS APPLY in place of standard-SQL LATERAL.
In case of ties (equal difference) the winner is arbitrary, unless you add more ORDER BY expressions as tiebreaker.
dbfiddle here
This solution works, if u know the minimum difference is always 1
SELECT *
FROM myTable as a
CROSS JOIN myTable as b
where a.stage-b.stage=1;
a.ID a.Name a.Stage b.ID b.Name b.Stage
1 A 4 1 N 5
Or simpler if u don't know the minimum
SELECT *
FROM myTable as a
CROSS JOIN myTable as b
where a.stage-b.stage in (SELECT min (a.stage-b.stage)
FROM myTable as a
CROSS JOIN myTable as b)

Cumulative list operations on Rows in SQL

I have a scenario where I need to compare the list of a values for a particular day with the previous day for same person. And for every day I need to maintain a difference of those two consecutive days.
This is data of sales people, every day they visit few houses from the whole list of houses in their town.
Below is the data I have:
Houses:
Select Distinct House_id from tbl_houses;
Visits:
Select sales_person_id, Date, visited_house_id;
Now, I have two different tables like this:
House_id
1
2
3
4
5
And, the sales persons visit's data is as follows:
sales_person_id Date visited_house
1 12/6/16 1
1 12/6/16 2
2 12/6/16 1
2 13/6/16 3
3 12/6/16 3
1 13/6/16 1
1 13/6/16 3
A sales person can visit a already visited house and more than one sales person can visit the same house on same day. No restrictions on this.
And, the desired output as follows:
Sales_person_Id Date Visited_Houses Not_Visited_Houses
1 12/6/16 [1,2] [3,4,5]
1 13/6/16 [1,2,3] [4,5]
2 12/6/16 [1] [2,3,4,5]
2 13/6/16 [1,3] [2,4,5]
3 12/6/16 [3] [1,2,4,5]
How can we achieve this in sql?
For SQL Server (you can alter the CTE at the top to use whatever date range you like):
WITH cteDates
AS
(
SELECT CAST(GETDATE() AS date) DT
UNION
SELECT CAST(GETDATE() - 1 AS date)
)
SELECT
SP.sales_person_id
, D.[DT] [Date]
, '[' + LEFT(V.Visited, LEN(V.Visited) - 1) + ']' Visited
, '[' + LEFT(NV.NotVisited, LEN(NV.NotVisited) - 1) + ']' Visited
FROM
cteDates D
CROSS JOIN (SELECT DISTINCT sales_person_id FROM tbl_visits) SP
OUTER APPLY
(
SELECT
CAST(
(
SELECT CAST(visited_house_id AS varchar) + ','
FROM tbl_visits
WHERE
sales_person_id = SP.sales_person_id
AND [Date] = D.DT
ORDER BY visited_house_id
FOR XML PATH ('')
)
AS varchar) Visited
) V
OUTER APPLY
(
SELECT
CAST(
(
SELECT CAST(House_id AS varchar) + ','
FROM tbl_houses
WHERE House_id NOT IN
(
SELECT visited_house_id
FROM tbl_visits
WHERE
sales_person_id = SP.sales_person_id
AND [Date] = D.DT
)
ORDER BY House_id
FOR XML PATH ('')
)
AS varchar) NotVisited
) NV
ORDER BY
sales_person_id
, [Date]
Please check below small piece of code which could help you :
SELECT DISTINCT
H.sales_person_id,
H.Date,
Visited_Houses = STUFF(
(
SELECT ','+CONVERT(NVARCHAR(MAX), houseid)
FROM history
WHERE [sales_person_id] = H.sales_person_id
AND [Date] = H.Date FOR XML PATH('')
), 1, 1, '[')+']',
Not_Visited_Houses = STUFF(
(
SELECT ','+CONVERT(NVARCHAR(MAX), TT.House_id)
FROM
(
SELECT HS1.House_id,
H1.sales_person_id
FROM house HS1
LEFT JOIN #history H1 ON H1.houseid = HS1.House_id
AND H1.sales_person_id = H.sales_person_id AND H1.Date = H.Date
) TT
WHERE TT.sales_person_id IS NULL FOR XML PATH('')
), 1, 1, '[')+']'
FROM house HS
INNER JOIN #history H ON H.houseid = HS.House_id;
Desired Output :
sales_person_id Date Visited_Houses Not_Visited_Houses
--------------- ---------- ---------------- -------------------
1 2016-06-12 [1,2] [3,4,5]
1 2016-06-13 [1,3] [2,4,5]
2 2016-06-12 [1] [2,3,4,5]
2 2016-06-13 [3] [1,2,4,5]
3 2016-06-12 [3] [1,2,4,5]
Note : The above output is as per your Data that you have given.
Hope, it will help you.

Materializing the path of Nested Set hierarchy in T-SQL

I have a table containing details on my company's chart of accounts - this data is essentially stored in nested sets (on SQL Server 2014), with each record having a left and right anchor - there are no Parent IDs.
Sample Data:
ID LeftAnchor RightAnchor Name
1 0 25 Root
2 1 16 Group 1
3 2 9 Group 1.1
4 3 4 Account 1
5 5 6 Account 2
6 7 8 Account 3
7 10 15 Group 1.2
8 11 12 Account 4
9 13 14 Account 5
10 17 24 Group 2
11 18 23 Group 2.1
12 19 20 Account 1
13 21 22 Account 1
I need to materialize the path for each record, so that my output looks like this:
ID LeftAnchor RightAnchor Name MaterializedPath
1 0 25 Root Root
2 1 16 Group 1 Root > Group 1
3 2 9 Group 1.1 Root > Group 1 > Group 1.1
4 3 4 Account 1 Root > Group 1 > Group 1.1 > Account 1
5 5 6 Account 2 Root > Group 1 > Group 1.1 > Account 2
6 7 8 Account 3 Root > Group 1 > Group 1.1 > Account 3
7 10 15 Group 1.2 Root > Group 1 > Group 1.2
8 11 12 Account 4 Root > Group 1 > Group 1.2 > Acount 4
9 13 14 Account 5 Root > Group 1 > Group 1.2 > Account 5
10 17 24 Group 2 Root > Group 2
11 18 23 Group 2.1 Root > Group 2 > Group 2.1
12 19 20 Account 1 Root > Group 2 > Group 2.1 > Account 10
13 21 22 Account 1 Root > Group 2 > Group 2.1 > Account 11
Whilst I've managed to achieve this using CTEs, the query is deathly slow. It takes just shy of two minutes to run with around 1200 records in the output.
Here's a simplified version of my code:
;with accounts as
(
-- Chart of Accounts
select AccountId, LeftAnchor, RightAnchor, Name
from ChartOfAccounts
-- dirty great where clause snipped
)
, parents as
(
-- Work out the Parent Nodes
select c.AccountId, p.AccountId [ParentId]
from accounts c
left join accounts p on (p.LeftAnchor = (
select max(i.LeftAnchor)
from accounts i
where i.LeftAnchor<c.LeftAnchor
and i.RightAnchor>c.RightAnchor
))
)
, path as
(
-- Calculate the Account path for each node
-- Root Node
select c.AccountId, c.LeftAnchor, c.RightAnchor, 0 [Level], convert(varchar(max), c.name) [MaterializedPath]
from accounts c
where c.LeftAnchor = (select min(LeftAnchor) from chart)
union all
-- Children
select n.AccountId, n.LeftAnchor, n.RightAnchor, p.level+1, p.path + ' > ' + n.name
from accounts n
inner join parents x on (n.AccountId=x.AccountId)
inner join path p on (x.ParentId=p.AccountId)
)
select * from path order by LeftAnchor
Ideally this query should only take a couple of seconds (max) to run. I can't make any changes to the database itself (read-only connection), so can anyone come up with a better way to write this query?
After your comments, I realized no need for the CTE... you already have the range keys.
Example
Select A.*
,Path = Replace(Path,'>','>')
From YourTable A
Cross Apply (
Select Path = Stuff((Select ' > ' +Name
From (
Select LeftAnchor,Name
From YourTable
Where A.LeftAnchor between LeftAnchor and RightAnchor
) B1
Order By LeftAnchor
For XML Path (''))
,1,6,'')
) B
Order By LeftAnchor
Returns
First you can try to rearrange your preparing CTEs (accounts and parents) to have it that each CTE contains all data from previous, so you only use the last one in path CTE - no need for multiple joins:
;with accounts as
(
-- Chart of Accounts
select AccountId, LeftAnchor, RightAnchor, Name
from ChartOfAccounts
-- dirty great where clause snipped
)
, parents as
(
-- Work out the Parent Nodes
select c.*, p.AccountId [ParentId]
from accounts c
left join accounts p on (p.LeftAnchor = (
select max(i.LeftAnchor)
from accounts i
where i.LeftAnchor<c.LeftAnchor
and i.RightAnchor>c.RightAnchor
))
)
, path as
(
-- Calculate the Account path for each node
-- Root Node
select c.AccountId, c.LeftAnchor, c.RightAnchor, 0 [Level], convert(varchar(max), c.name) [MaterializedPath]
from parents c
where c.ParentID IS NULL
union all
-- Children
select n.AccountId, n.LeftAnchor, n.RightAnchor, p.level+1, p.[MaterializedPath] + ' > ' + n.name
from parents n
inner join path p on (n.ParentId=p.AccountId)
)
select * from path order by LeftAnchor
This should give some improvement (50% in my test), but to have it really better, you can split first half of preparing data into #temp table, put clustered index on ParentID column in #temp table and use it in second part
if (Object_ID('tempdb..#tmp') IS NOT NULL) DROP TABLE #tmp;
with accounts as
(
-- Chart of Accounts
select AccountId, LeftAnchor, RightAnchor, Name
from ChartOfAccounts
-- dirty great where clause snipped
)
, parents as
(
-- Work out the Parent Nodes
select c.*, p.AccountId [ParentId]
from accounts c
left join accounts p on (p.LeftAnchor = (
select max(i.LeftAnchor)
from accounts i
where i.LeftAnchor<c.LeftAnchor
and i.RightAnchor>c.RightAnchor
))
)
select * into #tmp
from parents;
CREATE CLUSTERED INDEX IX_tmp1 ON #tmp (ParentID);
With path as
(
-- Calculate the Account path for each node
-- Root Node
select c.AccountId, c.LeftAnchor, c.RightAnchor, 0 [Level], convert(varchar(max), c.name) [MaterializedPath]
from #tmp c
where c.ParentID IS NULL
union all
-- Children
select n.AccountId, n.LeftAnchor, n.RightAnchor, p.level+1, p.[MaterializedPath] + ' > ' + n.name
from #tmp n
inner join path p on (n.ParentId=p.AccountId)
)
select * from path order by LeftAnchor
Hard to tell on small sample data, but it should be an improvement. Please tell if you try it.
Seems odd to me that you don't have a Parent ID, but with the aid of an initial OUTER APPLY, we can generate a Parent ID and then run a standard recursive CTE.
Example
Declare #Top int = null --<< Sets top of Hier Try 12 (Just for Fun)
;with cte0 as (
Select A.*
,B.*
From YourTable A
Outer Apply (
Select Top 1 Pt=ID
From YourTable
Where A.LeftAnchor between LeftAnchor and RightAnchor and LeftAnchor<A.LeftAnchor
Order By LeftAnchor Desc
) B
)
,cteP as (
Select ID
,Pt
,LeftAnchor
,RightAnchor
,Lvl=1
,Name
,Path = cast(Name as varchar(max))
From cte0
Where IsNull(#Top,-1) = case when #Top is null then isnull(Pt ,-1) else ID end
Union All
Select r.ID
,r.Pt
,r.LeftAnchor
,r.RightAnchor
,p.Lvl+1
,r.Name
,cast(p.path + ' > '+r.Name as varchar(max))
From cte0 r
Join cteP p on r.Pt = p.ID
)
Select *
From cteP
Order By LeftAnchor
Returns

SQL update all records except the last one with a value

I need to make a query where only the last line of each user that has a car gets a license plate number.
ID UserId LicensePlate HasCar
1 1 ABC123 1
2 1 ABC123 1
3 2 NULL 0
4 3 UVW789 1
5 3 UVW789 1
Should become:
ID UserId LicensePlate HasCar
1 1 NULL 1
2 1 ABC123 1
3 2 NULL 0
4 3 NULL 1
5 3 UVW789 1
So I basically need to find all users with a licenseplate and change all but the last one and make the LicensePlate NULL
Assuming the ID column is an identity column so it can provide the ordering, something like this should do the trick:
;WITH CTE AS
(
SELECT Id,
UserId,
LicensePlate,
ROW_NUMBER() OVER(PARTITION BY UserId ORDER BY Id DESC) rn
FROM Table
WHERE HasCar = 1
)
UPDATE CTE
SET LicensePlate = NULL
WHERE rn > 1
You can try this
UPDATE l
SET l.LicensePlate = null
FROM Car l
INNER JOIN (SELECT UserId, Max(Id) AS max_id
FROM Car
GROUP BY UserId) m ON m.UserId = l.UserId
AND m.max_id <> l.id
You can do it with a join on the table itself like that :
UPDATE car c
INNER JOIN car c2 ON c.userId = c2.userId AND c.id < c2.id AND c.HasCar = 1 AND c2.HasCar = 1
SET c.LicensePlate = NULL
The condition c.id < c2.id will avoid to select the last line
By using LAG Function also you can achieve it.
;WITH License(ID,UserId,LicensePlate,HasCar)
as
(
SELECT 1,1,'ABC123',1 UNION ALL
SELECT 2,1,'ABC123',1 UNION ALL
SELECT 3,2,NULL ,0 UNION ALL
SELECT 4,3,'UVW789',1 UNION ALL
SELECT 5,3,'UVW789',1
)
SELECT ID,UserId,LAG(LicensePlate,1,NULL) OVER(PARTITION BY UserId ORDER BY LicensePlate),HasCar FROM License

SQL Query for Count value from the latest date

I need to have a query that returns the ff:
Count from the latest Date in each Name
If the value of Count from the latest Date is -1 then it will return the count of the Date before the latest Date
If the value of Count from the latest Date is -1 and the other Date is -1. Then return 0
If the value of Count from the latest Date is -1 and no other Date of that Name. Then return 0
Example Table:
ID Name Date Count
1 Adj 09/29/2012 2
2 Adj 09/30/2012 4
3 Ped 09/29/2012 -1
4 Ped 09/30/2012 5
5 Mel 09/29/2012 3
6 Mel 09/30/2012 -1
7 Rod 09/30/2012 7
8 Ney 09/30/2012 -1
9 Jin 09/29/2012 -1
10 Jin 09/30/2012 -1
Desired Output:
Name Count
Adj 4
Ped 5
Mel 3
Rod 7
Ney 0
Jin 0
I am very confused on how to approach this in SQL since I only knew simple query.
Any idea on how to make a query for this? Thanks.
Btw, I'm sorry I forgot to include this. I am using SQL Server 2000.
Try this
SQL FIDDLE EXAMPLE
select A.name, isnull(T.[Count], 0) as [Count]
from (select distinct T.name from table1 as T) as A
outer apply
(
select top 1 T.[Count]
from table1 as T
where T.name = A.name and T.[Count] <> -1
order by T.[date] desc
) as T
order by A.name asc
UPDATE: for SQL 2000 you can use query like this
SQL FIDDLE EXAMPLE for SQL 2000
select A.name, isnull(T1.[Count], 0) as [Count]
from
(
select T.name, max(case when T.[Count] <> -1 then T.[date] else null end) as [date]
from table1 as T
group by T.name
) as A
left outer join table1 as T1 on T1.name = A.name and T1.[date] = A.[date]
but it relies on suggestion that you have unique constraint on name, [date] columns
an other one
Select * from
(
Select Test.name,[Count]
from TEST
Join(
Select name, MAX(Date) as Date from TEST
where [Count]<>-1
Group by Name) a
on a.Name=test.Name and a.Date=Test.Date
UNION
Select Distinct name,0 from test o where not Exists(Select * from test where name=o.Name and [count]<>-1)
) res
order by Name