Prioritized client segments - sql-server

My customers own products from different product groups. Example:
Client Product group
1 All-in-one
1 Senior
2 All-in-one
2 Other
3 Senior
3 Other
4 Other
The product groups are prioritized, so that if you own products from both the all-in-one and the senior product group you would be classified over all as an All-in-one customer.
The product groups prioritized are:
All-in-one
Senior
Other
I would like a view where each customer only appear once like:
Client Product group
1 All-in-one
2 All-in-one
3 Senior
4 Other
Can this be done in a single step without a ton of views?

First one works if only those product groups remain,second one you have flexibilty to order but uses ctes
select id,min(pg) from #client
group by id
;with cte
as
(
select
id,
min(case when pg='All-in-one' then 1
when pg='senior' then 2
when pg='other' then 3
end ) pg
from
#client
group by id
)
select id,
case pg when 1 then 'All-in-one'
when 2 then 'Senior'
when 3 then 'other'
end as 'PG'
from cte

Try this (you dont need use clients_groups cte because you have this table already):
;WITH clients_groups AS (
SELECT *
FROM (VALUES
(1, 'All-in-one'),
(1, 'Senior'),
(2, 'All-in-one'),
(2, 'Other'),
(3, 'Senior'),
(3, 'Other'),
(4, 'Other')) as cg (Client, Product_group)
),
final AS (
SELECT ROW_NUMBER() OVER(PARTITION BY Client ORDER BY id Asc) AS R,g.Client, p.Name
FROM clients_groups g
LEFT JOIN (VALUES
(1, 'All-in-one'),
(2, 'Senior'),
(3, 'Other')) as p(id, name) ON g.Product_group = p.name)
SELECT Client,Name
FROM final
WHERE R = 1
Results:
Client Name
----------- ----------
1 All-in-one
2 All-in-one
3 Senior
4 Other
(4 row(s) affected)

Related

Most Efficient Way to Query a Table with Children, Grandchildren, etc

I have a table with a list of Jobs that have children, grandchildren, etc. There is no limit on the level of hierarchy it goes down. The table has ID, Name, and ParentID. So for example, a Job table could look like:
ID Name Parent ID
1 Education null
2 IT null
3 Teacher 1
4 MS Teacher 3
5 7th Grade 4
6 Sys Admin 2
7 HS Teacher 3
8 12th Grade 7
9 IT Support 6
10 Developer 2
There is also a UserToJob table that is just the JobID and UserID. A person could be listed in more than one Job.
I'm looking for the most efficient way to get all people with a specified job and all decedents, so if I want to query for Education then it returns Education, Teacher, MS Teacher, 7th Grade, HS Teacher, and 12th Grade.
Right now my best attempt looks like
with
Closure AS (
select j.ID as AncestorID, j.ID as DescendantID, 0 as Depth from Jobs j
UNION ALL
select CTE.AncestorID, j.ID, CTE.Depth + 1 from Jobs j
inner join Closure CTE on j.ParentID = CTE.DescendantID
),
Job AS ( select j.UserID as ID from UserToJob j
where j.JobID in (select DescendantID from Closure where AncestorID in (1))
)
I want it to be able to work querying more than one job at a time, for example if I wanted all Education and Sys Admins then I'd change AncestorID in (1) to AncestorID in (1, 6) in the final line of my attempt.
You have your where clause in the wrong place. You want to limit the root of your recursive cte to only return the first level rows you are concerned with.
This should point you in the right direction.
declare #Jobs table
(
ID int
, Name varchar(50)
, ParentID int
)
insert #Jobs values
(1, 'Education', null)
, (2, 'IT', null)
, (3, 'Teacher', 1)
, (4, 'MS Teacher', 3)
, (5, '7th Grade', 4)
, (6, 'Sys Admin', 2)
, (7, 'HS Teacher', 3)
, (8, '12th Grade', 7)
, (9, 'IT Support', 6)
, (10, 'Developer', 2)
select *
from #Jobs;
with Closure AS
(
select j.ID as AncestorID
, j.ID as DescendantID
, 0 as Depth
from #Jobs j
where j.ID in (1, 6)
UNION ALL
select CTE.AncestorID
, j.ID
, CTE.Depth + 1
from #Jobs j
inner join Closure CTE on j.ParentID = CTE.DescendantID
)
select *
from Closure

Selecting the smallest value in one column per group

I have a table that looks like the following which was created using the following code...
SELECT Orders.ID, Orders.CHECKIN_DT_TM, Orders.CATALOG_TYPE,
Orders.ORDER_STATUS, Orders.ORDERED_DT_TM, Orders.COMPLETED_DT_TM,
Min(DateDiff("n",Orders.ORDERED_DT_TM,Orders.COMPLETED_DT_TM)) AS
Time_to_complete
FROM Orders
GROUP BY Orders.ORDER_ID, Orders.ID,
Orders.CHECKIN_DT_TM, Orders.CATALOG_TYPE, Orders.ORDERED_DT_TM,
Orders.COMPLETED_DT_TM, HAVING (((Orders.CATALOG_TYPE)="radiology");
ID Time_to_complete ... .....
1 5
1 7
1 8
2 23
2 6
3 7
4 16
4 14
I'd like to add to this code which would select the smallest Time_to_complete value per subject ID. Leaving the desired table:
ID Time_to_complete ... .....
1 5
2 6
3 7
4 14
I'm using Access and prefer to continue using Access to finish this code but I do have the option to use SQL Server if this is not possible in Access. Thanks!
I suspect you need correlated subquery :
SELECT O.*, DateDiff("n", O.ORDERED_DT_TM, O.COMPLETED_DT_TM) AS Time_to_complete
FROM Orders O
WHERE DateDiff("n", O.ORDERED_DT_TM, O.COMPLETED_DT_TM) = (SELECT Min(DateDiff("n", O1.ORDERED_DT_TM, O1.COMPLETED_DT_TM))
FROM Orders O1
WHERE O1.ORDER_ID = O.ORDER_ID AND . . .
);
EDIT : If you want unique records then you can do instead :
SELECT O.*, DateDiff("n", O.ORDERED_DT_TM, O.COMPLETED_DT_TM) AS Time_to_complete
FROM Orders O
WHERE o.pk = (SELECT TOP (1) o1.pk
FROM Orders O1
WHERE O1.ORDER_ID = O.ORDER_ID AND . . .
ORDER BY DateDiff("n", O.ORDERED_DT_TM, O.COMPLETED_DT_TM) ASC
);
pk is your identity column that specifies unique entry in Orders table, so you can change it accordingly.
Have a look at this:
DECLARE #myTable AS TABLE (ID INT, Time_to_complete INT);
INSERT INTO #myTable
VALUES (1, 5)
, (1, 7)
, (1, 8)
, (2, 23)
, (2, 6)
, (3, 7)
, (4, 16)
, (4, 14);
WITH cte AS
(SELECT *
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Time_to_complete) AS RN
FROM #myTable)
SELECT cte.ID
, cte.Time_to_complete
FROM cte
WHERE RN = 1;
Results :
ID Time_to_complete
----------- ----------------
1 5
2 6
3 7
4 14
It uses row numbers over groups, then selects the first row for each group. You should be able to adjust your code to use this technique. If in doubt wrap your entire query in a cte first then apply the technique here.
It's worth becoming familiar with this process as it gets used in a lot of places - especially around de-duping data.
Try This
DECLARE #myTable AS TABLE (ID INT, Time_to_complete INT);
INSERT INTO #myTable
VALUES (1, 5)
, (1, 7)
, (1, 8)
, (2, 23)
, (2, 6)
, (3, 7)
, (4, 16)
, (4, 14);
SELECT O.ID, O.Time_to_complete
FROM #myTable O
WHERE o.Time_to_complete = (Select min(m.Time_to_complete) FROM #myTable m
Where o.id=m.ID
);
Result :
ID Time_to_complete
1 5
2 6
3 7
4 14

select company 2's value if exists, company 1's value if not

Windows Server 2012, MS SQL Server
I know there's a set-based way to do this, but I can't figure out how to concisely phrase my question to get a useful google answer.
tblConfig
companyid var val
---------------------------------------------------------
1 fruit orange
1 game Monopoly
1 book Joyland
1 actor Ernest Thesiger
1 condiment ketchup
2 fruit apple
2 book Revival
3 actor Colin Clive
3 condiment relish
3 fruit kiwi
3 book Tales From a Buick8
I would like to select company 2's values (or 3, or 4, or n...), plus company 1's values where 2 doesn't have one (order doesn't matter), as in:
2 fruit apple
1 game Monopoly
2 book Revival
1 actor Ernest Thesiger
1 condiment ketchup
I've looked at this answer and thought I could make it work, but it eludes me. I just end up with a list of all values in the table.
You are looking for a prioritization query. In SQL Server, you can do this using row_number():
select t.*
from (select t.*,
row_number() over (partition by var
order by (case when companyid = 2 then 1
when companyid = 1 then 2
end)
) as seqnum
from t
) t
where seqnum = 1;
The logic for prioritization is in the order by clause for row_number().
Declare #YourTable table (companyid int,var varchar(50), val varchar(50))
Insert into #YourTable values
(1,'fruit','orange'),
(1,'game','Monopoly'),
(1,'book','Joyland'),
(1,'actor','Ernest Thesiger'),
(1,'condiment','ketchup'),
(2,'fruit','apple'),
(2,'book','Revival'),
(3,'actor','Colin Clive'),
(3,'condiment','relish'),
(3,'fruit','kiwi'),
(3,'book','Tales From a Buick8')
;with cteBase as (
Select *
,RowNr=Row_Number() over (Partition By var order by companyid Desc)
From #YourTable
Where companyid<=2
)
Select * from cteBase where RowNr=1
Returns
companyid var val RowNr
1 actor Ernest Thesiger 1
2 book Revival 1
1 condiment ketchup 1
2 fruit apple 1
1 game Monopoly 1

Compare the most recent row with the immediate previous in the same table

I am facing this problem where I need to compare the most recent row with the immediate previous one based on the same criteria (it will be trader in this case).
Here is my table:
ID Trader Price
-----------------
1 abc 5
2 xyz 5.2
3 abc 5.7
4 xyz 5
5 abc 5.2
6 abc 6
Here is the script
CREATE TABLE Sale
(
ID int not null PRIMARY KEY ,
trader varchar(10) NOT NULL,
price decimal(2,1),
)
INSERT INTO Sale (ID,trader, price)
VALUES (1, 'abc', 5), (2, 'xyz', 5.2),
(3, 'abc', 5.7), (4, 'xyz', 5),
(5, 'abc', 5.2), (6, 'abc', 6);
So far I am working with this solution that is not perfect yet
select
a.trader,
(a.price - b.price ) New_price
from
sale a
join
sale b on a.trader = b.trader and a.id > b.ID
left outer join
sale c on a.trader = c.trader and a.id > c.ID and b.id < c.ID
where
c.ID is null
Above is not perfect because I want to compare only the most recent with the immediate previous on... In this sample for example
Trader abc : I will compare only id = 6 and id = 5
Trader xyz : id = 4 and id = 2
Thanks for any help!
If you are using SQL Server 2012 or later, you can use functions LEAD and LAG to join previous and next data. Unfortunately these function can only be used in SELECT or ORDER BY clause, so you will need to use subquery to get the data you need:
SELECT t.trader, t.current_price - t.previous_price as difference
FROM (
SELECT
a.trader,
a.price as current_price,
LAG(a.price) OVER(PARTITION BY a.trader ORDER BY a.ID) as previous_price,
LEAD(a.price) OVER(PARTITION BY a.trader ORDER BY a.ID) as next_price
FROM sale a
) t
WHERE t.next_price IS NULL
Here in your subquery you create additional columns for previous and next value. Then in your main query you filter only these rows where next price is NULL - that indicates this is the last row for the specific trader.

Search within ColA duplicates against specific unique vals in ColB to exclude all of ColA

I apologize in advance I feel like I'm missing something really stupid simple. (and let's ignore database structure as I'm kind of locked into that).
I have, let's use customer orders - an order number can be shipped to more than one place. For the sake of ease I'm just illustrating three but it could be more than that (home, office, gift, gift2, gift 3, etc)
So my table is:
Customer orders:
OrderID MailingID
--------------------
1 1
1 2
1 3
2 1
3 1
3 3
4 1
4 2
4 3
What I need to find is OrderIDs that have been shipped to MailingID 1 but not 2 (basically what I need to find is orderID 2 and 3 above).
If it matters, I'm using Sql Express 2012.
Thanks
Maybe this could help:
create table #temp(
orderID int,
mailingID int
)
insert into #temp
select 1, 1 union all
select 1, 2 union all
select 1, 3 union all
select 2, 1 union all
select 3, 1 union all
select 3, 3 union all
select 4, 1 union all
select 4, 2 union all
select 4, 3
-- find orderIDs that have been shipeed to mailingID = 1
select
distinct orderID
from #temp
where mailingID = 1
except
-- find orderIDs that have been shipeed to mailingID = 2
select
orderID
from #temp
where mailingID = 2
drop table #temp
A simple Subquery With NOT IN Operator should work.
SELECT DISTINCT OrderID
FROM <tablename> a
WHERE orderid NOT IN (SELECT orderid
FROM <tablename> b
WHERE b.mailingID = 2)

Resources