SQL Query Help - Joining Multiple Columns Based On Condition [duplicate] - mysql

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
SQL query help - have two where conditons in join condition
I have the following tables with the columns as below. I have mentioned what I need from this. I already posted a link in here SQL query help - have two where conditons in join condition with what I have been trying but cannot get this through. Once again positing it plainly with what I need:
Book
BookId, BookName
Desk
DeskId, BookId ,DeskName
CounterParty
CPId, CpName
Trade
TradeId, Buyer, Seller
This is how the Buyer and Seller data would be :
Buyer Seller
B3232 B323
C32 B222
B323 C323
Based on the starting character B or C in these two columns, I need to join Book or CP table to check the ids.
I need **t.TradingDeskName, b.BookName, c.CpName, t.Buyer, t.Seller.**
Any help is very much appreciated.
Thanks,
mani
p.s : I am trying to get this done through SQL or Linq to Sql.
The recent query but have more to fix :
SELECT DISTINCT desk.Name as TradingDeskName, b.Name as Book, t.Seller, t.Buyer, c.PartyName, FROM TradingDesk AS desk
RIGHT JOIN Book as b
ON b.TradingDeskId = d.Id
RIGHT JOIN Trade as t
ON LEFT(t.Buyer, 1) = 'B' AND SUBSTRING(t.Buyer, 2, len(t.Buyer)) = b.Id
LEFT JOIN Book as b1
ON LEFT(t.Seller, 1) = 'B' AND SUBSTRING(t.Seller, 2, len(t.Seller)) = b1.Id
LEFT JOIN CounterParty as c
ON LEFT(t.Buyer, 1) = 'C' AND SUBSTRING(t.Buyer, 2, len(t.Buyer)) = c.PartyId
LEFT JOIN CounterParty as c1
ON LEFT(t.Seller, 1) = 'C' AND SUBSTRING(t.Seller, 2, len(t.Seller)) = c1.PartyId
As I mentioned I need :
Desk.Name - B.Name - T.Seller - T.Buyer- C.PartyName
The C.PartyName will have the value if T.Seller or T.Buyer value is starting with 'C' (from CounterParty Table) else will be null.
With the above query, I have null values coming in Desk.Name, B.Name and the logic of gettig C.PartyName is also not working.

There are a couple ways I could think of for achieving the desired results but because first things should come first, I'd suggest to modify the DB design if it is at all possible.
So, here are the 2 queries that I could work out:
Query 1
SELECT `t`.*,
(CASE
WHEN LEFT(`t`.`Buyer`, 1) = 'B' THEN
(SELECT `b`.`BookName`
FROM `Book` `b`
WHERE `b`.`BookId` = SUBSTRING(`t`.`Buyer`, 2))
ELSE (SELECT `c`.`CPName`
FROM `CounterParty` `c`
WHERE `c`.`CPId` = SUBSTRING(`t`.`Buyer`, 2))
END) AS `buyer_name`,
(CASE
WHEN LEFT(`t`.`Seller`, 1) = 'B' THEN
(SELECT `b`.`BookName`
FROM `Book` `b`
WHERE `b`.`BookId` = SUBSTRING(`t`.`Seller`, 2))
ELSE (SELECT `c`.`CPName`
FROM `CounterParty` `c`
WHERE `c`.`CPId` = SUBSTRING(`t`.`Seller`, 2))
END) AS `seller_name`
FROM `Trade` `t`
Query 2
SELECT *
FROM `Trade` `t`
LEFT JOIN `Book` `b` ON LEFT(`t`.`Buyer`, 1) = 'B' AND SUBSTRING(`t`.`Buyer`, 2) = `b`.`BookId`
LEFT JOIN `Book` `b1` ON LEFT(`t`.`Seller`, 1) = 'B' AND SUBSTRING(`t`.`Seller`, 2) = `b1`.`BookId`
LEFT JOIN `CounterParty` `c` ON LEFT(`t`.`Buyer`, 1) = 'C' AND SUBSTRING(`t`.`Buyer`, 2) = `c`.`CPId`
LEFT JOIN `CounterParty` `c1` ON LEFT(`t`.`Seller`, 1) = 'C' AND SUBSTRING(`t`.`Seller`, 2) = `c1`.`CPId`;
Both the above queries return same results but in different formats. Please try and see which one works best for you.
Also, it isn't very clear from your question where does the table Desk fit in and what relations does it hold with other tables. Please feel free to add respective columns you'll need from Desk.
Please note that the suggested queries are in MySQL. It is not very clear what system are you running - you've mentioned in your post that you are trying using SQL or Linq SQL and in the tags you've mentioned everything + MySQL.

You could do it something like this (untested):
select
t.Buyer,
t.Seller,
case when t.Buyer like 'B%' THEN (select BookName from Book where BookId = t.Buyer)
ELSE (select CpName from Counterparty where CPId = t.Buyer)
end BuyerName,
case when t.Buyer like 'B%' THEN (select DeskName from Desk where BookId = t.Buyer)
ELSE NULL
end BuyerDeskName,
case when t.Seller like 'B%' THEN (select BookName from Book where BookId = t.Seller)
ELSE (select CpName from Counterparty where CPId = t.Seller)
end SellerName,
case when t.Seller like 'B%' THEN (select DeskName from Desk where BookId = t.Seller)
ELSE NULL
end SellerDeskName,
from
Trade t
The problem you have is that, since the table you want to join to is data driven, you can't specify it in the FROM clause..

Related

mysql: duplicate column name on join with subquery

I've searched a lot but I still don't get it.
Here's my sample code
SELECT sp.testno, sp.companyid, st.*
FROM sponsor AS sp
LEFT JOIN
(
SELECT a.sponsorempno, (CASE WHEN t.companyid IS NULL OR t.companyid = '' THEN'aa' ELSE t.companyid END) agncy, a.controlno, a.tnpl, t.*
FROM applicant AS a
LEFT JOIN
test AS t
ON a.controlno = t.controlno
) AS st
ON sp.testno = st.testno
I still returns an error:
#1060 - Duplicate column name 'controlno'
Can somebody tell me what's wrong with the code?
In the subselect of your join, you are selecting a.controlno and by t.* t.controlno.
You should provide an alias for one of the selected columns. In your case a.controlno. This is necessary, because the table aliases of the inner select are lost, when accessing it from the outer one.
The statement below should work, if there aren't any other duplicate column names in test and the set of used columns from applicant.
SELECT sp.testno, sp.companyid, st.*
FROM sponsor AS sp
LEFT JOIN
(
SELECT a.sponsorempno, (CASE WHENt.companyid IS NULL OR t.companyid = '' THEN'aa' ELSE t.companyid END) agncy, a.controlno as a_controlno, a.tnpl, t.*
FROM applicant AS a
LEFT JOIN
test AS t
ON a.controlno = t.controlno
) AS st
ON sp.testno = st.testno

MYSQL SELECT is slow when crossing multiple tables

I have a mysql query which is to return the only 1 record that need to cross multiple table. However, the mysql query is slow when executing.
Query:
SELECT *,
(SELECT TreeName FROM sys_tree WHERE TreeId = Mktg_Unit_Booking.ProjectLevelId) AS PhaseName,
(CASE WHEN ProductType = 'U' THEN (SELECT UnitNo FROM prop_unit pu WHERE pu.UnitId = mktg_unit_booking.UnitId)
ELSE (SELECT BayNo FROM prop_car_park pcp WHERE pcp.CarParkId = UnitId) END) AS UnitNo,
(SELECT CustomerName FROM mktg_customer mc WHERE mc.CustomerId = mktg_unit_booking.CustomerId) AS CustomerName
FROM Mktg_Unit_Booking
WHERE IsDeleted <> '1' AND IsApproved = '1'
AND UnitId = 1110 AND ProductType = 'U'
ORDER BY UnitNo
I have run EXPLAIN in the query and I got this:
Any other suggestion on how to improve the speed of the query?
Thank you!
you are doing the cross product, instead of that you should use join.
Don't use sub-queries in select statement instead use proper join on Mktg_Unit_Booking in after from statement.
you query should something look like :
select
sys_tree.TreeName AS PhaseName,
case
WHEN Mktg_Unit_Booking.ProductType = 'U' then prop_unit.UnitNo
else prop_car_park.BayNo
end as UnitNo,
mktg_customer.CustomerName AS CustomerName
FROM Mktg_Unit_Booking
left join sys_tree on sys_tree.TreeId = Mktg_Unit_Booking.ProjectLevelId
left join prop_unit on prop_unit.UnitId = Mktg_Unit_Booking.UnitId
left join prop_car_park on prop_car_park.CarParkId = Mktg_Unit_Booking.UnitId
left join mktg_customer on mktg_customer.CustomerId = Mktg_Unit_Booking.CustomerId
WHERE IsDeleted <> '1' AND IsApproved = '1'
AND UnitId = 1110 AND ProductType = 'U'
ORDER BY UnitNo;
I have assumed that each table consists of only 1 matching tuple. If there are more then your logic needs to be modified.

Cross Referencing Multiple Tables

What I was trying to do is to get data from multiple tables, supposed that I have the following results in my query:
The numbers in the column ticket_item_type represents certain table. For example, 2 is for tbl_company and 3 is for tbl_lease. Then the details represents the id of a certain record in that table.
Suppose that I want to get the title of those records using ticket_item_type and details. Is it possible to embed it to the results? Or should I make separate queries for each.
I know JOIN, but I is it only for single table?
Here's my MYSQL query for the image above:
SELECT *
FROM
(SELECT *
FROM ticket_items
WHERE hs_customer = 1
AND ticket IN
(SELECT id
FROM tickets
WHERE hs_customer='1'
AND ticket_status = 'dispatch_reviewed')
AND ticket IN
(SELECT ticket
FROM ticket_items
WHERE ticket_item_type = 5
AND details = '159')) AS TB1
WHERE ticket_item_type IN (3,
2,
8)
You could try something like this:
SELECT
TB1.*,
CASE
WHEN TB1.ticket_item_type = 2 THEN t2.title
WHEN TB1.ticket_item_type = 3 THEN t3.title
WHEN TB1.ticket_item_type = 8 THEN t8.title
ELSE 'NA'
END as title
FROM
(
SELECT *
FROM ticket_items
WHERE hs_customer = 1
AND ticket IN (SELECT id FROM tickets WHERE hs_customer='1' AND ticket_status = 'dispatch_reviewed')
AND ticket IN (SELECT ticket FROM ticket_items WHERE ticket_item_type = 5 AND details = '159')
) AS TB1
LEFT JOIN tbl_company t2 ON TB1.details = t2.id
LEFT JOIN tbl_lease t3 ON TB1.details = t3.id
LEFT JOIN tbl_next t8 ON TB1.details = t8.id
WHERE ticket_item_type IN (3, 2, 8)
However, this is not a design that I would prefer. Without looking at details of your database it's going to be hard to write a query to cover multiple types of ticket_item_type. I hope this query works for you, though.

SQL: Get latest entries from history table

I have 3 tables
person (id, name)
area (id, number)
history (id, person_id, area_id, type, datetime)
In this tables I store the info which person had which area at a specific time. It is like a salesman travels in an area for a while and then he gets another area. He can also have multiple areas at a time.
history type = 'I' for CheckIn or 'O' for Checkout.
Example:
id person_id area_id type datetime
1 2 5 'O' '2011-12-01'
2 2 5 'I' '2011-12-31'
A person started traveling in area 5 at 2011-12-01 and gave it back on 2011-12-31.
Now I want to have a list of all the areas all persons have right now.
person1.name, area1.number, area2.number, area6.name
person2.name, area5.number, area9.number
....
The output could be like this too (it doesn't matter):
person1.name, area1.number
person1.name, area2.number
person1.name, area6.number
person2.name, area5.number
....
How can I do that?
This question is, indeed, quite tricky. You need a list of the entries in history where, for a given user and area, there is an 'O' record with no subsequent 'I' record. Working with just the history table, that translates to:
SELECT ho.person_id, ho.area_id, ho.type, MAX(ho.datetime)
FROM History AS ho
WHERE ho.type = 'O'
AND NOT EXISTS(SELECT *
FROM History AS hi
WHERE hi.person_id = ho.person_id
AND hi.area_id = ho.area_id
AND hi.type = 'I'
AND hi.datetime > ho.datetime
)
GROUP BY ho.person_id, ho.area_id, ho.type;
Then, since you're really only after the person's name and the area's number (though why the area number can't be the same as its ID I am not sure), you need to adapt slightly, joining with the extra two tables:
SELECT p.name, a.number
FROM History AS ho
JOIN Person AS p ON ho.person_id = p.id
JOIN Area AS a ON ho.area_id = a.id
WHERE ho.type = 'O'
AND NOT EXISTS(SELECT *
FROM History AS hi
WHERE hi.person_id = ho.person_id
AND hi.area_id = ho.area_id
AND hi.type = 'I'
AND hi.datetime > ho.datetime
);
The NOT EXISTS clause is a correlated sub-query; that tends to be inefficient. You might be able to recast it as a LEFT OUTER JOIN with appropriate join and filter conditions:
SELECT p.name, a.number
FROM History AS ho
JOIN Person AS p ON ho.person_id = p.id
JOIN Area AS a ON ho.area_id = a.id
LEFT OUTER JOIN History AS hi
ON hi.person_id = ho.person_id
AND hi.area_id = ho.area_id
AND hi.type = 'I'
AND hi.datetime > ho.datetime
WHERE ho.type = 'O'
AND hi.person_id IS NULL;
All SQL unverified.
You're looking for results where each row may have a different number of columns? I think you may want to look into GROUP_CONCAT()
SELECT p.`id`, GROUP_CONCAT(a.`number`, ',') AS `areas` FROM `person` a LEFT JOIN `history` h ON h.`person_id` = p.`id` LEFT JOIN `area` a ON a.`id` = h.`area_id`
I haven't tested this query, but I have used group concat in similar ways before. Naturally, you will want to tailor this to fit your needs. Of course, group concat will return a string so it will require post processing to use the data.
EDIT I thikn your question has been edited since I began responding. My query does not really fit your request anymore...
Try this:
select *
from person p
inner join history h on h.person_id = p.id
left outer join history h2 on h2.person_id = p.id and h2.area_id = h.area_id and h2.type = 'O'
inner join areas on a.id = h.area_id
where h2.person_id is null and h.type = 'I'

Hibernate HQL - Use CASE WHEN in COUNT Statement like IF in MySQL

I am trying to port a MySQL Query that works to Hibernate HQL, this is all very new to me, so I am open to any kind of hint (Wrong Way, Wrong Structure, change all... ;) )
Two tables A and B. (Structure broken down, only the relevant parts)
A contains entrys, each with a unique ID.
B references those IDs and holds a boolean-like marker (TINYINT(1)).
I want to know how many rows there are in B for each row in A with the Id from A's Row and Marker == True (1).
My MySQL query was like this:
SELECT A.id, COUNT( IF( B.marker = 1, 1, NULL ) ) AS markerTrue, COUNT( IF( B.marker =0, 1, NULL ) ) AS markerFalse FROM A LEFT JOIN B ON B.a_id = A.id GROUP BY A.id
It works and I ported it to this (HQL):
SELECT A.id, COUNT(CASE WHEN B.marker = 1 THEN 1 ELSE NULL END) as markerTrue, COUNT(CASE WHEN B.marker = 0 THEN 1 ELSE NULL END) as markerFalse FROM A LEFT JOIN B WITH B.a_id = A.id GROUP BY A.id
This throws an Exception:
org.hibernate.hql.ast.QuerySyntaxException: unexpected token: CASE near ...
In the logs, there is also
org.hibernate.hql.ast.ErrorCounter - line 1:19: unexpected token: CASE
antlr.NoViableAltException: unexpected token: CASE
But thats just the same internal Error.
Is there a way to do this in HQL? Is there another better way, like restructuring the tables, what is an experts opinion on this?
I am by no means an expert — when HQL stymies me, I rarely have qualms about bypassing the problem by switching to straight SQL — so I can't tell you if there is a better, more HQL-ish way to do this. But in your specific instance, where B.marker is always either 0 or 1, I suppose you could change
COUNT(CASE WHEN B.marker = 1 THEN 1 ELSE NULL END)
to
SUM(B.marker)
and
COUNT(CASE WHEN B.marker = 0 THEN 1 ELSE NULL END)
to
COUNT(*) - SUM(B.marker)
(though you may also need to wrap your SUMs in COALESCE(..., 0) — I'm not sure).
A rewrite in SQL. I hope it's more easily converted to HQL:
SELECT A.id
, COALESCE(markerTrue, 0) AS markerTrue
, COALESCE(markerFalse, 0) AS markerFalse
FROM A
LEFT JOIN
( SELECT a_id
, COUNT(*) AS markerTrue
FROM B
WHERE marker = 1
GROUP BY a_id
) AS BT
ON BT.a_id = A.id
LEFT JOIN
( SELECT a_id
, COUNT(*) AS markerFalse
FROM B
WHERE marker = 0
GROUP BY a_id
) AS BF
ON BF.a_id = A.id
Oops, maybe too late?
Try :
SUM(CASE WHEN B.marker = 0 THEN 1 ELSE NULL END) AS your_result
as there is no "conditionnal" count...