CASE for joining sql tables - sql

I need a help on sql database side. And i have
table 1 : ENTITY_TYPE
entity_type_id entity_name
1 Task
2 Page
3 Project
4 Message
5 User
and table 2 : MESSAGE , that contains message from each entity values like
message_id entity_type owner_tableid message
1 1 12 A message on task level
2 3 14 A message on project level
and I want select these message according to each entity type and details from its owner table using 'owner_tableid' ie a query like....
select * from MESSAGE JOIN
case entity_type when 1 then taskTable
when 2 then pageTable
when 3 then projectTable
when 4 then MessageTable
when 5 then UserTable
Which is best method to solve this issue on single procedure. Any idea ?? Now I am using IF clause for each entity...

You can't parameterise the tables involved in a query (so you can't put a table name in a variable and expect that to be used either).
One way to do it is as a chain of left joins:
select
* /* TODO - Pick columns */
from
MESSAGE m
left join
taskTable tt
on
m.entity_type = 1 and
m.owner_entity_id = tt.id
left join
pageTable pt
on
m.entity_type = 2 and
m.owner_entity_id = pt.id
left join
projectTable prt
on
m.entity_type = 3 and
m.owner_entity_id = prt.id
left join
MessageTable mt
on
m.entity_type = 4 and
m.owner_entity_id = mt.id
left join
UserTable ut
on
m.entity_type = 5 and
m.owner_entity_id = ut.id
If you want values from these tables to appear in a single column in the result, use a COALESCE across all of the values, e.g.
COALESCE(tt.Value,pt.Value,prt.Value,mt.Value,ut.Value) as Value

Use Union Clause with your individual entity_type
SELECT * FROM Message
JOIN pageTable ON ....
WHERE entity_type = 1
UNION ALL
..........
entity_type = 2
UNION ALL
..........
entity_type = 3

Select ...
From Message
Join (
Select 1 As entity_type, id
From taskTable
Union All
Select 2, id
From pageTable
Union All
Select 3, id
From projectTable
Union All
Select 4, id
From messageTable
Union All
Select 5, id
From userTable
) As Z
On Z.entity_type = Message.entity_type
And Z.id = Message.owner_tableid

If you need to return several entity_types details in one query, than UNION might help:
SELECT interesting_columns FROM Message
JOIN pageTable ON (joinPredicate)
WHERE entity_type = 1
UNION ALL
SELECT interesting_columns FROM Message
JOIN pageTable ON (joinPredicate)
WHERE entity_type = 2
-- ...
But if you only need details of certain entity_type than you original solution with IF would be much better.

Related

Subquery SQL for link name and firstname

Hello I would like to retrieve the name and the first name in the user table thanks to the id contained in the message table (id_receive and id_send) in sql via a subquery
SELECT user.nom FROM user
WHERE user.id IN (
SELECT message.id_send, message.id_receive FROM message WHERE message.id=1
)
```
I would recommend using EXISTS, twice:
SELECT u.nom
FROM user u
WHERE EXISTS (SELECT 1 FROM message m WHERE m.id = 1 AND u.id = id_send) OR
EXISTS (SELECT 1 FROM message m WHERE m.id = 1 AND u.id = id_receive) ;
However, a JOIN might also be appropriate:
SELECT u.nom
FROM user u JOIN
message m
ON u.id IN (m.id_send, id_receive)
WHERE m.id = 1;
I suspect it isn't actually what you want but it looks like this is what you're trying to do:
SELECT user.nom FROM user
WHERE user.id IN (
SELECT message.id_send FROM message WHERE message.id=1
UNION ALL
SELECT message.id_receive FROM message WHERE message.id=1
)
The query that drives the IN should return a single column of values
Try and conceive that in works like this:
SELECT * FROM t WHERE c IN(
1
2
3
)
Not like this:
SELECT * FROM t WHERE c IN(
1 2 3
)
Nor like this:
SELECT * FROM t WHERE c IN(
1 2 3
4 5 6
)
It might help you reember that the query inside it must return a single column, but multiple rows, all of qhich are searched for a matching value c by IN
Small addition to your original query to make it working:
SELECT user.nom FROM user
WHERE user.id IN (
SELECT unnest(array[message.id_send, message.id_receive])
FROM message
WHERE message.id=1
)

Use SELECT as value for JOIN

I have table ItemPropertyValue with unique ID and PropertyID
ID IDProperty Value ItemID
1 1 1 5
2 2 2 6
3 3 2 10
4 4 2 15
And another table called PropertyCategory
IDProperty Value Name
1 1 First Option
1 2 Second Option
2 1 Another option
What I want - it's to select PropertyID from first table ItemPropertyValue where ItemID = 10 and then join with second table PropertyCategory
So I have smth like this:
IDProperty Value Name
1 1 First Option
1 2 Second Option
2 1 Another option
I tried this but there is an error. No such column as PropertyID
SELECT * FROM PropertyCategory JOIN
(SELECT ItemPropertyValue.IDProperty AS PropertyID
WHERE ItemPropertyValue.IDItem = '10')
ON PropertyCategory.IDProperty = PropertyID
How can I do it?
MS SQLServer if any
Try Aliasing the subselect
SELECT * FROM PropertyCategory JOIN
(SELECT ItemPropertyValue.IDProperty AS PropertyID
WHERE ItemPropertyValue.IDItem = '10') a
ON PropertyCategory.IDProperty = a.PropertyID
Additionally, this would do well as a regular join with the filtrating happening in the outer WHERE clause, like
SELECT *
FROM PropertyCategory
JOIN ItemPropertyValue ON PropertyCategory.IDProperty = ItemPropertyValue.IDProperty
WHERE ItemPropertyValue.IDProperty = '10'
Finally, if you happen to be using SQL Server and really want to do it in a subselect-type of statement, I'd suggest checking out CROSS APPLY or OUTER APPLY for such an application.
Use table alias:
SELECT *
FROM PropertyCategory
JOIN
(SELECT ItemPropertyValue.IDProperty AS PropertyID
WHERE ItemPropertyValue.IDItem = '10') as T
ON PropertyCategory.IDProperty = T.PropertyID
I would go for something like
SELECT * FROM ItemPropertyValue JOIN PropertyCategory
ON ItemPropertyValue.IDProperty = PropertyCategory.IDProperty
WHERE ItemPropertyValue.ItemID=10
You can restrict to couple of field in your SELECT by replacing the * with fields.
you are doing fine, just add alias to table name and it will work
SELECT * FROM PropertyCategory a JOIN
(SELECT ItemPropertyValue.IDProperty AS PropertyID
WHERE ItemPropertyValue.IDItem = '10') b
ON a.IDProperty = b.PropertyID

SELECT Data from multiple tables?

I have 3 tables, with 3 fields all the same. I basically want to select information from each table
For example:
userid = 1
I want to select data from all 3 tables, where userid = 1
I am currently using:
SELECT r.*,
p.*,
l.*
FROM random r
LEFT JOIN pandom p ON r.userid = p.userid
LEFT JOIN landom l ON l.userid = r.userid
WHERE r.userid = '1'
LIMIT 0, 30
But it doesn't seem to work.
with 3 fields all the same
So you mean that you want the same 3 fields from all 3 tables?
SELECT r.col1, r.col2, r.col3
FROM random r
WHERE r.userid = '1'
LIMIT 0, 30
UNION ALL
SELECT p.pcol1, p.pcol_2, p.p3
FROM pandom p
WHERE p.userid = '1'
LIMIT 0, 30
UNION ALL
SELECT l.l1, l.l2, l.l3
FROM landom l
WHERE l.userid = '1'
LIMIT 0, 30
The fields don't have to be named the same, but the same types need to line up in position 1, 2 and 3.
The way the limits work is:
it will attempt to get 30 from random.
If it has 30 already, it won't even look at the other 2 tables
if it has less than 30 from random, it will try to fill up to 30 from pandom and only finally landom
SELECT t1.*, t2.*, t3.*
FROM `random` as t1, `pandom` as t2, `landom` as t3
WHERE t1.`userid`='1' AND t2.`userid`='1' AND t3.`userid`='1'
SELECT * FROM `random`
JOIN `pandom` USING (`userid`)
JOIN `landom` USING (`userid`)
WHERE `userid`='1'

Using (IN operator) OR condition in Where clause as AND condition

Please look at following image, I have explained my requirements in the image.
alt text http://img30.imageshack.us/img30/5668/shippment.png
I can't use here WHERE UsageTypeid IN(1,2,3,4) because this will behave as an OR condition and fetch all records.
I just want those records, of first table, which are attached with all 4 ShipmentToID .
All others which are attached with 3 or less ShipmentToIDs are not needed in result set.
Thanks.
if (EntityId, UsageTypeId) is unique:
select s.PrimaryKeyField, s.ShipmentId from shipment s, item a
where s.PrimaryKeyField = a.EntityId and a.UsageTypeId in (1,2,3,4)
group by s.PrimaryKeyField, s.ShipmentId having count(*) = 4
otherwise, 4-way join for the 4 fields,
select distinct s.* from shipment s, item a, item b, item c, item d where
s.PrimaryKeyField = a.EntityId = b.EntityId = c.EntityId = d.EntityId and
a.UsageTypeId = 1 and b.UsageTypeId = 2 and c.UsageTypeId = 3 and
d.UsageTypeId = 4
you'll want appropriate index on (EntityId, UsageTypeId) so it doesn't hang...
If there will never be duplicates of the UsageTypeId-EntityId combo in the 2nd table, so you'll never see:
EntityUsageTypeId | EntityId | UsageTypeId
22685 | 4477 | 1
22687 | 4477 | 1
You can count matching EntityIds in that table.
WHERE (count(*) in <tablename> WHERE EntityId = 4477) = 4
DECLARE #numShippingMethods int;
SELECT #numShippingMethods = COUNT(*)
FROM shippedToTable;
SELECT tbl1.shipmentID, COUNT(UsageTypeId) as Usages
FROM tbl2 JOIN tbl1 ON tbl2.EntityId = tbl1.EntityId
GROUP BY tbl1.EntityID
HAVING COUNT(UsageTypeId) = #numShippingMethods
This way is preferred to the multiple join against same table method, as you can simply modify the IN clause and the COUNT without needing to add or subtract more tables to the query when your list of IDs changes:
select EntityId, ShipmentId
from (
select EntityId
from (
select EntityId
from EntityUsage eu
where UsageTypeId in (1,2,3,4)
group by EntityId, UsageTypeId
) b
group by EntityId
having count(*) = 4
) a
inner join Shipment s on a.EntityId = s.EntityId

Left join not pulling through nulls where expected

I have a query that looks something like this (I've changed the table names):
select #user_id
, isnull(ur.rule_value, isnull(manr.rule_value, def.rule_value)) [rule_value]
, isnull(urt.name, isnull(manrt.name, def.name)) [rule_type]
from (select #user_id [user_id]
, rule.rule_value
, rule_type.name
from rule
join rule_type on rule_type.rule_type_id = rule.rule_type_id
where rule.user_id = 1) def
join user on user.user_id = def.user_id
join manager man on man.manager_id = user.manager_id
left join rule ur on ur.user_id = user.user_id
left join rule_type urt on urt.rule_type_id = ur.rule_type_id
left join rule manr on manr.manager_id = man.manager_id
left join rule_type manrt on manrt.rule_type_id = manr.rule_type_id
What I'm expecting is that when there isn't a rule for the user or the user's manager, then the default rule should be used. However, I'm only getting a result if the user has a rule.
I've tried left joining everything but to no avail, and the select statement for the def table brings back all the default rules.
What am I doing wrong?
#user_id is a variable.
Update
Example of schema
rule
rule_id user_id manager_id rule_value
1 1 1 27
2 1 1 24
3 1 1 25
4 1 1 44
5 1 1 88
1 2 4 2
2 2 4 23
3 2 4 18
3 NULL 4 19
4 NULL 4 20
5 NULL 4 21
rule_type
rule_id name
1 'Craziness'
2 'Number of legs'
3 'Hair ranking'
4 'Banana preference'
5 'Rule 5'
user
user_id manager_id ... other columns
1 1
2 4
3 4
manager
manager_id ... other columns
1
2
3
4
5
6
So if #user_id is 2 then I would expect the output
2, 2, 'Craziness'
2, 23, 'Number of legs'
2, 18, 'Hair ranking'
2, 20, 'Banana preference'
2, 21, 'Rule 5'
But if #user_id is 3 then I would expect the output
3, 27, 'Craziness'
3, 24, 'Number of legs'
3, 19, 'Hair ranking'
3, 20, 'Banana preference'
3, 21, 'Rule 5'
A regular join is actually a left inner join. What you actually want is an outer join, which fetches the result, even if it cannot join it with the other table.
To answer the question in the title, a LEFT JOIN does NOT act like a REGULAR JOIN. The LEFT JOIN is the same as the LEFT OUTER JOIN, meaning will also return the records in the LEFT table that don't match the ON criteria.
Get rid of this :
left join rule ur on ur.user_id = user.user_id
left join rule_type urt on urt.rule_type_id = ur.rule_type_id
left join rule manr on manr.manager_id = man.manager_id
left join rule_type manrt on manrt.rule_type_id = manr.rule_type_id
and replace it with this :
left join rule ur on ur.user_id = user.user_id or ur.user_id = man.manager_id
left join rule_type urt on urt.rule_type_id = ur.rule_type_id
then change your opening SELECT to :
select #user_id
, isnull(ur.rule_value, def.rule_value) [rule_value]
, isnull(urt.name, def.name) [rule_type]
Essentially what you're doing wrong is that you have one entity (user-manager-default) and you're trying to link it to a different entity (rule) via two different joins. If one join doesn't work but the other does, there's no matching NULL returned for your ISNULL to find. It's a little counter-intuitive.
I've split the query into two parts.
I think because the table structures are more complicated than I put into the original question, this means that CodeByMoonlight's answer isn't quite enough - it was giving me duplicate rows that I couldn't account for.
My solution is as follows (with respect to the details I gave in the question):
create table #user_rules
(
user_id int,
rule_value int,
rule_type varchar(255),
rule_type_id,
)
--Insert default values
insert into #user_rules
select #user_id [user_id]
, rule.rule_value
, rule_type.name
, rule_type.rule_type_id
from rule
join rule_type on rule_type.rule_type_id = rule.rule_type_id
where rule.user_id = 1
--Update table with any available values
update #user_rules
set rule_value = ur.rule_value
from user u
join manager m on u.manager_id = m.manager_id
join rule ur on ur.user_id = u.user_id or (ur.manager_id = man.manager_id and ur.user_id is null)
join rule_type urt on urt.rule_type_id = ur.rule_type_id
where urt.rule_type_id = #urse_rules.rule_type_id
and u.user_id = #user_id