Need query to select direct and indirect customerID aliases - sql

I need a query that will return all related alias id's from either column. Shown here are some alias customer ids, among thousands of other rows. If the input parameter to a query is id=7, I need a query that would return 5 rows (1,5,7,10,22). That is because they are all aliases of one-another. For example, 22 and 10 are indirect aliases of 7.
CustomerAlias
--------------------------
AliasCuID AliasCuID2
--------------------------
1 5
1 7
5 7
10 5
22 1
Here is an excerpt from the customer table.
Customer
----------------------------------
CuID CuFirstName CuLastName
----------------------------------
1 Mike Jones
2 Fred Smith
3 Jack Jackson
4 Emily Simpson
5 Mike Jones
6 Beth Smith
7 Mike jones
8 Jason Robard
9 Emilie Jiklonmie
10 Michael jones
11 Mark Lansby
12 Scotty Slash
13 Emilie Jiklonmy
22 mike jones
I've been able to come close, but I cannot seem to select the indirectly related aliases correctly. Given this query:
SELECT DISTINCT Customer.CuID, Customer.CuFirstName, Customer.CuLastName
FROM Customer WHERE
(Customer.CuID = 7) OR (Customer.CuID IN
(SELECT AliasCuID2
FROM CustomerAlias AS CustomerAlias_2
WHERE (AliasCuID = 7))) OR (Customer.CuID IN
(SELECT AliasCuID
FROM CustomerAlias AS CustomerAlias_1
WHERE (AliasCuID2 = 7)))
Returns 3 out of 5 of the desired ids of course. This lacks the indirectly related aliased id of 10 and 22 in the result rows.
1 Mike Jones
5 Mike Jones
7 Mike jones
* Based on suggestions below, I am trying a CTE hierarchical query.
I have this now after following some suggestions. It works for some, as long as the records in the table reference enough immediate ids. But, if the query uses id=10, then it still comes up short, just by the nature of the data.
DECLARE #id INT
SET #id = 10;
DECLARE #tmp TABLE ( a1 INT, a2 INT, Lev INT );
WITH Results (AliasCuID, AliasCuID2, [Level]) AS (
SELECT AliasCuID,
AliasCuID2,
0 as [Level]
FROM CustomerAlias
WHERE AliasCuID = #id OR AliasCuID2 = #id
UNION ALL
-- Recursive step
SELECT a.AliasCuID,
a.AliasCuID2,
r.[Level] + 1 AS [Level]
FROM CustomerAlias a
INNER JOIN Results r ON a.AliasCuID = r.AliasCuID2 )
INSERT INTO #tmp
SELECT * FROM Results;
WITH Results3 (AliasCuID, AliasCuID2, [Level]) AS (
SELECT AliasCuID,
AliasCuID2,
0 as [Level]
FROM CustomerAlias
WHERE AliasCuID = #id OR AliasCuID2 = #id
UNION ALL
-- Recursive step
SELECT a.AliasCuID,
a.AliasCuID2,
r.[Level] + 1 AS [Level]
FROM CustomerAlias a
INNER JOIN Results3 r ON a.AliasCuID2 = r.AliasCuID )
INSERT INTO #tmp
SELECT * FROM Results3;
SELECT DISTINCT a1 AS id FROM #tmp
UNION ALL
SELECT DISTINCT a2 AS id FROM #tmp
ORDER BY id
Note that this is a simplified the query to just give a list of related ids.
---
id
---
5
5
7
10
But, it is still unable to pull in ids 1 and 22.

This is not an easy problem to solve unless you have some idea of the depth of your search (https://stackoverflow.com/a/7569520/1803682) - which it looks like you do not - and take a brute force approach to it.
Assuming you do not know the depth you will need to write a stored proc. I followed this approach for a nearly identical problem: https://dba.stackexchange.com/questions/7147/find-highest-level-of-a-hierarchical-field-with-vs-without-ctes/7161#7161
UPDATE
If you don't care about the chain of how the alias's were created - I would run a script recursively to make them all refer to a single (master?) record. Then you can easily do the search and it will be quick - not a solution if you care about how the alias's get traversed though.

I created a SQL Fiddle for SQL Server 2012. Please let me know if you can or cannot access it.
My thought here was that you'd want to just keep checking the left and right branches recursively, separately. This logic probably falls apart if the relationships bounce between left and right. You could set up a third CTE to reference the first two, but joining on left to right and right to left, but ain't nobody got time for that.
The code is below as well.
CREATE TABLE CustomerAlias
(
AliasCuID INT,
AliasCuID2 INT
)
GO
INSERT INTO CustomerAlias
SELECT 1,5
UNION SELECT 1, 7
UNION SELECT 5, 7
UNION SELECT 10, 5
UNION SELECT 22, 1
GO
DECLARE #Value INT
SET #Value = 7
; WITH LeftAlias AS
(
SELECT AliasCuID
, AliasCuID2
FROM CustomerAlias
WHERE AliasCuID2 = #Value
UNION ALL
SELECT a.AliasCuID
, a.AliasCuID2
FROM CustomerAlias a
JOIN LeftAlias b
ON a.AliasCuID = b.AliasCuID2
)
, RightAlias AS
(
SELECT AliasCuID
, AliasCuID2
FROM CustomerAlias
WHERE AliasCuID = #Value
UNION ALL
SELECT a.AliasCuID
, a.AliasCuID2
FROM CustomerAlias a
JOIN LeftAlias b
ON a.AliasCuID2 = b.AliasCuID
)
SELECT DISTINCT A
FROM
(
SELECT A = AliasCuID
FROM LeftAlias
UNION ALL
SELECT A = AliasCuID2
FROM LeftAlias
UNION ALL
SELECT A = AliasCuID
FROM RightAlias
UNION ALL
SELECT A = AliasCuID2
FROM RightAlias
) s
ORDER BY A

Related

How can I create 1 ID for a group of customers in a column?

I have a list of customers who have different contracts with my service company.
Sometimes we can have different customers per contracts. Example:
Karen and her boyfriend Will have a contract.
Sometimes a group of customers can have different contracts. Example: Karen and Will have multiple contracts with me.
Here is the table:
idCustomer idContract NameCust
-----------------------------------------
1 A Karen
1 B Will
2 A Karen
2 B Will
3 C Steph
4 C Peter
But because Karen and Will can have multiple contracts, I want a unique id for them and other group of customers. Result table I want:
idCustomer idContract NameCust Customer_GroupID
-----------------------------------------------------
1 A Karen 1
1 B Will 1
2 A Karen 1
2 B Will 1
3 C Steph 2
4 C Peter 2
I'm stuck because I tried different things that doesn't give me the result I need. I find in the forum someone who used Dense_Rank function but here is the result:
SELECT
RANK() OVER (ORDER BY idCustomers) AS Customer_GroupID,
IdCustomers,
IdContract
FROM
Table
Here is the result:
Cust_GroupID idCustomer idContract
--------------------------------------
1 1 A
2 1 B
1 2 A
2 2 B
3 3 C
3 4 C
I even tried to use multiple select, not exists but nothing.
It seem that I have understood your requirement. Still you should throw little more sample data to make it absolute clear to all. Append few more varied sample data in existing one.
You should test with different sample data and let me know if its not working.
Sample Data,
create table #test(idCustomer int,idContract varchar(50) , NameCust varchar(50))
insert into #test (idCustomer ,idContract , NameCust ) VALUES
(1,'A','Karen')
,(1,'B','Will' )
,(2,'A','Karen')
,(2,'B','Will' )
,(3,'C','Steph')
,(4,'C','Peter')
Method 1- SET BASED Approach,
;with CTE as
(
select *
,ROW_NUMBER()over(order by idCustomer)rn
from #test
)
,CTE1 as
(
select t.id,t.idContract,t.NameCust
, isnull(t1.idCustomer,t.idCustomer)customerGroupID
from CTE t
outer apply(
select top 1 idCustomer
from CTE t1
where t1.id< t.id
and((t.idCustomer=t1.idCustomer)
or (t.idContract=t1.idContract))
order by t1.id
)t1
)
,CTE2 AS(
select *
,DENSE_RANK()OVER( order by customerGroupID )Customer_GroupID
from CTE1
)
select * from CTE2
Method 2- RBAR (using cursor),
create table #test1(id int identity(1,1),idCustomer int
,idContract varchar(50) , NameCust varchar(50),customer_Groupid int)
insert into #test1 (idCustomer ,idContract
, NameCust,customer_Groupid )
select idCustomer ,idContract , NameCust,null
from #test
DECLARE #idCustomer INT
DECLARE #idContract varchar(50)
DECLARE #id INT
declare #customer_Groupid int
DECLARE #getCustomer CURSOR
SET #getCustomer = CURSOR FOR
SELECT id, idCustomer,idContract
FROM #test1
OPEN #getCustomer
FETCH NEXT
FROM #getCustomer INTO #id, #idCustomer,#idContract
WHILE ##FETCH_STATUS = 0
BEGIN
select top 1 #customer_Groupid=customer_Groupid
from #test1 where id<#id order by id desc
if not exists(select 1 from #test1 where id<#id
and (idCustomer=#idCustomer or idContract=#idContract))
BEGIN
select top 1 #customer_Groupid=customer_Groupid
from #test1 where id<#id order by id desc
if(#customer_Groupid is not null)
set #customer_Groupid=#customer_Groupid+1
end
if(#customer_Groupid is null)
set #customer_Groupid=1
update #test1 set customer_Groupid=#customer_Groupid where id=#id
FETCH NEXT
FROM #getCustomer INTO #id, #idCustomer,#idContract
END
CLOSE #getCustomer
DEALLOCATE #getCustomer
select * from #test1
drop table #test1
drop table #test
There seems to be a need to create an interim table of NameCustGroup like
NameCustList idCustomer idContract
Karen,Will 1 A,B
Karen,Will 2 A,B
Peter,Steph 3 C
Peter,Steph 4 C
And then use that to create
Customer_GroupID idCustomer idContract
1 1 A,B
1 2 A,B
2 3 C
2 4 D
The interim table seems hardest, because the NameCustList needs to be formed either by Common idCustomer or Common idContract. This is a work in progress... ...

T-SQL Execute Recursion for Each Value in Multiple Columns

I have a dataset with two columns. A person in the first column may be in the span of control of a person in the second column (i.e. everyone is in Michael's span of control, Dwight & Stanley are in Jim's span of control):
source_id source target_id target
1 Michael Scott 5 Kelly Kapoor
3 Dwight Schrute 2 Jim Halpert
4 Stanley Hudson 2 Jim Halpert
2 Jim Halpert 5 Kelly Kapoor
I have a table that lists each person and their supervisor:
person_id person supervisor_id supervisor
1 Michael Scott 0 None
2 Jim Halpert 1 Michael Scott
3 Dwight Schrute 2 Jim Halpert
4 Stanley Hudson 2 Jim Halpert
6 Ryan Howard 1 Michael Scott
5 Kelly Kapoor 6 Ryan Howard
I have a block of code that uses recursion to find a single person's span of control from the preceding table:
with anchor as
(
select person_id, supervisor_id from table where unique_id = #ID
union all
select a.person_id, a.supervisor_id
from table a
inner join Anchor b ON b.person_id = a.supervisor_id
)
select a.person_id
from anchor a
This block of code can be turned into a stored procedure or a function. Running it for Jim (for example), returns:
person_id
3 (Dwight Schrute)
4 (Stanley Hudson)
How do I compare each value in the initial dataset (from both the source and target columns) to the values in the column returned by the preceding block of code? In other words, for each row in source, I need to check if that name is within the span of control of target. In addition, for each row in target, I need to check if that name is within the span of control of source.
Desired End Result:
source_id source target_id target isEitherPersonInOther'sSOC
1 Michael Scott 5 Kelly Kapoor Yes
3 Dwight Schrute 2 Jim Halpert Yes
4 Stanley Hudson 2 Jim Halpert Yes
2 Jim Halpert 5 Kelly Kapoor No
I know iterations are bad (i.e. running the stored procedure for each row with a cursor or while loop). I also know cross apply and a function together may work, but I have been unable to figure my way through that.
Thank you for any insight you all may have!
What it looks like you need is to take your recursive CTE and output ID pairs into a temp table. I'm picturing something like this:
DECLARE #id int
DECLARE cx CURSOR FOR
SELECT person_id FROM PersonSupervisor
CREATE TABLE #tmpPS (Supervisor int, personInChain int)
OPEN cx
FETCH NEXT FROM cx
INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
with anchor as
(
select person_id, supervisor_id from table where unique_id = #ID
union all
select a.person_id, a.supervisor_id
from table a
inner join Anchor b ON b.person_id = a.supervisor_id
)
INSERT INTO #tmpPS
select #id, a.person_id
from anchor a
FETCH NEXT FROM cx
INTO #id
END
Close cx
Deallocate cx
This creates a table of all relationships, recursively expanded. Then you can output whether any given person is either above or below a given other person with this subquery. Add it to whatever query outputs your base grid:
SELECT
SourceId,
Source,
TargetId,
Target,
CASE
WHEN EXISTS (SELECT 1 FROM #tmpPS WHERE Supervisor = SourceId and PersonInChain = TargetId)
THEN 'Yes'
WHEN EXISTS (SELECT 1 FROM #tmpPS WHERE Supervisor = TargetId and PersonInChain = SourceId)
THEN 'Yes'
ELSE 'No'
END as [isEitherPersonInOther'sSOC]
FROM ....
This also implies a version of this where you can separate the relationships out - If the first query, the TargetId is a subordinate of the SourceId. If the second query, then TargetId is a superior to SourceId.
There has to be a better way to do this but this is definitely one way to go about it -- note the code creates permanent tables to use in a function:
create table dbo.[source]
(
source_id int,
[source] nvarchar(500),
target_id int,
[target] nvarchar(500)
)
insert into [source]
select 1, 'Michael Scott', 5, 'Kelly Kapoor'
union all select 3,'Dwight Schrute',2,'Jim Halpert'
union all select 4 ,'Stanley Hudson',2,'Jim Halpert'
union all select 2 ,'Jim Halpert',5,'Kelly Kapoor'
create table dbo.supervisors
(
person_id int,
person nvarchar(500),
supervisor_id int,
supervisor nvarchar(500)
)
insert into dbo.supervisors
select 1,'Michael Scott', 0,'None'
union all select 2,'Jim Halpert',1,'Michael Scott'
union all select 3,'Dwight Schrute',2,'Jim Halpert'
union all select 4,'Stanley Hudson',2,'Jim Halpert'
union all select 6,'Ryan Howard',1,'Michael Scott'
union all select 5 ,'Kelly Kapoor',6,'Ryan Howard'
go
create function dbo.fn_isinspanofcontrol
(
#sourceid int,
#targetid int
)
RETURNS varchar(1)
as
begin
declare #retVal varchar(1)
declare #tbl table
(
person_id int
)
;with anchor as
(
select person_id, supervisor_id from supervisors where person_id = #sourceid
union all
select a.person_id, a.supervisor_id
from supervisors a
inner join Anchor b ON b.person_id = a.supervisor_id
)
insert into #tbl
select a.person_id
from anchor a
where
a.person_id = #targetid
;with anchor as
(
select person_id, supervisor_id from supervisors where person_id = #targetid
union all
select a.person_id, a.supervisor_id
from supervisors a
inner join Anchor b ON b.person_id = a.supervisor_id
)
insert into #tbl
select a.person_id
from anchor a
where
a.person_id = #sourceid
if exists( select 1
from #tbl
)
begin
set #retVal = 'Y'
end
else
begin
set #retVal = 'N'
end
return #retVal
end
select
*, dbo.fn_isinspanofcontrol(source_id,target_id)
from [source]

Select all hierarchy level and below SQL Server

I am having a difficult time with this one. I have seen a few examples on how to obtain all child records from a self referencing table given a parent and even how to get the parents of child records.
What I am trying to do is return a record and all child records given the ID.
To put this into context - I have a corporate hierarchy. Where:
#Role Level#
--------------------
Corporate 0
Region 1
District 2
Rep 3
What I need is a procedure that (1) figures out what level the record is and (2) retrieves that record and all children records.
The idea being a Region can see all districts and reps in a district, Districts can see their reps. Reps can only see themselves.
I have table:
ID ParentId Name
-------------------------------------------------------
1 Null Corporate HQ
2 1 South Region
3 1 North Region
4 1 East Region
5 1 West Region
6 3 Chicago District
7 3 Milwaukee District
8 3 Minneapolis District
9 6 Gold Coast Dealer
10 6 Blue Island Dealer
How do I do this:
CREATE PROCEDURE GetPositions
#id int
AS
BEGIN
--What is the most efficient way to do this--
END
GO
For example the expected result for #id = 3, I would want to return:
3, 6, 7, 8, 9, 10
I'd appreciate any help or ideas on this.
You could do this via a recursive CTE:
DECLARE #id INT = 3;
WITH rCTE AS(
SELECT *, 0 AS Level FROM tbl WHERE Id = #id
UNION ALL
SELECT t.*, r.Level + 1 AS Level
FROM tbl t
INNER JOIN rCTE r
ON t.ParentId = r.ID
)
SELECT * FROM rCTE OPTION(MAXRECURSION 0);
ONLINE DEMO
Assuming that you're on a reasonably modern version of SQL Server, you can use the hierarchyid datatype with a little bit of elbow grease. First, the setup:
alter table [dbo].[yourTable] add [path] hierarchyid null;
Next, we'll populate the new column:
with cte as (
select *, cast(concat('/', ID, '/') as varchar(max)) as [path]
from [dbo].[yourTable]
where [ParentID] is null
union all
select child.*,
cast(concat(parent.path, child.ID, '/') as varchar(max)) as [path]
from [dbo].[yourTable] as child
join cte as parent
on child.ParentID = parent.ID
)
update t
set path = c.path
from [dbo].[yourTable] as t
join cte as c
on t.ID = c.ID;
This is just a bog standard recursive table expression with one calculated column that represents the hierarchy. That's the hard part. Now, your procedure can look something like this:
create procedure dbo.GetPositions ( #id int ) as
begin
declare #h hierarchyid
set #h = (select Path from [dbo].[yourTable] where ID = #id);
select ID, ParentID, Name
from [dbo].[yourTable]
where Path.IsDescendentOf(#h) = 1;
end
So, to wrap up, all you're doing with the hierarchyid is storing the lineage for a given row so that you don't have to calculate it on the fly at select time.

How do you find a missing number in a table field starting from a parameter and incrementing sequentially?

Let's say I have an sql server table:
NumberTaken CompanyName
2 Fred 3 Fred 4 Fred 6 Fred 7 Fred 8 Fred 11 Fred
I need an efficient way to pass in a parameter [StartingNumber] and to count from [StartingNumber] sequentially until I find a number that is missing.
For example notice that 1, 5, 9 and 10 are missing from the table.
If I supplied the parameter [StartingNumber] = 1, it would check to see if 1 exists, if it does it would check to see if 2 exists and so on and so forth so 1 would be returned here.
If [StartNumber] = 6 the function would return 9.
In c# pseudo code it would basically be:
int ctr = [StartingNumber]
while([SELECT NumberTaken FROM tblNumbers Where NumberTaken = ctr] != null)
ctr++;
return ctr;
The problem with that code is that is seems really inefficient if there are thousands of numbers in the table. Also, I can write it in c# code or in a stored procedure whichever is more efficient.
Thanks for the help
Fine, if this question isn't going to be closed, I may as well Copy and paste my answer from the other one:
I called my table Blank, and used the following:
declare #StartOffset int = 2
; With Missing as (
select #StartOffset as N where not exists(select * from Blank where ID = #StartOffset)
), Sequence as (
select #StartOffset as N from Blank where ID = #StartOffset
union all
select b.ID from Blank b inner join Sequence s on b.ID = s.N + 1
)
select COALESCE((select N from Missing),(select MAX(N)+1 from Sequence))
You basically have two cases - either your starting value is missing (so the Missing CTE will contain one row), or it's present, so you count forwards using a recursive CTE (Sequence), and take the max from that and add 1
Tables:
create table Blank (
ID int not null,
Name varchar(20) not null
)
insert into Blank(ID,Name)
select 2 ,'Fred' union all
select 3 ,'Fred' union all
select 4 ,'Fred' union all
select 6 ,'Fred' union all
select 7 ,'Fred' union all
select 8 ,'Fred' union all
select 11 ,'Fred'
go
I would create a temp table containing all numbers from StartingNumber to EndNumber and LEFT JOIN to it to receive the list of rows not contained in the temp table.
If NumberTaken is indexed you could do it with a join on the same table:
select T.NumberTaken -1 as MISSING_NUMBER
from myTable T
left outer join myTable T1
on T.NumberTaken= T1.NumberTaken+1
where T1.NumberTaken is null and t.NumberTaken >= STARTING_NUMBER
order by T.NumberTaken
EDIT
Edited to get 1 too
1> select 1+ID as ID from #b as b
where not exists (select 1 from #b where ID = 1+b.ID)
2> go
ID
-----------
5
9
12
Take max(1+ID) and/or add your starting value to the where clause, depending on what you actually want.

Picking info using junction table(SQL SERVER 2005) [ SET BASED]

I have 3 tables
1) tblPurchaser having 2 columns:
PurchaserId PurchaserName
1 A1
2 A2
3 A3
2) tblCar having 2 columns:
CarId Carname
11 C1
12 C2
13 C3
14 C4
And the last is a junction table tblInformation where the information about those persons are given who has purchased cars.
PurchaserId CarId
1 11
1 12
2 11
2 13
Now I need to write a set based query where I can be able to obtain the information of those cars which has not been purchased by the persons
Desired Output
PurchaserId CarId
1 13
1 14
2 12
2 14
3 11
3 12
3 13
3 14
Note: This is a real time problem which I am implementing in my project. Because of privacy of company, I have changed the tables and information. But my situation is something similar
Please help me
Edited
So far I have written this query:
SELECT 1 as purchaserid,carid from tblcar
where carid not in (select carid from tblinformation where purchaserid = 1)
union all
SELECT 2 as purchaserid,carid from tblcar
where carid not in (select carid from tblinformation where purchaserid = 2)
union all
SELECT 3 as purchaserid,carid from tblcar
where carid not in (select carid from tblinformation where purchaserid = 3)
But as you can make out that i am hardcoding the purchaserid's. And also in real time I will not know how many id's will be there. So everything has to be done at runtime.
Please helpenter code here
Clue: NOT EXISTS
You should really try to do some homework yourself... 3rd question today...
LEFT JOIN ... WHERE ... IS NULL to the rescue:
SELECT tblPurchaser.PurchaserId, tblCar.CarId
FROM tblPurchaser JOIN tblCar
LEFT JOIN tblInformation ON(
tblPurchaser.PurchaserId = tblInformation.PurchaserId
AND tblCar.CarId = tblInformation.CarId)
WHERE tblInformation.CarId IS NULL
Try this
select pur.PurchaserId, car.CarId
from tblPurchaser pur, tblCar car
where not exists (select 1 from tblInformation where PurchaserId = pur. PurchaserId and CarId = car. CarId)
order by pur.PurchaserId;
Try this:
SELECT PurchaserID, CarID
FROM Purchasers
CROSS JOIN Cars
EXCEPT
SELECT *
FROM tblInformation
Here is a SQL script that demonstrates that this technique works correctly:
declare #soPurchaser table(PurchaserId int, PurchaserName varchar(4));
insert #soPurchaser select 1,'A1'
insert #soPurchaser select 2,'A2'
insert #soPurchaser select 3,'A3'
Declare #SOtblCar table(CarId int, Carname varchar(4))
insert #SOtblCar select 11,'C1'
insert #SOtblCar select 12,'C2'
insert #SOtblCar select 13,'C3'
insert #SOtblCar select 14,'C4'
Declare #SOtblInfo table(PurchaserId int, CarId int)
insert #SOtblInfo select 1,11
insert #SOtblInfo select 1,12
insert #SOtblInfo select 2,11
insert #SOtblInfo select 2,13
SELECT PurchaserID, CarID
FROM #soPurchaser
CROSS JOIN #SOtblCar
EXCEPT
SELECT *
FROM #SOtblInfo
The SQL Set operators (UNION, INTERSECT, and EXCEPT) all operate on two table-sets. You will note that they have no way to map the columns from one set to the other. In all cases in SQL when column must be mapped to each other, but there is no syntax to do it explicitly, then they are always mapped based on column order.
So in this one case, if you have one of the table's column order wrong, then it will not work correctly.