SQL Server - select every combination - sql

We have two tables below, I am trying to write a query that will select EVERY Purchase for EVERY person on the team. For example, it should show PersonA being associated to PurchaseID 1 and 2 because they are on the same Team as TeamA.
Is this possible? I thought a cross join would work but it seemed to bring back too many columns. I am running SQL Server.
Thank you
Purchases
| PurchaseID | PersonID |
|------------ |---------- |
| 1 | TeamA |
| 2 | TeamA |
| 3 | PersonA |
| 4 | PersonB |
| 5 | TeamB |
Teams
| TeamID | PersonID |
|-------- |---------- |
| 1 | PersonA |
| 1 | TeamA |
| 1 | PersonC |
| 2 | PersonB |
| 2 | TeamB |
Expected results (when filtered on PurchaseID 1):
| PurchaseID | PersonID |
|------------ |---------- |
| 1 | TeamA |
| 1 | PersonA |
| 1 | PersonC |

Your data structure is a little odd, but I think I understand what you want.
If PersonA made a purchase, and PersonA is on TeamA, then everyone on TeamA should be shown as being associated with the purchase, right? Like "I bought these doughnuts for my team, so everyone on my team gets a doughnut".
What you're going to want is to join Purchase to Team on PersonID, as you probably guessed. But then use a CROSS APPLY function, which is in inline table value function, to return all the people on the same team as the person in the "current row".
I used two common table expressions to represent your tables so I could run it. You'll just want the SELECT part:
with Purchases as (
select 1 as PurchaseID, 'TeamA' as PersonID
union select 2 as PurchaseID, 'TeamA' as PersonID
union select 3 as PurchaseID, 'PersonA' as PersonID
union select 4 as PurchaseID, 'PersonB' as PersonID
union select 5 as PurchaseID, 'TeamB' as PersonID
)
, Teams as (
select 1 as TeamID, 'PersonA' as PersonID
union select 1 as TeamID, 'TeamA' as PersonID
union select 1 as TeamID, 'PersonC' as PersonID
union select 2 as TeamID, 'PersonB' as PersonID
union select 2 as TeamID, 'TeamB' as PersonID
)
select Purchases.PurchaseID
, EveryTeamMember.PersonID
from Purchases
join Teams
on Teams.PersonID = Purchases.PersonID
cross apply (
select PersonID
from Teams InnerTable
where InnerTable.TeamID = Teams.TeamID
) as EveryTeamMember
where Purchases.PurchaseID = 1

If you are looking ti get all Team persons when the PersonID starts with Team then i think you should do a CROSS APPLY over all PersonID who starts with Team and UNION (NOT UNION ALL) Single Person purchases:
DECLARE #Purchases TABLE (
PurchaseID INT,
PersonID Varchar(50)
)
INSERT INTO #Purchases(PersonID,PurchaseID) VALUES ('TeamA', 1);
INSERT INTO #Purchases(PersonID,PurchaseID) VALUES ('TeamA', 2);
INSERT INTO #Purchases(PersonID,PurchaseID) VALUES ('PersonA', 3);
INSERT INTO #Purchases(PersonID,PurchaseID) VALUES ('PersonB', 4);
INSERT INTO #Purchases(PersonID,PurchaseID) VALUES ('TeamB', 5);
DECLARE #Teams TABLE (
TeamID INT,
PersonID Varchar(50)
)
INSERT INTO #Teams(PersonID,TeamID) VALUES ('PersonA', 1);
INSERT INTO #Teams(PersonID,TeamID) VALUES ('TeamA', 1);
INSERT INTO #Teams(PersonID,TeamID) VALUES ('PersonC', 1);
INSERT INTO #Teams(PersonID,TeamID) VALUES ('PersonB', 2);
INSERT INTO #Teams(PersonID,TeamID) VALUES ('TeamB', 2);
SELECT T1.PurchaseID,TeamPersons.PersonID
FROM #Purchases T1
INNER JOIN #Teams T2
ON T2.PersonID = T1.PersonID AND T1.PersonID LIKE'Team%'
CROSS APPLY (
SELECT PersonID
FROM #Teams T3
WHERE T3.TeamID = T2.TeamID
) AS TeamPersons
UNION
SELECT T1.PurchaseID
, T1.PersonID
FROM #Purchases T1
WHERE T1.PersonID NOT LIKE 'Team%'
Result

Related

Select all records of one table that contain two records in another with certain id

I have two tables of 1:m relation. Need to select which People records have both records in Actions table whit id 1 and 2
People
+----+------+--------------+
| id | name | phone_number |
+----+------+--------------+
| 1 | John | 111111111111 |
+----+------+--------------+
| 3 | Jane | 222222222222 |
+----+------+--------------+
| 4 | Jack | 333333333333 |
+----+------+--------------+
Action
+----+------+------------+
| id | PplId| ActionId |
+----+------+------------+
| 1 | 1 | 1 |
+----+------+------------+
| 2 | 1 | 2 |
+----+------+------------+
| 3 | 2 | 1 |
+----+------+------------+
| 4 | 4 | 2 |
+----+------+------------+
Output
+----+------+--------------+----------
|PplId| name | Phone |ActionId |
+-----+------+-------------+----+-----
| 1 | John | 111111111111| 1 |
+-----+------+-------------+----+-----
| 1 | John | 111111111111| 2 |
+-----+------+-------------+----+-----
Return records of People that have both Have Actionid 1 and Action id 2(Have records in Actions).
Window functions are one method. Assuming actions are not duplicated for a person:
select pa.*
from (select p.*, a.action, count(*) over (partition by p.id) as num_actions
from people p join
action a
on p.id = a.pplid
where a.action in (1, 2)
) pa
where num_actions = 2;
In my opinion, getting two rows with the action detail seems superfluous -- you already know the actions. If you only want the people, then exists comes to mind:
select p.*
from people p
where exists (select 1 from actions where a.pplid = p.id and a.action = 1) and
exists (select 1 from actions where a.pplid = p.id and a.action = 2);
With the right index (actions(pplid, action)), I would expect two exists to be faster than group by.
Try this below query using subquery and join
select a.Pplid, name, phone, actionid from (
select a.pplid as Pplid, name, phone_number as phone
from People P
join Action A on a.pplid= p.id
group by a.pplid, name, phone_number
having count(*)>1 )P
join Action A on a.Pplid= p.Pplid
Try something like this
IF OBJECT_ID('tempdb..#People') IS NOT NULL DROP TABLE #People
CREATE TABLE #People (id INT, name VARCHAR(255), phone_number VARCHAR(50))
INSERT #People
SELECT 1, 'John', '111111111111' UNION ALL
SELECT 3, 'Jane', '222222222222' UNION ALL
SELECT 4, 'Jack', '333333333333'
IF OBJECT_ID('tempdb..#Action') IS NOT NULL DROP TABLE #Action
CREATE TABLE #Action (id INT, PplId INT, ActionId INT)
INSERT #Action
SELECT 1, 1, 1 UNION ALL
SELECT 2, 1, 2 UNION ALL
SELECT 3, 2, 1 UNION ALL
SELECT 4, 4, 2
GO
SELECT p.ID AS PplId
, p.name
, p.phone_number AS Phone
, a.ActionId
FROM #People p
JOIN #Action a
ON p.ID = a.PplId
WHERE p.ID IN ( SELECT PplId
FROM #Action
WHERE ActionId IN (1, 2)
GROUP BY PplId
HAVING COUNT(*) = 2 )
AND a.ActionId IN (1, 2)
GO

Returning most recent row SQL Server

I have this table
CREATE TABLE Test (
OrderID int,
Person varchar(10),
LastModified Date
);
INSERT INTO Test (OrderID, Person, LastModified)
VALUES (1, 'Sam', '2018-05-15'),
(1, 'Tim','2018-05-14'),
(1, 'Kim','2018-05-05'),
(1, 'Dave','2018-05-13'),
(1, 'James','2018-05-11'),
(1, 'Fred','2018-05-05');
select * result:
| OrderID | Person | LastModified |
|---------|--------|--------------|
| 1 | Sam | 2018-05-15 |
| 1 | Tim | 2018-05-14 |
| 1 | Kim | 2018-05-05 |
| 1 | Dave | 2018-05-13 |
| 1 | James | 2018-05-11 |
| 1 | Fred | 2018-05-05 |
I am looking to return the most recent modified row which is the first row with 'Sam'.
Now i now i can use max to return the most recent date but how can i aggregate the person column to return sam?
Looking for a result set like
| OrderID | Person | LastModified |
|---------|--------|--------------|
| 1 | Sam | 2018-05-15 |
I ran this:
SELECT
OrderID,
max(Person) AS [Person],
max(LastModified) AS [LastModified]
FROM Test
GROUP BY
OrderID
but this returns:
| OrderID | Person | LastModified |
|---------|--------|--------------|
| 1 | Tim | 2018-05-15 |
Can someone advice me further please? thanks
*** UPDATE
INSERT INTO Test (OrderID, Person, LastModified)
VALUES (1, 'Sam', '2018-05-15'),
(1, 'Tim','2018-05-14'),
(1, 'Kim','2018-05-05'),
(1, 'Dave','2018-05-13'),
(1, 'James','2018-05-11'),
(1, 'Fred','2018-05-05'),
(2, 'Dave','2018-05-13'),
(2, 'James','2018-05-11'),
(2, 'Fred','2018-05-05');
So i would be looking for this result to be:
| OrderID | Person | LastModified |
|---------|--------|--------------|
| 1 | Sam | 2018-05-15 |
| 2 | Dave | 2018-05-13 |
If you always want just one record (the latest modified one) per OrderID then this would do it:
SELECT
t2.OrderID
, t2.Person
, t2.LastModified
FROM (
SELECT
MAX( LastModified ) AS LastModified
, OrderID
FROM
Test
GROUP BY
OrderID
) t
INNER JOIN Test t2
ON t2.LastModified = t.LastModified
AND t2.OrderID = t.OrderID
Expanding on your comment ("thanks very much, is there a way i can do this if there is more than one orderID e.g. multiple people and lastmodified for multiple orderID's?"), in xcvd's answer, I assume what you therefore want is this:
WITH CTE AS(
SELECT OrderId,
Person,
LastModifed,
ROW_NUMBER() OVER (PARTITION BY OrderID ORDER BY LastModified DESC) AS RN
FROM YourTable)
SELECT OrderID,
Person,
LastModified
FROM CTE
WHERE RN = 1;
How about just using TOP (1) and ORDER BY?
SELECT TOP (1) t.*
FROM Test t
ORDER BY LastModified DESC;
If you want this for each orderid, then this is a handy method in SQL Server:
SELECT TOP (1) WITH TIES t.*
FROM Test t
ORDER BY ROW_NUMBER() OVER (PARTITION BY OrderId ORDER BY LastModified DESC);
"xcvd's" answer is perfect for this, I would just like to add another solution that can be used here for the sake of showing you a method that can be used in more complex situations than this. This solution uses a nested query (sub-query) to find the MAX(LastModified) regardless of any other field and it will use the result in the original query's WHERE clause to find any results that meet the new criteria. Cheers.
SELECT OrderID
, Person
, LastModified
FROM Test
WHERE LastModified IN (SELECT MAX(LastModified)
FROM Test)
Here is one other method :
select t.*
from Test t
where LastModified = (select max(t1.LastModified) from Test t1 where t1.OrderID = t.OrderID);

Displaying whole table after stripping characters in SQL Server

This question has 2 parts.
Part 1
I have a table "Groups":
group_ID person
-----------------------
1 Person 10
2 Person 11
3 Jack
4 Person 12
Note that not all data in the "person" column have the same format.
In SQL Server, I have used the following query to strip the "Person " characters out of the person column:
SELECT
REPLACE([person],'Person ','')
AS [person]
FROM Groups
I did not use UPDATE in the query above as I do not want to alter the data in the table.
The query returned this result:
person
------
10
11
12
However, I would like this result instead:
group_ID person
-------------------
1 10
2 11
3 Jack
4 12
What should be my query to achieve this result?
Part 2
I have another table "Details":
detail_ID group1 group2
-------------------------------
100 1 2
101 3 4
From the intended result in Part 1, where the numbers in the "person" column correspond to those in "group1" and "group2" of table "Details", how do I selectively convert the numbers in "person" to integers and join them with "Details"?
Note that all data under "person" in Part 1 are strings (nvarchar(100)).
Here is the intended query output:
detail_ID group1 group2
-------------------------------
100 10 11
101 Jack 12
Note that I do not wish to permanently alter anything in both tables and the intended output above is just a result of a SELECT query.
I don't think first part will be a problem here. Your query is working fine with your expected result.
Schema:
CREATE TABLE #Groups (group_ID INT, person VARCHAR(50));
INSERT INTO #Groups
SELECT 1,'Person 10'
UNION ALL
SELECT 2,'Person 11'
UNION ALL
SELECT 3,'Jack'
UNION ALL
SELECT 4,'Person 12';
CREATE TABLE #Details(detail_ID INT,group1 INT, group2 INT);
INSERT INTO #Details
SELECT 100, 1, 2
UNION ALL
SELECT 101, 3, 4 ;
Part 1:
For me your query is giving exactly what you are expecting
SELECT group_ID,REPLACE([person],'Person ','') AS person
FROM #Groups
+----------+--------+
| group_ID | person |
+----------+--------+
| 1 | 10 |
| 2 | 11 |
| 3 | Jack |
| 4 | 12 |
+----------+--------+
Part 2:
;WITH CTE AS(
SELECT group_ID
,REPLACE([person],'Person ','') AS person
FROM #Groups
)
SELECT D.detail_ID, G1.person, G2.person
FROM #Details D
INNER JOIN CTE G1 ON D.group1 = G1.group_ID
INNER JOIN CTE G2 ON D.group1 = G2.group_ID
Result:
+-----------+--------+--------+
| detail_ID | person | person |
+-----------+--------+--------+
| 100 | 10 | 10 |
| 101 | Jack | Jack |
+-----------+--------+--------+
Try following query, it should give you the desired output.
;WITH MT AS
(
SELECT
GroupId, REPLACE([person],'Person ','') Person
AS [person]
FROM Groups
)
SELECT Detail_Id , MT1.Person AS group1 , MT2.Person AS AS group2
FROM
Details D
INNER JOIN MT MT1 ON MT1.GroupId = D.group1
INNER JOIN MT MT2 ON MT2.GroupId= D.group2
The first query works
declare #T table (id int primary key, name varchar(10));
insert into #T values
(1, 'Person 10')
, (2, 'Person 11')
, (3, 'Jack')
, (4, 'Person 12');
declare #G table (id int primary key, grp1 int, grp2 int);
insert into #G values
(100, 1, 2)
, (101, 3, 4);
with cte as
( select t.id, t.name, ltrim(rtrim(replace(t.name, 'person', ''))) as sp
from #T t
)
-- select * from cte order by cte.id;
select g.id, c1.sp as grp1, c2.sp as grp2
from #G g
join cte c1
on c1.id = g.grp1
join cte c2
on c2.id = g.grp2
order
by g.id;
id grp1 grp2
----------- ----------- -----------
100 10 11
101 Jack 12

Selecting data from 3 tables, grouping by latest date value and another value

Okay, so I've been racking my brains on this for a while, and I think it's time to ask the collective!
I'm using SQLServer and I've got 3 tables, defined as such:
VolumeData
__________________________
| dataid | currentReading|
--------------------------
| 1 | 22 |
| 7 | 33 |
| 9 | 25 |
| 12 | 12 |
--------------------------
LatestData
________________________________________________________________
| dataid | unitNumber | unitLocation | dateTimeStamp |
----------------------------------------------------------------
| 1 | 2344454 | 2 | 2017-07-10 13:16:29.000 |
| 7 | 2344451 | 44 | 2017-07-10 13:22:29.000 |
| 9 | 2344456 | 92 | 2017-07-10 12:16:29.000 |
| 12 | 2344456 | 12 | 2017-07-10 12:13:23.000 |
----------------------------------------------------------------
unitData
____________________________________________________________________________________
| unitNumber | unitLocation | buildingNumber | officeNumber | officeName | country |
------------------------------------------------------------------------------------
| 2344454 | 2 | 44 | 1 | Telford | UK |
| 2344451 | 44 | 22 | 1 | Telford | UK |
| 2344456 | 92 | 12 | 2 | Hamburg | GER |
| 2344456 | 12 | 33 | 2 | Hamburg | GER |
------------------------------------------------------------------------------------
I need to retrieve just the latest currentReading (based on the dateTimeStamp field in LatestData) along with the following fields, grouped on the unitNumber:
currentReading, unitNumber, officeName, country, buildingNumber
One more thing to note is that records can arrive in any order.
The following is one example that I tried, I've tried many more but I've not kept them open unfortunately:
SELECT
a.currentReading
,MAX(b.dateTimeStamp)
,c.unitNumber
,c.country
,c.officeName
FROM [VolumeData] a INNER JOIN LatestData b ON a.dataid = b.dataid INNER JOIN
unitData c ON c.[unitNumber] = b.[unitNumber] AND c.[unitLocation] = b.[unitLocation];
This results in: Column 'VolumeData.currentReading' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Any advice would be much appreciated! Everything I try either results in retrieving far too many rows or results in logical SQL errors. I should also add that these tables contain millions of rows, and grow daily, so I'm looking for a really efficient way to do this.
Thanks!
You can use ROW_NUMBER() to order the date. Then you just take the first one, which correspond to the latest date.
SELECT *
FROM (
SELECT a.currentReading
, b.dateTimeStamp
, c.unitNumber
, c.country
, c.officeName
, ROW_NUMBER() OVER (PARTITION BY c.unitNumber ORDER BY b.dateTimeStamp DESC) AS rowNum
FROM [VolumeData] a
INNER JOIN LatestData b ON a.dataid = b.dataid
INNER JOIN unitData c ON c.[unitNumber] = b.[unitNumber] AND c.[unitLocation] = b.[unitLocation]
) a
WHERE rowNum = 1
Same logic as Eric's answer, probably a bit cleaner using CTE and joins lesser records.
DECLARE #VolumeData TABLE
(
dataid int,
currentReading int
);
INSERT INTO #VolumeData VALUES(1, 22);
INSERT INTO #VolumeData VALUES(7, 33);
INSERT INTO #VolumeData VALUES(9, 25);
INSERT INTO #VolumeData VALUES(12,12);
DECLARE #LatestData TABLE
(
dataid int,
unitNumber int,
unitLocation int,
dateTimeStamp datetime
);
INSERT INTO #LatestData VALUES(1, 2344454, 2, '2017-07-10 13:16:29.000');
INSERT INTO #LatestData VALUES(7, 2344451, 44, '2017-07-10 13:22:29.000');
INSERT INTO #LatestData VALUES(9, 2344456, 92, '2017-07-10 12:16:29.000');
INSERT INTO #LatestData VALUES(12, 2344456, 12, '2017-07-10 12:13:23.000');
DECLARE #UnitData TABLE
(
unitNumber int,
unitLocation int,
buildingNumber int,
officeNumber int,
officeName varchar(50),
country varchar(50)
);
INSERT INTO #UnitData VALUES(2344454, 2, 44, 1, 'Telford', 'UK');
INSERT INTO #UnitData VALUES(2344451, 44, 22, 1, 'Telford', 'UK');
INSERT INTO #UnitData VALUES(2344456, 92, 12, 2, 'Hamburg', 'GER');
INSERT INTO #UnitData VALUES(2344456, 12, 33, 2, 'Hamburg', 'GER');
WITH LatestData_CTE (dataid, unitNumber, unitLocation, dateTimeStamp, rowNum)
AS
(
SELECT dataid
, unitNumber
, unitLocation
, dateTimeStamp
, ROW_NUMBER() OVER (PARTITION BY unitNumber ORDER BY dateTimeStamp DESC) AS rowNum
FROM #LatestData
)
SELECT currentReading, l.unitNumber, officeName, country, buildingNumber
FROM LatestData_CTE l
INNER JOIN #VolumeData v ON v.dataid = l.dataid
INNER JOIN #UnitData u ON u.[unitNumber] = l.[unitNumber] AND u.[unitLocation] = l.[unitLocation]
WHERE l.rowNum = 1
Not complete code, but an advice - It can be implemented by ROW_NUMBER function in CTE
Similar to
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/597b876e-eb00-4013-a613-97c377408668/rownumber-and-cte?forum=transactsql
http://datachix.com/2010/02/10/use-a-common-table-expression-and-the-row_number-function-to-eliminate-duplicate-rows-3/
Just google CTE+ROW_NUMBER to get more examples.
So in CTE you join all required tables and you apply ROW_NUMBER over partition, ordered by dateTimestamp (DESC) and then you use WHERE CTE_name.Rank = 1 in the query that uses that CTE.

Retrieve data from one table and insert into another table

I am trying to retrieve data from one table and then insert it into another table.
This is a a sample of the first table in which there is the following data. tb1 is the table which consists of data. The two columns Manager and TeamLeader basically means for example : Josh is managed by Vik and so on. An employee can also be a manager to another employer. For example, Josh is the manager of Nirvan and Deva.
+---------+-------------+
| tbl1 |
+---------+-------------+
| Manager | Employee |
+---------+-------------+
| Vik | Josh |
+---------+-------------+
| Vik | Cindy |
+---------+-------------+
| Vik | Alvin |
+---------+-------------+
| Vik | Kim |
+---------+-------------+
| Josh | Nirvan |
+---------+-------------+
| Josh | Deva |
+---------+-------------+
| Cindy | Mervyn |
+---------+-------------+
| Nirvan | Reeta |
+---------+-------------+
| Nirvan | Zaki |
+---------+-------------+
| Nirvan | Sunny |
+---------+-------------+
What i want is to insert all these records in another table with the following columns : Id(which is set to IDENTITY/AUTONUM), Name(name of employee/manager), ParentId(of the manager which a particular employee has to report to).
So for example,
I should be getting something of the sort :
ID Name ParentId
1 Vik 0
2 Josh 1
3 Cindy 1
4 Alvin 1
5 Kim 1
6 Nirvan 2
7 Deva 2
8 Mervyn 3
9 Reeta 6
10 Zaki 6
11 Sunny 6
I am having difficulty to get the right sql to retrieve this data from the first table and insert it into another table.
You have to do it in two steps. One to create all your ID fields. Then you can join with this table to match up the employee to find the manager and the manager's ID:
insert into MyNewTable (Name, ParentID)
select Manager, ParentID
from tbl1
union
select Employee, ParentID
from tbl1
update MyNewTable
set MyNewTable.ParentId = Managers.Id
from MyNewTable
join tbl1
on tbl1.Employee = MyNewTable.Name
join MyNewTable Managers
on MyNewTable.Name = Managers.Manager
INSERT INTO tbl2 (
Name
,parentId
)
SELECT DISTINCT manager
,0
FROM tbl1
WHERE manager NOT IN (
SELECT employee
FROM tbl1
)
INSERT INTO tbl2
SELECT DISTINCT employee
,0
FROM tbl1
UPDATE tbl2
SET parentid = parent.id
FROM tbl2
INNER JOIN tbl1 ON tbl2.Name = tbl1.employee
INNER JOIN tbl2 parent ON parent.Name= tbl1.manager
create table #tbl1 (manager char(15), employee char(15))
create table #tbl2 (ID int identity(1,1), Name char(15), ParentID int)
insert into #tbl1
values ('Vik', 'Josh') ,
('Vik' , 'Cindy') ,
('Vik' , 'Alvin') ,
('Vik' , 'Kim') ,
('Josh' , 'Nirvan'),
('Josh' , 'Deva') ,
('Cindy' ,'Mervyn') ,
('Nirvan' , 'Reeta'),
('Nirvan' , 'Zaki'),
('Nirvan' , 'Sunny')
--- big Boss
insert into #tbl2(Name, ParentID)
SELECT DISTINCT manager, 0
FROM #tbl1
WHERE manager NOT IN ( SELECT employee FROM #tbl1 )
--- Grab all Employees (Name)
insert into #tbl2(Name)
select distinct employee from #tbl1
--- Update ParentID
declare #StructureLevl int = 3 -- 0 1 2 3 Do something to figure out this number first
declare #Var_int int = 0
while (#Var_int < #StructureLevl)
begin
update #tbl2
set ParentID= #Var_int +1
where Name in (select A.employee
from #tbl1 A inner join #tbl2 B on A.manager = B.Name
where B.ParentID = #Var_int)
set #Var_int +=1
end
select * from #tbl2
order by ParentID