UNION columns in one SELECT - sql

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

Related

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

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
);

Join table using column value as table name

Is it possible to join a table whereby the table name is a value in a column?
Here is a TABLE called food:
id food_name price_table pricing_reference_id
1 | 'apple' | 'daily_price' | 13
2 | 'banana' | 'monthly_price' | 13
3 | 'hotdog' | 'weekly_price' | 17
4 | 'sandwich' | 'monthly_price' | 9
There are three other tables (pricing tables): daily_price, weekly_price, and monthly_price tables.
Side note: Despite their names, the three pricing tables display vastly different kinds of information, which is why the three tables were not merged into one table
Each row in the food table can only be joined with one of the three pricing tables at most.
The following does not work -- it is just to illustrate what I am trying to get at:
SELECT *
FROM food
LEFT JOIN food.price_table ON food.pricing_reference_id = daily_price.id
WHERE id = 1;
Obviously the query does not work. Is there any way that the name of the table in the price_table column could be used as the table name in a join?
I would suggest left joins:
select f.*,
coalesce(dp.price, wp.price, mp.price) as price
from food f left join
daily_price dp
on f.pricing_reference_id = dp.id and
f.pricing_table = 'daily_price' left join
weekly_price wp
on f.pricing_reference_id = wp.id and
f.pricing_table = 'weekly_price' left join
monthly_price mp
on f.pricing_reference_id = mp.id and
f.pricing_table = 'monthly_price' ;
For the columns you reference, you need to use coalesce() to combine the results from the three tables. You say that the tables have different data, so you would need to list the columns separately.
The main reason I recommend this approach is performance. I think the left joins should be faster than any solution that uses union all.
Could you get your expected result using by a derived table with UNION SELECT which has a column of each table name?
SELECT *
FROM food
LEFT JOIN
(
SELECT 'daily_price' AS price_table, * FROM daily_price
UNION ALL SELECT 'monthly_price', * FROM monthly_price
UNION ALL SELECT 'weekly_price', * FROM weekly_price
) t
ON food.price_table = t.price_table AND
food.pricing_reference_id = t.id
ORDER BY food.id;
dbfiddle

Is it possible to replace content from multiple foreign keys when doing a query?

I have the following tables:
TABLE PLAYER
id | name
1 | A
2 | B
3 | C
4 | D
TABLE PAIRINGS
id | player_a | player_b
1 | 3 |1
2 | 2 |4
Both columns in table Pairings are foreign keys to column id of table player.
My problem is, I would like to avoid making a query from code every time I want to know which is my player's name (like, Select name From Player Where Id = pairings.player_a). I have thought about adding Name as an extra columnd to Pairings table, but that would denormalize it.
Instead, it would be nice if I could get the names in just one query, like:
RESULT
player_a | player_b
C | A
B | D
Is it this possible? Thanks so much in advance.
You may join the PAIRINGS table to the PLAYER table, twice:
SELECT
p1.name AS player_a,
p2.name AS player_b
FROM PAIRINGS pr
INNER JOIN PLAYER p1
ON pr.player_a = p1.id
INNER JOIN PLAYER p2
ON pr.player_b = p2.id;
Demo
Don't do it! One of the points of using a relational database is that data is stored in only one place. That is a big convenience. Of course, there are exceptions, but these exceptions should have firm reasons.
In this case, just define a view:
CREATE VIEW vw_pairings AS
SELECT p.*, pa.name AS player_a_name,
pb.name AS player_b_name
FROM PAIRINGS p JOIN
PLAYER pa
ON p.player_a = pa.id JOIN
PLAYER pb
ON p.player_b = pb.id;
When you query from the view, you will see the names, along with all the other information in the PAIRINGS table.
Hope can help you
Select * Into #PLAYER From (
Select 1 [ID], 'A' [Name] Union All
Select 2 [ID], 'B' [Name] Union All
Select 3 [ID], 'C' [Name] Union All
Select 4 [ID], 'D' [Name]
) A
Select * Into #PAIRINGS From (
Select 1 [ID], 3 [PLAYER_A], 1 [PLAYER_B] Union All
Select 2 [ID], 2 [PLAYER_A], 4 [PLAYER_B]
) A
Select
P.ID, A.NAME, B.NAME
From #PAIRINGS P
Left Join #PLAYER A On A.ID = P.PLAYER_A
Left Join #PLAYER B On B.ID = P.PLAYER_B
You can create view, for avoid making query
Example
Create View vwPAIRINGS As
Select
P.ID, A.NAME, B.NAME
From #PAIRINGS P
Left Join #PLAYER A On A.ID = P.PLAYER_A
Left Join #PLAYER B On B.ID = P.PLAYER_B
After that, just select usual
Select * from vwPAIRINGS

Select with count on 3 tables

I need your help for a particular SELECT on 3 tables. I'm not skilled on SQL so it's a difficult SELECT for me, since I have to apply COUNT (I suppose) to the query.
I show you my tables:
I need to know how many contacts there are in the database (all the contacts!!!!) and how many photos and videos are bound to any contact.
I should get a result similar to this:
-----------------------------------
| ID | NAME | PHOTO | VIDEO |
-----------------------------------
| 1 | MARK | 3 | 1 |
-----------------------------------
| ID | LISA | 2 | 0 |
-----------------------------------
Thank you for your help
You can use the following approach, if you are hesitant about duplicates in the query you can use a sql function and pass type parameter as a string. If you have uncertain number of types (VIDEO, PHOTO, TEXT etc) you need to redesign the output table format (I would go with the following tuple TYPE, CONTACT_ID, COUNT), or at the worst case go with dynamic query construction.
select c.ID, c.NAME,
(select count(*) from CONTACT_MEDIA cm join MEDIA m on
m.ID = cm.ID_MEDIA and m.TYPE = 'PHOTO' where cm.ID_CONTACT = c.ID) as PHOTO,
(select count(*) from CONTACT_MEDIA cm join MEDIA m on
m.ID = cm.ID_MEDIA and m.TYPE = 'VIDEO' where cm.ID_CONTACT = c.ID) as VIDEO
from CONTACT c
Please use below query , this will give you exact result
select contact_media.ID_Contact, contact.Name, count(M1.ID) as 'PHOTO', COUNT(M2.ID) as 'VIDEO' from Contact inner join contact_media on Contact.ID=contact_media.ID_Contact
left outer join media M1 on contact_media.ID_Media=M1.ID and M1.TYPE='PHOTO'
left outer join media M2 on contact_media.ID_Media=M2.ID and M2.TYPE='VIDEO'
group by contact_media.ID_Contact, contact.Name

SQL Server - only join if condition is met

I have three tables (at least, something similar) with the following relationships:
Item table:
ID | Val
---------+---------
1 | 12
2 | 5
3 | 22
Group table:
ID | Parent | Range
---------+---------+---------
1 | NULL | [10-30]
2 | 1 | [20-25]
3 | NULL | [0-15]
GroupToItem table:
GroupID | ItemID
---------+---------
1 | 1
1 | 3
And now I want to add rows to the GroupToItem table for Groups 2 and 3, using the same query (since some other conditions not shown here are more complicated). I want to restrict the items through which I search if the new group has a parent, but to look through all items if there is not.
At the moment I am using an IF/ELSE on two statements that are almost exactly the same, but for the addition of another JOIN row when a parent exists. Is it possible to do a join to reduce the number of items to look at, only if a restriction is possible?
My two queries as they stand are given below:
DECLARE #GroupID INT = 2;...
INSERT INTO GroupToItem(GroupID, ItemID)
SELECT g.ID,
i.ID,
FROM Group g
JOIN Item i ON i.Val IN g.Range
JOIN GroupToItem gti ON g.Parent = gti.GroupID AND i.ID = gti.ItemID
WHERE g.ID = #GroupID
-
DECLARE #GroupID INT = 3;...
INSERT INTO GroupToItem(GroupID, ItemID)
SELECT g.ID,
i.ID,
FROM Group g
JOIN Item i ON i.Val IN g.Range
WHERE g.ID = #GroupID
So essentially I only want to do the second JOIN if the given group has a parent. Is this possible in a single query? It is important that the number of items that are compared against the range is as small as possible, since for me this is an intensive operation.
EDIT: This seems to have solved it in this test setup, similar to what was suggested by Denis Valeev. I'll accept if I can get it to work with my live data. I've been having some weird issues - potentially more questions coming up.
SELECT g.Id,
i.Id
FROM Group g
JOIN Item i ON (i.Val > g.Start AND i.Val < g.End)
WHERE g.Id = 2
AND (
(g.ParentId IS NULL)
OR
(EXISTS(SELECT 1 FROM GroupToItem gti WHERE g.ParentId = gti.GroupId AND i.Id = gti.ItemId))
)
SQL Fiddle
Try this:
INSERT INTO GroupToItem(GroupID, ItemID)
SELECT g.ID,
i.ID,
FROM Group g
JOIN Item i ON i.Val IN g.Range
WHERE g.ID = #GroupID
and (g.ID in (3) or exists (select top 1 1 from GroupToItem gti where g.Parent = gti.GroupID AND i.ID = gti.ItemID))
If a Range column is a varchar datatype, you can try something like this:
INSERT INTO GROUPTOITEM (GROUPID, ITEMID)
SELECT A.ID, B.ID
FROM GROUP AS A
LEFT JOIN ITEM AS B
ON B.VAL BETWEEN CAST(SUBSTRING(SUBSTRING(A.RANGE,1,CHARINDEX('-',A.RANGE,1)-1),2,10) AS INT)
AND CAST(REPLACE(SUBSTRING(A.RANGE,CHARINDEX('-',A.RANGE,1)+1,10),']','') AS INT)