Many-to-many query selecting records with no match from the right as well as values from the left in SQLite - sql

I have a standard many-to-many schema like this:
items table:
id
name
1
foo
groups table:
id
name
slug
1
baz
qux
items_to_groups:
item_id
group_id
1
1
The groups.slug is used to query Group information. The ID is internal.
I need to query for items that are not in a specified group, but also need to include the group attributes (like name) in the result set.
The query is trivial without the need for values from the groups table, but I cannot figure out how to include them.
Here is my best attempt:
SELECT
g.slug,
g.name,
i.name
FROM
items AS i
LEFT JOIN items_to_groups AS itg ON i.id = itg.item_id
LEFT JOIN groups AS g ON itg.group_id = g.id
AND g.slug = 'group-slug-1'
WHERE
itg.item_id IS NULL;
Results:
||Item name 1
||Item name 2
Desired results:
group-slug-1|1|Item name 1
group-slug-1|1|Item name 2

Hmmm . . . I'm thinking:
select i.*, g.*
from items i cross join
groups g
where g.slug = 'group-slug-1' and
not exists (select 1
from items_to_groups ig
where ig.item_id = i.id and ig.group_id = g.id
);

Related

How to find an objects that doesn't have some required properties from dictionary

I have three tables:
object
id
Name
1
ball
2
pencil
object_properties
object_id
property_id
image
1
4
path
1
5
path
1
6
path
2
5
path
property
id
name
4
left
5
right
6
top
All rows in the table property are required for object_properties.
In this case query should find the second object pencil, because it doesn't have the all properties.
I tried a query:
select b.*
from objects b
left join object_properties p ON b.id = p.object_id
where property_id not in (select id from property)
But it's not working.
SQL Fiddle
Expected result:
id
Name
2
pencil
An alternative to Gordon's query is to not only compare the number of records, but the records themselves:
WITH j AS (
SELECT b.id,array_agg(p.property_id) prop
FROM objects b
LEFT JOIN object_properties p ON b.id = p.object_id
GROUP BY b.id
)
SELECT * FROM j
WHERE prop <> (SELECT array_agg(id) FROM property);
If you just count the records, the query will filter out objects that e.g. contain left three times, which is most certainly invalid.
Demo: db<>fiddle
You can use aggregation to count the properties on each property and then having to filter down to the ones missing properties:
select o.id, o.name
from objects o left join
object_properties op
on o.id = op.object_id left join
properties p
on op.property_id = p.id
group by o.id, o.name
having count(p.id) <> (select count(*) from properties);
Here is a db<>fiddle. Note that this returns both objets because the properties table has four rows not three.

UNION columns in one SELECT

Let's say :
SELECT Item.Id, Item.ParentId FROM Item ..."
Returns me this data:
Id | ParentId
----------------
1 | NULL
2 | 17
3 | 13
Is there is a way to get this data as one column by using some kind of UNION but on columns from only one SELECT ? Something like:
SELECT (Item.Id UNION Item.ParentId) AS Id FROM Item...
Result :
Id |
----
1 |
2 |
3 |
NULL
17 |
13 |
EDIT EXAMPLE:
I have Media Table:
Id | ParentId
----------------
1 | NULL
2 | 1
3 | 2
It have relations with itself, this is some kind of 3 level tree structure
(Series -> Seasons -> Episodes)
There is another Table Offer which contain information about availability:
Id | MediaId | Availability
------------------------------
1 | 3 | true
I need to get id's of all media that are available, but also all parent's id, of all levels.
I was thinking about:
SELECT Media.Id, MediaSeason.Id, MediaSeries.Id FROM Media
LEFT JOIN Media AS MediaSeason ON MediaSeason.Id = Media.ParentId
LEFT JOIN Media AS MediaSeries ON MediaSeries.Id = MediaSeason.ParentId
LEFT JOIN Offer ON Offer.MediaId = Media.Id
WHERE Offer.Availability = true
This gives me all id's i need but in three different columns and I'm trying to find a way to put it into one, without repeating join and where login in 3 different SELECTS.
I'm using MSSQL.
Try this:
SELECT * FROM (SELECT Item.Id FROM Item ...
UNION ALL
SELECT Item.ParentId FROM Item ...)
If your children and parents are in the same table (Item)
SELECT Id FROM Item
Will retrieve all Items, including Parents because parents are also Items.
But if what you want is to not repeat the where clause and have Ids of any matched Media and its associated parents (even if the parent media does not match the where clause) you can try this:
SELECT
m.Id
FROM
Media m INNER JOIN (
SELECT
m2.Id, m2.ParentId
FROM
Media m2
LEFT JOIN Offer ON Offer.MediaId = m2.Id
WHERE
Offer.Availability = true
) tmp ON (tmp.Id = m.Id OR tmp.ParentId = m.Id)
Finally, for three levels:
SELECT
m.Id
FROM
Media m INNER JOIN (
SELECT
m2.Id, m2.ParentId, m3.ParentId AS GrandParentId
FROM
Media m2
LEFT JOIN Media m3 ON m2.ParentId = m3.Id
LEFT JOIN Offer ON Offer.MediaId = m2.Id
WHERE
Offer.Availability = true
) tmp ON (tmp.Id = m.Id OR tmp.ParentId = m.Id OR tmp.GrandParentId = m.Id)
SELECT DISTINCT
pivot_hierarchy.media_id
FROM
offers o
LEFT JOIN
media m1
ON m1.id = o.media_id
LEFT JOIN
media m2
ON m2.id = m1.parent_id
OUTER APPLY
(
SELECT o.media_id
UNION ALL
SELECT m1.parent_id WHERE m1.parent_id IS NOT NULL
UNION ALL
SELECT m2.parent_id WHERE m2.parent_id IS NOT NULL
)
AS pivot_hierarchy
WHERE
o.availability = 'true'
Everything up to the APPLY should be self explanatory. Get the offers, get the parent of that media if it has one, and the parent of that media if it has one.
The APPLY then joins each row on to a function that can return more than one row each. In this case the function returns 1, 2 or 3 rows. Those being the media id, it parent if it has one, and its grand-parent if it has one. To do that, the function unions the three input columns, provided that they’re not null.
This avoids having to join back on to the media table again.
Also, you need a distinct in the select. Otherwise the same series or season id could return multiple times.
Nested selects can be avoided in UNION
create table tab (
Id int,
ParentId int
);
insert into tab
values
(1, NULL),
(2, 17),
(3, 13);
then do
select ID as ID
from tab
union all
select ParentId as ID
from tab
NOTE: DB queries can be conveniently tested live, e.g. http://sqlfiddle.com/#!17/7a3a8/2

Join two tables to get data on multiple criteria

I have 2 tables
1.Table groups
group_id
group_name
game_id
2.Table champ_groups
id
championship_id
group_id
group_id is PK for Table groups and FK for Table champ_groups.
championship_id is unique id assigned to a championship.
and Table_champ_groups contain different groups allowed in a championship.
I want to join both the tables in such a way that I get list of group_name along with group_id of a particular championship.
This is what I have tried till now
SELECT *
FROM champ_group cg
INNER JOIN groups g
ON cg.group_id = g.group_id
WHERE cg.championship_id = '80623809'
Desired Result
group_id | championship_id | group_name
1 | 80623809 | Micro-U/6
2 | 80623809 | Mini-U/8
How do I get the desired result ?
What you are doing is almost 100% correct. If you only need the group_name along with the group_id as a result you have only to substitute the asterisk in your select with the following:
SELECT g.group_name, g.group_id
FROM champ_group cg
INNER JOIN groups g
ON cg.group_id = g.group_id
WHERE cg.championship_id = '1'
Based on your question, it is slightly unclear of what you might expect as a result as your query seems totally correct.

how do select all rows from table groups and count of contacts for each groups in sql

I have 2 tables:
groups
contacts
groups table's fields are:
group_id
group_name
contacts table fields are:
contact_group_id
contact_id
contact_name
Now, I want to select all groups with count of group's contacts...
For e.g.:
groupname contacts count
friend 12 |
school 8 |
ennemy 0 |
family 25 |
i want all groups dispaly(include groups that have not any contacts)
thanks a lot
you need to do left join and then group by. Do following
select a.group_name, count(b.contact_id) from
groups a left join contacts b on
a.group_id = b.contact_group_id
group by a.group_name
See Fiddle
Create a view containing the number of contacts per group and then outer join that view with your groups table.
CREATE VIEW contactscount AS
SELECT contact_group_id, COUNT(contact_id) AS count FROM contacts GROUP BY contact_group_id
SELECT * FROM groups OUTER JOIN contactscount ON contactscount.contact_group_id=groups.group_id
This selects Null for count of groups with no contacts. You could omit the view.
SELECT table1.group_name, COUNT(table2.contact_id)
FROM table1
INNER JOIN table2
ON table1 .group_id = table2 .contact_group_id
GROUP BY table1.group_name

Get latest record from second table left joined to first table

I have a candidate table say candidates having only id field and i left joined profiles table to it. Table profiles has 2 fields namely, candidate_id & name.
e.g. Table candidates:
id
----
1
2
and Table profiles:
candidate_id name
----------------------------
1 Foobar
1 Foobar2
2 Foobar3
i want the latest name of a candidate in a single query which is given below:
SELECT C.id, P.name
FROM candidates C
LEFT JOIN profiles P ON P.candidate_id = C.id
GROUP BY C.id
ORDER BY P.name;
But this query returns:
1 Foobar
2 Foobar3
...Instead of:
1 Foobar2
2 Foobar3
The problem is that your PROFILES table doesn't provide a reliable means of figuring out what the latest name value is. There are two options for the PROFILES table:
Add a datetime column IE: created_date
Define an auto_increment column
The first option is the best - it's explicit, meaning the use of the column is absolutely obvious, and handles backdated entries better.
ALTER TABLE PROFILES ADD COLUMN created_date DATETIME
If you want the value to default to the current date & time when inserting a record if no value is provided, tack the following on to the end:
DEFAULT CURRENT_TIMESTAMP
With that in place, you'd use the following to get your desired result:
SELECT c.id,
p.name
FROM CANDIDATES c
LEFT JOIN PROFILES p ON p.candidate_id = c.id
JOIN (SELECT x.candidate_id,
MAX(x.created_date) AS max_date
FROM PROFILES x
GROUP BY x.candidate_id) y ON y.candidate_id = p.candidate_id
AND y.max_date = p.created_date
GROUP BY c.id
ORDER BY p.name
Use a subquery:
SELECT C.id, (SELECT P.name FROM profiles P WHERE P.candidate_id = C.id ORDER BY P.name LIMIT 1);