pivot then group on value - sql

I have a logs table with the following definition:
Column | Type | Collation | Nullable | Default
------------------+-----------------------+-----------+----------+---------
id | integer | | not null |
work_location_id | uuid | | not null |
hard_disk_id | integer | | not null |
and a works table with the following definition:
Column | Type | Collation | Nullable | Default
-------------+-----------------------+-----------+----------+---------
id | integer | | not null |
location_id | uuid | | not null |
f_index | integer | | not null |
f_name | character varying(40) | | not null |
f_value | character varying(40) | | not null |
The logs table has data such as:
id | work_location_id | hard_disk_id
----+--------------------------------------+--------------
1 | 40e6215d-b5c6-4896-987c-f30f3678f608 | 1
2 | 3f333df6-90a4-4fda-8dd3-9485d27cee36 | 2
3 | c17bed94-3a9c-4c21-be49-dc77f96d49dc | 3
4 | 6ecd8c99-4036-403d-bf84-cf8400f67836 | 4
5 | 6ecd8c99-4036-403d-bf84-cf8400f67836 | 5
And the works table has data such as:
id | location_id | f_index | f_name | f_value
----+--------------------------------------+---------+-------------+------------
1 | 40e6215d-b5c6-4896-987c-f30f3678f608 | 1 | plot_crop | pears
2 | 3f333df6-90a4-4fda-8dd3-9485d27cee36 | 1 | plot_crop | pears
3 | c17bed94-3a9c-4c21-be49-dc77f96d49dc | 1 | plot_crop | pears
4 | 1cdc7c05-0acd-46cb-b48a-4d3e240a4548 | 1 | plot_crop | pears
5 | dae1eee7-508f-4a76-8906-8ff7b8bfab26 | 1 | plot_crop | pears
6 | 6ecd8c99-4036-403d-bf84-cf8400f67836 | 1 | plot_id | 137
7 | 6ecd8c99-4036-403d-bf84-cf8400f67836 | 2 | farmer_name | John Smith
Desired Output
I want to be able to query the two tables and get the following output
location_id | plot_id | farmer_name
---------------------------------------+---------+-------------
40e6215d-b5c6-4896-987c-f30f3678f608 | None | None
3f333df6-90a4-4fda-8dd3-9485d27cee36 | None | None
c17bed94-3a9c-4c21-be49-dc77f96d49dc | None | None
6ecd8c99-4036-403d-bf84-cf8400f67836 | 137 | John Smith
Notice how for location_id = 6ecd8c99-4036-403d-bf84-cf8400f67836, both values are now showing in one row. I tried to use group by location_id but that didn't work, I was still getting duplicates.
I have also created a db-fiddle.

This looks like conditional aggregation:
select location_id,
max(f_value) filter (where f_name = 'plot_id') as plot_id,
max(f_value) filter (where f_name = 'farmer_name') as farmer_name
from t
group by location_id;
In other databases, you would just use:
max(case when f_name = 'plot_id' then f_value end) as plot_id

As you want to have None as text
Schema (PostgreSQL v13)
-- create table
create table logs (
id integer not null,
work_location_id uuid not null,
hard_disk_id integer not null
);
create table works (
id integer not null,
location_id uuid not null,
f_index integer not null,
f_name varchar(40) not null,
f_value varchar(40) not null
);
-- insert data into table
insert into logs (id, work_location_id, hard_disk_id) values
(1, '40e6215d-b5c6-4896-987c-f30f3678f608', 1),
(2, '3f333df6-90a4-4fda-8dd3-9485d27cee36', 2),
(3, 'c17bed94-3a9c-4c21-be49-dc77f96d49dc', 3),
(4, '6ecd8c99-4036-403d-bf84-cf8400f67836', 4),
(5, '6ecd8c99-4036-403d-bf84-cf8400f67836', 5);
insert into works (id, location_id, f_index, f_name, f_value) values
(1, '40e6215d-b5c6-4896-987c-f30f3678f608', 1, 'plot_crop', 'pears'),
(2, '3f333df6-90a4-4fda-8dd3-9485d27cee36', 1, 'plot_crop', 'pears'),
(3, 'c17bed94-3a9c-4c21-be49-dc77f96d49dc', 1, 'plot_crop', 'pears'),
(4, '1cdc7c05-0acd-46cb-b48a-4d3e240a4548', 1, 'plot_crop', 'pears'),
(5, 'dae1eee7-508f-4a76-8906-8ff7b8bfab26', 1, 'plot_crop', 'pears'),
(6, '6ecd8c99-4036-403d-bf84-cf8400f67836', 1, 'plot_id', '137'),
(7, '6ecd8c99-4036-403d-bf84-cf8400f67836', 2, 'farmer_name', 'John Smith');
Query #1
select w.location_id,
COALESCE(MAX(case
when w.f_name = 'plot_id' then w.f_value
else NULL
end),'None') as "plot_id",
COALESCE(MAX(case
when w.f_name = 'farmer_name' then w.f_value
else NULL
end),'None') as "farmer_name"
from logs l
inner join works w on w.location_id = l.work_location_id
GROUP BY location_id;
location_id
plot_id
farmer_name
3f333df6-90a4-4fda-8dd3-9485d27cee36
None
None
40e6215d-b5c6-4896-987c-f30f3678f608
None
None
6ecd8c99-4036-403d-bf84-cf8400f67836
137
John Smith
c17bed94-3a9c-4c21-be49-dc77f96d49dc
None
None
View on DB Fiddle

Related

Convert values in related table to comma-separated list

I have two SQL Server tables:
TableA TableB
+------+--------+ +-----+------------+
| aid | Name | | aid | Activity |
+------+--------+ +-----+------------+
| 1 | Jim | | 1 | Skiing |
| 2 | Jon | | 1 | Surfing |
| 3 | Stu | | 1 | Riding |
| 4 | Sam | | 3 | Biking |
| 5 | Kat | | 3 | Flying |
+------+--------+ +-----+------------+
I'm trying to the following result where the related activities are in a comma-separated list:
+------+--------+------------------------------+
| aid | Name | Activity |
+------+--------+------------------------------+
| 1 | Jim | Skiing, Surfing, Riding |
| 2 | Jon | NULL |
| 3 | Stu | Biking, Flying |
| 4 | Sam | NULL |
| 5 | Kat | NULL |
+------+--------+------------------------------+
I tried:
SELECT aid, Name, STRING_AGG([Activity], ',') AS Activity
FROM TableA
INNER JOIN TableB
ON TableA.aid = TableB.aid
GROUP BY aid, Name
Can someone help me with this SQL query? Thank you.
You could use OUTER APPLY to aggregate the string if you're using SQL Server 2017 or higher.
drop table if exists #TableA;
go
create table #TableA (
aid int not null,
[Name] varchar(10) not null);
insert #TableA(aid, [Name]) values
(1, 'Jim'),
(2, 'Jon'),
(3, 'Stu'),
(4, 'Sam'),
(5, 'Kat');
drop table if exists #TableB;
go
create table #TableB (
aid int not null,
[Activity] varchar(10) not null);
insert #TableB(aid, [Activity]) values
(1, 'Skiing'),
(1, 'Surfing'),
(1, 'Riding'),
(3, 'Biking'),
(3, 'Flying');
select a.aid, a.[Name], oa.sa
from #TableA a
outer apply (select string_agg(b.Activity, ', ') sa
from #TableB b
where a.aid=b.aid) oa;
Name sa
Jim Skiing, Surfing, Riding
Jon NULL
Stu Biking, Flying
Sam NULL
Kat NULL

SQL Eliminate Duplicates whilst merging additional table

i have two tables, ADDRESSES and an additional table CONTACTS. CONTACTS have a SUPERID which is the ID of the ADDRESS they belong to.
I want to identify duplicates (same Name, Firstname and Birthday) in the ADDRESSES Table and merge the contacts of these duplicates onto the latest Adress (latest DATECREATE or highest ID of the Adress).
Afterwards the other duplicates shall be deleted.
My approach for merging the contacts does not work though. Deleting duplicates works.
This is my approach. Would be grateful for support what is wrong here.
Thank you!
UPDATE dbo.CONTACTS
SET SUPERID = ADDRESSES.ID FROM dbo.ADDRESSES
inner join CONTACTS on ADDRESSES.ID = CONTACTS.SUPERID
WHERE ADDRESSES.id in (
SELECT id FROM dbo.ADDRESSES
WHERE EXISTS(
SELECT NULL FROM ADDRESSES AS tmpcomment
WHERE dbo.ADDRESSES.FIRSTNAME0 = tmpcomment.FIRSTNAME0
AND dbo.ADDRESSES.LASTNAME0 = tmpcomment.LASTNAME0
and dbo.ADDRESSES.BIRTHDAY1 = tmpcomment.BIRTHDAY1
HAVING dbo.ADDRESSES.id > MIN(tmpcomment.id)
))
DELETE FROM ADDRESSES
WHERE id in (
SELECT id FROM dbo.ADDRESSES
WHERE EXISTS(
SELECT NULL FROM ADDRESSES AS tmpcomment
WHERE dbo.ADDRESSES.FIRSTNAME0 = tmpcomment.FIRSTNAME0
AND dbo.ADDRESSES.LASTNAME0 = tmpcomment.LASTNAME0
and dbo.ADDRESSES.BIRTHDAY1 = tmpcomment.BIRTHDAY1
HAVING dbo.ADDRESSES.id > MIN(tmpcomment.id)
)
)
Here is a sample for understanding the issue.
ADDRESSES
| ID | DATECREATE | LASTNAME0 | FIRSTNAME0 | BIRTHDAY1 |
|:-----------|------------:|:------------:|------------:|:------------:|
| 1 | 19.07.2011 | Arthur | James | 05.05.1980 |
| 2 | 23.08.2012 | Arthur | James | 05.05.1980 |
| 3 | 11.12.2015 | Arthur | James | 05.05.1980 |
| 4 | 22.10.2016 | Arthur | James | 05.05.1980 |
| 6 | 20.12.2014 | Doyle | Peter | 01.01.1950 |
| 7 | 09.01.2016 | Doyle | Peter | 01.01.1950 |
|:-----------|------------:|:------------:|------------:|:------------:|
CONTACTS
| ID | SUPERID |
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
| 4 | 2 |
| 5 | 3 |
| 6 | 4 |
| 7 | 4 |
| 8 | 6 |
| 9 | 6 |
| 10 | 6 |
| 11 | 7 |
The result shall be like this
ADDRESSES
| ID | DATECREATE | LASTNAME0 | FIRSTNAME0 | BIRTHDAY1 |
|:-----------|------------:|:------------:|------------:|:------------:|
| 4 | 22.10.2016 | Arthur | James | 05.05.1980 |
| 7 | 09.01.2016 | Doyle | Peter | 01.01.1950 |
CONTACTS
| ID | SUPERID |
| 1 | 4 |
| 2 | 4 |
| 3 | 4 |
| 4 | 4 |
| 5 | 4 |
| 6 | 4 |
| 7 | 4 |
| 8 | 7 |
| 9 | 7 |
| 10 | 7 |
| 11 | 7 |
My approach would use a temporary table:
/*
CREATE TABLE addresses
([ID] int, [DATECREATE] varchar(10), [LASTNAME0] varchar(6), [FIRSTNAME0] varchar(5), [BIRTHDAY1] datetime);
INSERT INTO addresses
([ID], [DATECREATE], [LASTNAME0], [FIRSTNAME0], [BIRTHDAY1])
VALUES
(1, '19.07.2011', 'Arthur', 'James', '1980-05-05 00:00:00'),
(2, '23.08.2012', 'Arthur', 'James', '1980-05-05 00:00:00'),
(3, '11.12.2015', 'Arthur', 'James', '1980-05-05 00:00:00'),
(4, '22.10.2016', 'Arthur', 'James', '1980-05-05 00:00:00'),
(6, '20.12.2014', 'Doyle', 'Peter', '1950-01-01 00:00:00'),
(7, '09.01.2016', 'Doyle', 'Peter', '1950-01-01 00:00:00');
CREATE TABLE contacts
([ID] int, [SUPERID] int);
INSERT INTO contacts
([ID], [SUPERID])
VALUES
(1, 1),
(2, 1),
(3, 2),
(4, 2),
(5, 3),
(6, 4),
(7, 4),
(8, 6),
(9, 6),
(10, 6),
(11, 7);
*/
DROP TABLE IF EXISTS #t; --sqls2016+ only, google for an older method if yours is sub 2016
SELECT id as oldid, MAX(id) OVER(PARTITION BY lastname0, firstname0, birthday1) as newid INTO #t
FROM
addresses;
/*now #t contains data like
1, 4
2, 4
3, 4
4, 4
6, 7
7, 7*/
--remove the ones we don't need to change
DELETE FROM #t WHERE oldid = newid;
BEGIN TRANSACTION;
SELECT * FROM addresses;
SELECT * FROM contacts;
--now #t is the list of contact changes we need to make, so make those changes
UPDATE contacts
SET contacts.superid = #t.newid
FROM
contacts INNER JOIN #t ON contacts.superid = #t.oldid;
--now scrub the old addresses with no contact records. This catches all such records, not just those in #t
DELETE FROM addresses WHERE id NOT IN (SELECT DISTINCT superid FROM contacts);
--alternative to just clean up the records we affected in this operation
DELETE FROM addresses WHERE id IN (SELECT oldid FROM #t);
SELECT * FROM addresses;
SELECT * FROM contacts;
ROLLBACK TRANSACTION;
Please note, i have tested this and it produces the results you want but I advocate caution copying an update/delete query off the internet and running. I've inserted a transaction that selects the data before and after and rolls back the transaction so nothing gets wrecked. Run it on a test db first though!

Select distinct one field other first non empty or null

I have table
| Id | val |
| --- | ---- |
| 1 | null |
| 1 | qwe1 |
| 1 | qwe2 |
| 2 | null |
| 2 | qwe4 |
| 3 | qwe5 |
| 4 | qew6 |
| 4 | qwe7 |
| 5 | null |
| 5 | null |
is there any easy way to select distinct 'id' values with first non null 'val' values. if not exist then null. for example
result should be
| Id | val |
| --- | ---- |
| 1 | qwe1 |
| 2 | qwe4 |
| 3 | qwe5 |
| 4 | qew6 |
| 5 | null |
In your case a simple GROUP BY should be the solution:
SELECT Id
,MIN(val)
FROM dbo.mytable
GROUP BY Id
Whenever using a GROUP BY, you have to use an aggregate function on all columns, which are not listed in the GROUP BY.
If an Id has a value (val) other than NULL, this value will be returned.
If there are just NULLs for the Id, NULL will be returned.
As far as i unterstood (regarding your comment), this is exactly what you're going to approach.
If you always want to have "the first" value <> NULL, you'll need another sort criteria (like a timestamp column) and might be able to solve it with a WINDOW-function.
If you want the first non-NULL value (where "first" is based on id), then MIN() doesn't quite do it. Window functions do:
select t.*
from (select t.*,
row_number() over (partition by id
order by (case when val is not null then 1 else 2 end),
id
) as seqnum
from t
) t
where seqnum = 1;
SQL Fiddle:
Create Table from SQL Fiddle:
CREATE TABLE tab1(pid integer, id integer, val varchar(25))
Insert dummy records :
insert into tab1
values (1, 1 , null),
(2, 1 , 'qwe1' ),
(3, 1 , 'qwe2'),
(4, 2 , null ),
(5, 2 , 'qwe4' ),
(6, 3 , 'qwe5' ),
(7, 4 , 'qew6' ),
(8, 4 , 'qwe7' ),
(9, 5 , null ),
(10, 5 , null );
fire below query:
SELECT Id ,MIN(val) as val FROM tab1 GROUP BY Id;

SQL query check value exists in lookup based on another column value

I have Name/Value pair records in a table and I need to confirm the values exist against a lookup for each Name
KeyVal - Table of NameValue pairs
| MyID1 | MyRecNumber | MyFieldName | MyFieldValue |
|-------|-------------|-------------|--------------|
| 1 | 1 | FirstField | One |
| 2 | 1 | SecondField | Car |
| 3 | 2 | FirstField | Two |
| 4 | 2 | SecondField | Firetruck |
| 5 | 3 | FirstField | Blue |
| 6 | 3 | SecondField | Car |
LookupTable - Table to match Name Values (from KeyVal) with LookupValue (in CheckVals table)
| MyID2 | MyFieldName | LookupName |
|-------|-------------|------------|
| 1 | FirstField | FieldOne |
| 2 | SecondField | FieldTwo |
CheckVals - Table with valid values for each field
| MyID3 | LookupFieldName | LookupValue |
|-------|-----------------|-------------|
| 1 | FieldOne | One |
| 2 | FieldOne | Two |
| 3 | FieldOne | Three |
| 4 | FieldTwo | Car |
| 5 | FieldTwo | Truck |
| 6 | FieldTwo | Bus |
I have a query that will check values against a single name lookup, but am unsure how to make this check all names against the lookup table. In this query it bypasses the LookupTable as I specify the lookup value in the query itself.
DECLARE #AttributeName AS VARCHAR(50)
DECLARE #Lookup AS VARCHAR(50)
SET #AttributeName = 'SecondField'
SET #Lookup = 'FieldTwo';
SELECT
MyRecNumber,
MyFieldName,
MyFieldValue
FROM
dbo.KeyVal kv
WHERE
MyFieldName = #AttributeName
AND MyFieldValue NOT IN
(
SELECT
LookupValue
FROM
dbo.CheckVals cv
WHERE cv.LookupFieldName = #Lookup
)
Question: How can I do a lookup against all values in the KeyVal table, through the LookupTable table, to check if the value in MyFieldValue exists in CheckVals against the MyFieldName and LookupName match?
This is what I'm hoping to get - the two rows that have invalid values are returned in the query results
| MyRecNumber | MyFieldName | MyFieldValue |
|-------------|-------------|--------------|
| 2 | SecondField | Firetruck |
| 3 | FirstField | Blue |
Sample Tables
CREATE TABLE [dbo].[KeyVal](
[MyID1] [smallint] IDENTITY(1,1) NOT NULL,
[MyRecNumber] [smallint] NULL,
[MyFieldName] [varchar](50) NULL,
[MyFieldValue] [varchar](50) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[LookupTable](
[MyID2] [smallint] IDENTITY(1,1) NOT NULL,
[MyFieldName] [varchar](50) NULL,
[LookupName] [varchar](50) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[CheckVals](
[MyID3] [smallint] IDENTITY(1,1) NOT NULL,
[LookupFieldName] [varchar](50) NULL,
[LookupValue] [varchar](50) NULL
) ON [PRIMARY]
Sample Data
INSERT INTO [dbo].[KeyVal]
([MyRecNumber], [MyFieldName], [MyFieldValue])
VALUES
(1, 'FirstField', 'One'),
(1, 'SecondField', 'Car'),
(2, 'FirstField', 'Two'),
(2, 'SecondField', 'Firetruck'),
(3, 'FirstField', 'Blue'),
(3, 'SecondField', 'Car')
INSERT INTO [dbo].[LookupTable]
([MyFieldName], [LookupName])
VALUES
('FirstField', 'FieldOne'),
('SecondField', 'FieldTwo')
INSERT INTO [dbo].[CheckVals]
([LookupFieldName], [LookupValue])
VALUES
('FieldOne', 'One'),
('FieldOne', 'Two'),
('FieldOne', 'Three'),
('FieldTwo', 'Car'),
('FieldTwo', 'Truck'),
('FieldTwo', 'Bus')
Let me assume that you want the rows in the first table where the values do not match:
select kv.*
from keyval kv left join
lookuptable lt
on kv.myfieldname = lt.myfieldname left join
checkvals cv
on cv.LookupFieldName = lt.LookupName and
cv.LookupValue = kv.MyFieldValue
where cv.myid3 is null;

SQL count occurrences of values grouped by external tables references

What is the best approach in terms of performance and maintainability to count the number of occurrences of the same value in a table, grouping the results with the same reference that groups the entries of the table?
Let's say I have three tables (concepts have been shrinked in order to represent a scenario that is similar to the one I'm working on):
|----------| |----------------| |-----------------------------------|
| MEAL | | RECIPE | | INGREDIENT_ENTRY |
|----------| |----------------| |-----------------------------------|
| ID | ... | | ID | ID_m | ...| | ID | ID_r | amount and description|
|----------| |----------------| |-----------------------------------|
| 1 | ... | | 1 | 1 | ...| | 1 | 1 | '15gr of yeast' |
| 2 | ... | | 2 | 2 | ...| | 2 | 4 | '2 eggs' |
| 3 | ... | | 3 | 3 | ...| | 3 | 1 | '300cl of water' |
| 4 | ... | | 4 | 4 | ...| | 4 | 2 | '300cl of beer' |
|----------| | 5 | 1 | ...| | 5 | 3 | '250cl of milk' |
| 6 | 4 | ...| | 6 | 5 | '100gr of biscuits' |
| 7 | 5 | ...| | 7 | 2 | '15gr of yeast' |
| 8 | 6 | ...| | 8 | 1 | '500gr of flour' |
|----------------| | 9 | 2 | '500gr of flour' |
| 10 | 2 | '10gr of salt' |
| 11 | 4 | '15gr of yeast' |
|-----------------------------------|
The same MEAL can be cooked with a different RECIPE, and each RECIPE is made of different INGREDIENT_ENTRYs, organized in the same RECIPE by sharing the same ID_r value.
INGREDIENT_ENTRY.[amount and description] is a column of type VARCHAR(MAX), this is the value that must be compared.
In the example, making the query with (MEAL 1,RECIPE 1):
It has 3 ingredients (1,3,8), and shares:
Two ingredients with RECIPE 2 (7,9) -> and so can be found in MEAL 2
One ingredient with RECIPE 4 (11) -> and so can be found in MEAL 3
Result should look something like:
|------| |--------| |-------|
| MEAL | | RECIPE | | COUNT |
|------| |--------| |-------|
| 2 | | 2 | | 2 |
| 4 | | 4 | | 1 |
|------| |--------| |-------|
I'm experimenting with views to reduce SQL complexity, but I cannot make it with a single SQL statement and I would like to avoid going back and forth to code (C#) and perform multiple queries (for example query for every ingredient, and reconcile results with HashMaps or similar).
Please, note that I cannot modify the DB structure.
You can find common ingredients using EXISTS. In the below I have simply used a Common table expression so that I don't have to write out the joins more than once to get back to a meal ID:
DECLARE #SelectedMealID INT = 1;
WITH LinkedData AS
(
SELECT MealID = r.ID_m,
RecipeID = r.ID,
Ingredient = i.[amount and description]
FROM RECIPE AS r
INNER JOIN INGREDIENT_ENTRY AS i
ON i.ID_r = r.ID
)
SELECT a.MealID,
a.RecipeID,
CommonIngedients = COUNT(*)
FROM LinkedData AS a
WHERE a.MealID != #SelectedMealID
AND EXISTS
( SELECT 1
FROM LinkedData AS b
WHERE b.Ingredient = a.Ingredient
AND b.MealID = #SelectedMealID
)
GROUP BY a.MealID, a.RecipeID;
I have tested this with the below sample:
-- GENERATE TABLES AND DATA
DECLARE #Meal TABLE (ID INT);
INSERT #Meal (ID) VALUES (1), (2), (3), (4);
DECLARE #Recipe TABLE (ID INT, ID_m INT);
INSERT #Recipe (ID, ID_m)
VALUES (1, 1), (2, 2), (3, 3), (4, 4), (5, 1), (6, 4), (7, 5), (8, 6);
DECLARE #Ingredient TABLE (ID INT, ID_r INT, AmountAndDescription VARCHAR(MAX));
INSERT #Ingredient (ID, ID_R, AmountAndDescription)
VALUES
(1, 1, '15gr of yeast'), (2, 4, '2 eggs'),
(3, 1, '300cl of water'), (4, 2, '300cl of beer'),
(5, 3, '250cl of milk'), (6, 5, '100gr of biscuits'),
(7, 2, '15gr of yeast'), (8, 1, '500gr of flour'),
(9, 2, '500gr of flour'), (10, 2, '10gr of salt'),
(11, 4, '15gr of yeast');
-- TEST QUERY
DECLARE #SelectedMealID INT = 1;
WITH LinkedData AS
(
SELECT MealID = r.ID_m,
RecipeID = r.ID,
Ingredient = i.AmountAndDescription
FROM #Recipe AS r
INNER JOIN #Ingredient AS i
ON i.ID_r = r.ID
)
SELECT a.MealID,
a.RecipeID,
CommonIngedients = COUNT(*)
FROM LinkedData AS a
WHERE a.MealID != #SelectedMealID
AND EXISTS
( SELECT 1
FROM LinkedData AS b
WHERE b.Ingredient = a.Ingredient
AND b.MealID = #SelectedMealID
)
GROUP BY a.MealID, a.RecipeID;
OUTPUT
MealID RecipeID CommonIngedients
------------------------------------------
2 2 2
4 4 1
N.B. The expected output in the question differs slighly but I think the question may contain a typo (states Recipe 4 relates to meal 3, but this doesn't appear to be the case in the sample data)