From several lines a hierarchy into one line - sql

I have a table with data hierarchy
CREATE TABLE my_table(
object_id varchar,
parent_id varchar
);
INSERT INTO my_table(object_id , parent_id)
VALUES
('1', '0'),
('2', '0'),
('3', '1'),
('4', '1'),
('5', '1'),
('6', '3'),
('7', '2'),
('8', '2');
object_id
parent_id
1
0
2
0
3
1
4
1
5
1
6
3
7
2
8
2
I need to tranformation to
object_id
parent_id
{1,3,4,5,6},{2,7,8}
0
I think there is no way to do without an intermediate table
So far, I have only found this request, but I don’t know which way to dig further
SELECT parent_id, array_to_string(array_agg(distinct(object_id)), ' , ', '') AS object_id
FROM my_table
GROUP BY parent_id;
Thx

Related

Combine rows into columns in SQL Server in a particular way

I don't know how to describe my problem, but I'll do my best. I'm working in SQL Server 2014. I've simplified the problem as much as I can since I'm working with sensitive info.
I currently have a query that returns the following from a table of test answers:
test_id
question_id
is_checked
1
1
TRUE
1
2
TRUE
1
3
FALSE
1
4
FALSE
2
1
FALSE
2
2
FALSE
2
3
FALSE
2
4
TRUE
3
1
FALSE
3
2
FALSE
3
3
FALSE
3
4
FALSE
Each test has only 4 yes/no questions (and this is unlikely to ever change). For each test, one or more questions can be marked yes. Above...
test 1 has questions 1 and 2 as yes, the rest as no.
test 2 has question 4 as yes, the rest as no.
test 3 has all questions marked as no.
I want my results to look like this:
test_id
question_1
question_2
question_3
question_4
1
TRUE
TRUE
FALSE
FALSE
2
FALSE
FALSE
FALSE
TRUE
3
FALSE
FALSE
FALSE
FALSE
I tried to use PIVOT to no luck. Any help would be appreciated, and I'm happy to provide more info.
EDIT:
My attempt at using PIVOT (please forgive my likely horrible formatting):
SELECT *
FROM (
SELECT test_id, question_id, is_checked FROM example_table
) as sourcetable
pivot(
any(is_checked)
for question_id
in (question_1, question_2, question_3, question_4)
) as pivottable
Populating an example table based on the above:
CREATE TABLE example_table (test_id int, question_id int, is_checked bit);
INSERT INTO example_table (test_id, question_id, is_checked)
VALUES
('1', '1', '1'),
('1', '2', '1'),
('1', '3', '0'),
('1', '4', '0'),
('2', '1', '0'),
('2', '2', '0'),
('2', '3', '0'),
('2', '4', '1'),
('3', '1', '0'),
('3', '2', '0'),
('3', '3', '0'),
('3', '4', '0');
Finally, my SQL Server version is SQL Server 2014. I previously put SQL Server 17 above, but have corrected it.
FINAL EDIT:
The column is_checked is a bit type in my system, but someone must have set it to output TRUE and FALSE when queried. In the answer below, I replaced is_checked with CAST(is_checked AS INT) and that worked.
Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (test_ID INT, question_id INT, is_checked VARCHAR(5));
INSERT INTO #tbl (test_ID, question_id, is_checked) VALUES
(1, 1, 'TRUE'),
(1, 2, 'TRUE'),
(1, 3, 'FALSE'),
(1, 4, 'FALSE'),
(2, 1, 'FALSE'),
(2, 2, 'FALSE'),
(2, 3, 'FALSE'),
(2, 4, 'TRUE'),
(3, 1, 'FALSE'),
(3, 2, 'FALSE'),
(3, 3, 'FALSE'),
(3, 4, 'FALSE');
-- DDL and sample data population, end
SELECT test_ID
, MAX(IIF(question_id = 1, is_checked, '')) AS question_1
, MAX(IIF(question_id = 2, is_checked, '')) AS question_2
, MAX(IIF(question_id = 3, is_checked, '')) AS question_3
, MAX(IIF(question_id = 4, is_checked, '')) AS question_4
FROM #tbl
GROUP BY test_ID
ORDER BY test_ID;
Output
+---------+------------+------------+------------+------------+
| test_ID | question_1 | question_2 | question_3 | question_4 |
+---------+------------+------------+------------+------------+
| 1 | TRUE | TRUE | FALSE | FALSE |
| 2 | FALSE | FALSE | FALSE | TRUE |
| 3 | FALSE | FALSE | FALSE | FALSE |
+---------+------------+------------+------------+------------+

Building tree type structure of a network

I am a GIS specialist working on optic fiber infrastructure
Newbie in SQL (Microsoft SQL Server) I'm currently trying to build a tree like structure of our network from root to customer
I have already built a very repetitive query that consists of one root query, to which I join the same query over and over until I reach the end of the network.
SELECT DISTINCT *
FROM
(
SELECT DISTINCT
B.bay,
B.drawer,
B.connector,
C.cable_code,
F.tube,
F.fiber,
F.route_id,
C.cable_origin,
C.cable_destination
FROM cable_table C
INNER JOIN fiber_table F ON F.cable_code = C.cable_code
INNER JOIN bay_table B ON B.cable_code = C.cable_code
WHERE B.tubeid = F.tubeid
AND B.fiberid = F.fiberid
) root
LEFT JOIN
(
SELECT DISTINCT
C.cable_code cable_code1,
F.tube tube1,
F.fiber fiber1,
F.route_id route_id1,
C.cable_origin cable_origin1,
C.cable_destination cable_destination1
FROM cable_table C
INNER JOIN fiber_table F ON F.cable_code = C.cable_code
) cable1
ON root.route_id = cable1.route_id1
AND root.cable_destination = cable1.cable_origin1
ORDER BY root.bay, root.drawer, root.connector, root.cable_code, root.tube, root.fiber
--From there I join the new query to the last, using the same column name, only changing the last number by 2, 3, 4 ...
Rules : Connectors, tubes, fibers are not always lined up like fiber 1 from tube 1 on connector 1. It can be mixed up. Fiber 12 on tube 22 connector 79
Here is an example of the expected result :
+------------------+--------------------------+-------------+--------------------+------+-------+----------+------------------+--------------------+--------------------+-------+--------+-----------+--------------------+--------------------+
| bay | drawer | connector | cable_code | tube | fiber | route_id | cable_origin | cable_destination | cable_code1 | tube1 | fiber1 | route_id1 | cable_origin1 | cable_destination1 |
+------------------+--------------------------+-------------+--------------------+------+-------+----------+------------------+--------------------+--------------------+-------+--------+-----------+--------------------+--------------------+
| PMU-25-003-EG-01 | TIR-25-003-EG-01-A2-01-A | CO000000001 | CDI-25-003-EG-A005 | 1 | 1 | 404979 | PMU-25-003-EG-01 | PBO-25-003-EG-A005 | CDI-25-003-EG-A010 | 1 | 1 | 404979 | PBO-25-003-EG-A005 | PEC-25-003-EG-A010 |
| PMU-25-003-EG-01 | TIR-25-003-EG-01-A2-01-A | CO000000002 | CDI-25-003-EG-A005 | 1 | 2 | 951378 | PMU-25-003-EG-01 | PBO-25-003-EG-A005 | CDI-25-003-EG-A010 | 1 | 2 | 951378 | PBO-25-003-EG-A005 | PEC-25-003-EG-A010 |
| PMU-25-003-EG-01 | TIR-25-003-EG-01-A2-01-A | CO000000003 | CDI-25-003-EG-A005 | 1 | 3 | 404981 | PMU-25-003-EG-01 | PBO-25-003-EG-A005 | CDI-25-003-EG-A010 | 1 | 3 | 404981 | PBO-25-003-EG-A005 | PEC-25-003-EG-A010 |
+------------------+--------------------------+-------------+--------------------+------+-------+----------+------------------+--------------------+--------------------+-------+--------+-----------+--------------------+--------------------+
The tree can have up to 20 nodes so my query consists of the root and x20 cable query. It's very repetitive and I doubt it's very efficient SQL wise. That's why i'm here, I now it's the a 'I am missing the trick moment' and I need your help to build a better (Recursive ?) query that can prevent having 20 times the same block and possibly speed up process time.
Here a script with some sample of my different tables
CREATE TABLE cable_table
(
[cable_code] [varchar](255) NULL,
[cable_origin] [varchar](255) NULL,
[cable_destination] [varchar](255) NULL
)
INSERT INTO cable_table
VALUES
('CDI-25-003-EG-A005','PMU-25-003-EG-01', 'PBO-25-003-EG-A005'),
('CDI-25-003-EG-B005','PMU-25-003-EG-01', 'PBO-25-003-EG-B005'),
('CDI-25-003-EG-C005','PMU-25-003-EG-01', 'PBO-25-003-EG-C005'),
('CDI-25-003-EG-A010','PBO-25-003-EG-A005', 'PBO-25-003-EG-A010'),
('CDI-25-003-EG-B010','PBO-25-003-EG-B005', 'PBO-25-003-EG-B010'),
('CDI-25-003-EG-C010','PBO-25-003-EG-C005', 'PBO-25-003-EG-C010'),
('CDI-25-003-EG-A015','PBO-25-003-EG-A010', 'PBO-25-003-EG-A015'),
('CDI-25-003-EG-B015','PBO-25-003-EG-B010', 'PBO-25-003-EG-B015'),
('CDI-25-003-EG-C015','PBO-25-003-EG-C010', 'PBO-25-003-EG-C015')
CREATE TABLE fiber_table
(
[cable_code] [varchar](255) NULL,
[route_id] [bigint] NULL,
[tubeid] [bigint] NULL,
[tube] [int] NULL,
[fiberid] [bigint] NULL,
[fiber] [int] NULL
)
INSERT INTO fiber_table
VALUES
('CDI-25-003-EG-A005', '484979', '1800', '1', '187371', '1'),
('CDI-25-003-EG-A005', '959378', '1800', '1', '187372', '2'),
('CDI-25-003-EG-A005', '472981', '1800', '1', '187373', '3'),
('CDI-25-003-EG-A005', '752894', '1800', '1', '187374', '4'),
('CDI-25-003-EG-B005', '457883', '1800', '1', '187381', '1'),
('CDI-25-003-EG-B005', '457236', '1800', '1', '187382', '2'),
('CDI-25-003-EG-B005', '997855', '1800', '1', '187383', '3'),
('CDI-25-003-EG-B005', '374564', '1800', '1', '187384', '4'),
('CDI-25-003-EG-C005', '455883', '1800', '1', '187391', '1'),
('CDI-25-003-EG-C005', '785696', '1800', '1', '187392', '2'),
('CDI-25-003-EG-C005', '453453', '1800', '1', '187393', '3'),
('CDI-25-003-EG-C005', '777784', '1800', '1', '187394', '4'),
('CDI-25-003-EG-A010', '484979', '1800', '1', '187541', '1'),
('CDI-25-003-EG-A010', '959378', '1800', '1', '187542', '2'),
('CDI-25-003-EG-A010', '472981', '1800', '1', '187543', '3'),
('CDI-25-003-EG-A010', '752894', '1800', '1', '187544', '4'),
('CDI-25-003-EG-B010', '457883', '1800', '1', '187641', '1'),
('CDI-25-003-EG-B010', '457236', '1800', '1', '187642', '2'),
('CDI-25-003-EG-B010', '997855', '1800', '1', '187643', '3'),
('CDI-25-003-EG-C010', '785696', '1800', '1', '189642', '2'),
('CDI-25-003-EG-A015', '484979', '1800', '1', '189881', '1'),
('CDI-25-003-EG-A015', '959378', '1800', '1', '189882', '2'),
('CDI-25-003-EG-A015', '472981', '1800', '1', '189883', '3'),
('CDI-25-003-EG-A015', '752894', '1800', '1', '189884', '4'),
('CDI-25-003-EG-B015', '457883', '1800', '1', '188771', '1'),
('CDI-25-003-EG-B015', '457236', '1800', '1', '188772', '2'),
('CDI-25-003-EG-B015', '997855', '1800', '1', '188773', '3'),
('CDI-25-003-EG-C015', '785696', '1800', '1', '188772', '2')
CREATE TABLE bay_table
(
[bay] [varchar](255) NULL,
[drawer] [varchar](255) NULL,
[connector] [varchar](255) NULL,
[cable_code] [varchar](255) NULL,
[tubeid] [bigint] NULL,
[fiberid] [bigint] NULL,
)
INSERT INTO bay_table
VALUES
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000001', 'CDI-25-003-EG-A005', '1800', '187371'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000002', 'CDI-25-003-EG-A005', '1800', '187372'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000003', 'CDI-25-003-EG-A005', '1800', '187373'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000004', 'CDI-25-003-EG-A005', '1800', '187374'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000005', 'CDI-25-003-EG-B005', '1800', '187381'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000006', 'CDI-25-003-EG-B005', '1800', '187382'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000007', 'CDI-25-003-EG-B005', '1800', '187383'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000008', 'CDI-25-003-EG-B005', '1800', '187384'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000009', 'CDI-25-003-EG-C005', '1800', '187391'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000010', 'CDI-25-003-EG-C005', '1800', '187392'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000011', 'CDI-25-003-EG-C005', '1800', '187393'),
('PMU-25-003-EG-01', 'TIR-25-003-EG-01-A2-01-A', 'CO000000012', 'CDI-25-003-EG-C005', '1800', '187394')

Sort by status and effective date range

Before
After
Is there any way to make 'Before' to 'After'?
if a function or procedure is required, please write one for me.
please help me
drop table test;
create table test (employee_code varchar2(8), status varchar2(1), effective_date date, expiry_date date, rate number);
insert into test values ('1', 'U', '01-JAN-20','15-JAN-20',10);
insert into test values ('1', 'U', '06-JAN-20','01-FEB-20',11);
insert into test values ('1', 'N', '02-FEB-20','15-MAR-20',5);
insert into test values ('1', 'N', '16-MAR-20','15-JUN-20',6);
insert into test values ('2', 'N', '01-JAN-20','11-JAN-20',20);
insert into test values ('2', 'U', '12-JAN-20','12-FEB-20',100);
insert into test values ('2', 'N', '13-FEB-20','19-MAR-20',25);
insert into test values ('2', 'N', '20-MAR-20','21-JUN-20',30);
drop table result;
create table result (employee_code varchar2(8), status varchar2(1), effective_date date, expiry_date date);
insert into result values ('1', 'U', '01-JAN-20','01-FEB-20');
insert into result values ('1', 'N', '02-FEB-20','15-JUN-20');
insert into result values ('2', 'N', '01-JAN-20','11-JAN-20');
insert into result values ('2', 'U', '12-JAN-20','12-FEB-20');
insert into result values ('2', 'N', '13-FEB-20','21-JUN-20');
select * from test;
select * from result;
You just need to use GROUP BY and analytical function as follows:
SQL> Select employee_code,
2 status,
3 min(effective_date) effective_date,
4 max(expiry_date) expiry_date
5 From
6 (Select t.*,
7 Sum(case when lgst is null or lgst <> status then 1 end)
8 over (partition by employee_code order by effective_date) as sm
9 From
10 (Select t.*,
11 Lag(status) over (partition by employee_code order by effective_date) as lgst
12 From test t) t
13 )
14 Group by employee_code, sm, status
15 order by employee_code, effective_date;
EMPLOYEE S EFFECTIVE EXPIRY_DA
-------- - --------- ---------
1 U 01-JAN-20 01-FEB-20
1 N 02-FEB-20 15-JUN-20
2 N 01-JAN-20 11-JAN-20
2 U 12-JAN-20 12-FEB-20
2 N 13-FEB-20 21-JUN-20
SQL>

How to recode a field into this specific structure using T-SQL?

I am using SQL Server 2014 and I have a column (ID) in a table (tbl1). The column ID is a nvarchar field.
Here are some examples of what it contains:
ID
18FD64245
533040174
12AZ61356
19AK13355
18HD24189
I would like to run a T-SQL query to recode those values based on the following logic:
IF THEN IF THEN
A 1 0 3
B 2 1 6
C 3 2 7
D 4 3 1
E 5 4 2
F 6 5 4
G 7 6 8
H 8 7 9
I 9 8 5
J 10 9 0
K 11
L 12
M 13
N 14
O 15
P 16
Q 17
R 18
S 19
T 20
U 21
V 22
W 23
X 24
Y 25
Z 26
Therefore the first 2 values shown above would be recoded as:
ID ID2
18FD64245 656482724
533040174 411323692
I am having a hard time approaching the problem from a T-SQL point of view. I am thinking about using CASE Statements to solve the problem. I also had a look at the REPLACE function.
But I am stuck as to how to go about it since the ID field is an alpha-numeric field.
Any ideas on how I move forward with this?
Edit (to show my sql codes as per answer proposed by #Squirrel):
declare #map table
(
map_fr char(1),
map_to varchar(2)
)
insert into #map
values
('A', '1'),
('B', '2'),
('C', '3'),
('D', '4'),
('E', '5'),
('F', '6'),
('G', '7'),
('H', '8'),
('I', '9'),
('J', '10'),
('K', '11'),
('L', '12'),
('M', '13'),
('N', '14'),
('O', '15'),
('P', '16'),
('Q', '17'),
('R', '18'),
('S', '19'),
('T', '20'),
('U', '21'),
('V', '22'),
('W', '23'),
('X', '24'),
('Y', '25'),
('Z', '26')
; with rcte as
(
select [ID], idx = 1, ch = substring([ID], 1, 1)
from Table1
WHERE [ID] IS NOT NULL
union all
select [ID], idx = idx + 1, ch = substring([ID], idx + 1, 1)
from rcte
where idx < len([ID])
),
cte as
(
select r.[ID], r.idx, m.map_to
from rcte r
inner join #map m on r.ch = m.map_fr
)
select [ID],
(select '' + map_to from cte x where x.[ID] = c.[ID] order by idx for xml path('')) as ID2
from cte c
group by [ID]
order by [ID]
I would create a mapping table like
declare #map table
(
map_fr char(1),
map_to varchar(2)
)
and insert the mapping there
insert into #map
values ('A', '1'), ('B', '2'), ('C', '3'), ('D', '4'), ('E', '5'), ('F', '6'),
('G', '7'), ('H', '8'), ('I', '9'), ('J', '10'),('K', '11'),('L', '12'),
('M', '13'),('N', '14'),('O', '15'),('P', '16'),('Q', '17'),('R', '18'),
('S', '19'),('T', '20'),('U', '21'),('V', '22'),('W', '23'),('X', '24'),
('Y', '25'),('Z', '26'),
('0', '3'), ('1', '6'), ('2', '7'), ('3', '1'), ('4', '2'), ('5', '4'),
('6', '8'), ('7', '9'), ('8', '5'), ('9', '0')
then use recursive CTE to split the character and join to the mapping table. And finally concatenate back the string using the mapped value.
; with rcte as
(
select ID, idx = 1, ch = substring(ID, 1, 1)
from yourtbl
union all
select ID, idx = idx + 1, ch = substring(ID, idx + 1, 1)
from rcte
where idx < len(ID)
),
cte as
(
select r.ID, r.idx, m.map_to
from rcte r
inner join #map m on r.ch = m.map_fr
)
select ID,
(select '' + map_to from cte x where x.ID = c.ID order by idx for xml path('')) as ID2
from cte c
group by ID
order by ID
This is better suited as an scalar function, but if you want to do it all on a single SQL statement, here is a way :
select ID,
case substring(ID, 1, 1) when 'A' then '1'
when 'B' then '2'
...
when '9' then '0'
end
+
case substring(ID, 2, 1) when 'A' then '1'
when 'B' then '2'
...
when '9' then '0'
end
+
...
...
case substring(ID, 9, 1) when 'A' then '1'
when 'B' then '2'
...
when '9' then '0'
end
as ID2
from MY_TABLE
You can also map these using a tally table and some of the new features of SQL Server 2017 (STRING_AGG):
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE IDS
(
ID NVARCHAR(9)
)
INSERT INTO IDS
VALUES ('18FD64245'),
('533040174'),
('12AZ61356'),
('19AK13355'),
('18HD24189');
Query 1:
WITH Tally
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Nums.Num) AS Number
FROM (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) AS Nums(Num)
CROSS APPLY (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) AS Nums2(Num)
),
Chars
As
(
-- Turn each character of ID to new row
SELECT ID, SUBSTRING(ID, Number, 1) AS OldChar, Number As Ind
FROM IDS
CROSS APPLY Tally
WHERE SUBSTRING(ID, Number, 1) <> ''
),
NewChars
AS
(
-- Map old characters to new characters
SELECT *,
CASE WHEN ISNumeric(OldChar) = 1 THEN
-- effectively a mapping string to map old characters to new
SUBSTRING('3671248950', CHARINDEX(OldChar, '0123456789'), 1)
ELSE
-- for alphanumeric we can simply make 'A' be 1 and 'B' be 2
-- by subtracting the ASCII value of 'A' from the ASCII of the
-- Character and add 1
ASCII(OldChar) - ASCII('A') + 1
END As NewChar
FROM Chars
)
-- Recombine New Characters to form new Id (SQL Server 2017 only)
SELECT ID, STRING_AGG(NewChar,'') WITHIN GROUP (ORDER BY Ind) AS NewId
FROM NewChars
GROUP BY ID
ORDER BY Id
Results:
| ID | NewId |
|-----------|------------|
| 12AZ61356 | 6712686148 |
| 18FD64245 | 656482724 |
| 18HD24189 | 658472650 |
| 19AK13355 | 6011161144 |
| 533040174 | 411323692 |

SQL - Select query to get purchase orders that have two or more products from different product lines

I'm wondering what I can do to rework/improve this query (it takes too long to run).
Some background info:
Order ID => 'id'
Purchase ID => 'pid'
"Table" is a table of purchase lines, the id is the id of an order, and orders can have multiple lines. For example, there can be three lines with id 1, having various purchase ids, so:
id | pid
1 | 3
1 | 3
1 | 46
The purpose of the query is to find orders that have purchases from multiple product lines, such as 1, 21, 31, 41 and 2, 22, 32, 42.
Query below is what I came up with, but it runs very slowly from all the sub-queries, is it possible to optimize this query or get the same results with a different, faster query?
SELECT a.id
FROM Table AS a
GROUP BY a.id
HAVING (
CAST( CASE WHEN EXISTS (SELECT NULL FROM Table WHERE pid IN ('1', '21', '31', '41') AND id = a.id ) THEN 1 ELSE 0 END AS INT) +
CAST( CASE WHEN EXISTS (SELECT NULL FROM Table WHERE pid IN ('2', '22', '32', '42') AND id = a.id ) THEN 1 ELSE 0 END AS INT) +
CAST( CASE WHEN EXISTS (SELECT NULL FROM Table WHERE pid IN ('3', '23', '33', '43') AND id = a.id ) THEN 1 ELSE 0 END AS INT) +
CAST( CASE WHEN EXISTS (SELECT NULL FROM Table WHERE pid IN ('4', '24', '34', '44') AND id = a.id ) THEN 1 ELSE 0 END AS INT) +
CAST( CASE WHEN EXISTS (SELECT NULL FROM Table WHERE pid IN ('5', '25', '35', '45') AND id = a.id ) THEN 1 ELSE 0 END AS INT) +
CAST( CASE WHEN EXISTS (SELECT NULL FROM Table WHERE pid IN ('6', '26', '36', '46') AND id = a.id ) THEN 1 ELSE 0 END AS INT) +
CAST( CASE WHEN EXISTS (SELECT NULL FROM Table WHERE pid IN ('7', '27', '37', '47') AND id = a.id ) THEN 1 ELSE 0 END AS INT) +
CAST( CASE WHEN EXISTS (SELECT NULL FROM Table WHERE pid IN ('8', '28', '38', '48') AND id = a.id ) THEN 1 ELSE 0 END AS INT)
) > 1
EDIT:
Final working query (is 97% faster than previous):
SELECT y.Id
FROM (SELECT x.Id,
x.productLine
FROM ( SELECT a.id,
CASE
WHEN a.pid IN ('1', '21', '31', '41') THEN 1
WHEN a.pid IN ('2', '22', '32', '42') THEN 2
WHEN a.pid IN ('3', '23', '33', '43') THEN 3
WHEN a.pid IN ('4', '24', '34', '44') THEN 4
WHEN a.pid IN ('5', '25', '35', '45') THEN 5
WHEN a.pid IN ('6', '26', '36', '46') THEN 6
WHEN a.pid IN ('7', '27', '37', '47') THEN 7
WHEN a.pid IN ('8', '28', '38', '48') THEN 8
ELSE 9
END AS productLine
FROM Table AS a
WHERE a.pid IN ('1', '21', '31', '41','2', '22', '32', '42','3', '23', '33', '43','4', '24', '34', '44','5', '25', '35', '45','6', '26', '36', '46','7', '27', '37', '47','8', '28', '38', '48')
) AS x
GROUP BY x.Id, x.productLine
) AS y
GROUP BY y.Id
HAVING COUNT(*) > 1
As I understand Your problem this query should meet your request:
SELECT x.Id
FROM ( SELECT a.Id ,
CAST(a.pid AS INT) % 10 AS pid
FROM [Table] AS a
GROUP BY a.Id ,
CAST(a.pid AS INT) % 10
) x
GROUP BY x.Id
HAVING COUNT(*) > 1
After taking into account new assumptions query should look like:
SELECT y.Id
(SELECT x.Id,
x.pid
FROM ( SELECT a.id,
CASE WHEN a.pid IN ('1', '21', '31', '41') THEN 1
WHEN a.pid IN ('2', '22', '32', '42') THEN 2
WHEN a.pid IN ('3', '23', '33', '43') THEN 3
WHEN a.pid IN ('4', '24', '34', '44') THEN 4
WHEN a.pid IN ('5', '25', '35', '45') THEN 5
WHEN a.pid IN ('6', '26', '36', '46') THEN 6
WHEN a.pid IN ('7', '27', '37', '47') THEN 7
WHEN a.pid IN ('8', '28', '38', '48') THEN 8
ELSE 9 END AS productLine
FROM Table AS a
) x
GROUP BY x.Id, x.pid) y
GROUP BY y.Id
HAVING COUNT(*) > 1
Althoug I really like Rafał's solution as it gives numbers to the groups, I thought of another, little simplier solution, but was unable to test it earlier.
SELECT distinct(id) FROM store a
WHERE 4 = (
SELECT COUNT(DISTINCT(pid))
FROM store
where (id = a.id AND (
pid in (1, 21, 31, 41) OR
pid in (2, 22, 32, 42)
)
)
)