SQL MariaDB getting data from 7 tables including mm-tables resulting in too many unwanted rows - sql

I'm struggling with getting data from 7 different sql-tables without receiving too many rows.
I have the following (simple) query which retrieves data from 7 different tables:
SELECT h.name, h.address, h.zipcode, h.city, h.association, r.name_de, f.first_name, f.last_name, f.email, p.year, j.name
FROM `tx_gipdhotels_domain_model_hotel` AS h
JOIN `tx_gipdhotels_hotel_jobs_mm` AS hj ON h.uid = hj.uid_local
JOIN `tx_gipdhotels_domain_model_jobs` AS j ON j.uid = hj.uid_foreign
JOIN `tx_gipdhotels_hotel_participations_mm` AS hp ON h.uid = hp.uid_local
JOIN `tx_gipdhotels_domain_model_participations` AS p ON p.uid = hp.uid_foreign
JOIN `tx_gipdhotels_domain_model_region` AS r ON r.uid = h.region
JOIN `fe_users` AS f ON f.uid = h.feuser
As you can see there are two many-to-many-relationships between the tables. These two tables aren't related (except through the h table). Now the problem is that this results in receiving a row for each possible combination of these mm-tables.
Example:
table 1 hotel
|-----------|------------|----------|----------|
| uid | name | jobs | part |
|...........|............|..........|..........|
| 1 | ab | 3 | 2 |
| | | | |
table 2 jobs
|-----------|------------|
| uid | name |
|...........|............|
| 1 | tech |
| 2 | cs |
| 3 | perf |
| | |
table 3 part
|-----------|------------|
| uid | name |
|...........|............|
| 1 | abcd |
| 2 | efgh |
| | |
With this combination (including mm-tables for hotel_jobs and hotel_part) I would receive 6 rows for one hotel only and in each row only one value would differ from another row:
result:
|-----------|------------|----------|----------|
| uid | name | job | part |
|...........|............|..........|..........|
| 1 | ab | tech | abcd |
| 1 | ab | tech | defg |
| 1 | ab | cs | abcd |
| 1 | ab | cs | defg |
| 1 | ab | perf | abcd |
| 1 | ab | perf | defg |
| | | | |
It would be lovely if I could retrieve this data in one single row like the following:
wanted result:
|-----------|------------|--------------------|----------------|
| uid | name | job | part |
|...........|............|....................|................|
| 1 | ab | tech, cs, perf | abcd, efgh |
| | | | |
I can't figure out how to get the wanted result, it exceeds my experience and knowledge so I'm asking you, do you know how to achieve this with a single query?
I've googled quite a bit and I have found the STUFF() method but it's not supported in MariaDB. In some question here on stack someone has done something similar with a cast but I didn't understand it too well and I didn't know how to adapt this to my problem...
I'm using MariaDB and the query will be made from php. There is no way of changing the data structure of the tables.
Any help and explanations would be greatly appreciated.

I hope this will work, try it, if there is any error, we are gonna fix it.
SELECT
h.name,
h.address,
h.zipcode,
h.city,
h.association,
GROUP_CONCAT(DISTINCT p.year SEPARATOR ', '),
GROUP_CONCAT(DISTINCT j.name SEPARATOR ', '),
r.name_de,
f.first_name,
f.last_name,
f.email,
h.tstamp,
h.crdate
FROM tx_gipleasedisturbhotels_domain_model_hotel AS h
JOIN `tx_gipleasedisturbhotels_hotel_jobs_mm` AS hj
ON h.uid = hj.uid_local
JOIN `tx_gipleasedisturbhotels_domain_model_jobs` AS j
ON j.uid = hj.uid_foreign
JOIN `tx_gipleasedisturbhotels_hotel_participations_mm` AS hp
ON h.uid = hp.uid_local
JOIN `tx_gipleasedisturbhotels_domain_model_participations` AS p
ON p.uid = hp.uid_foreign
JOIN `tx_gipleasedisturbhotels_domain_model_region` AS r
ON r.uid = h.region
JOIN `fe_users` AS f
ON f.uid = h.feuser
GROUP BY h.name
ORDER BY h.name ASC

Thanks to #jarlh I found the solution:
SELECT h.name, h.address, h.zipcode, h.city, h.association,
GROUP_CONCAT(DISTINCT p.year SEPARATOR ', '),
GROUP_CONCAT(DISTINCT j.name SEPARATOR ', '),
r.name_de, f.first_name, f.last_name, f.email, h.tstamp, h.crdate
FROM `tx_gipleasedisturbhotels_domain_model_hotel` AS h
JOIN `tx_gipleasedisturbhotels_hotel_jobs_mm` AS hj ON h.uid = hj.uid_local
JOIN `tx_gipleasedisturbhotels_domain_model_jobs` AS j ON j.uid = hj.uid_foreign
JOIN `tx_gipleasedisturbhotels_hotel_participations_mm` AS hp ON h.uid = hp.uid_local
JOIN `tx_gipleasedisturbhotels_domain_model_participations` AS p ON p.uid = hp.uid_foreign
JOIN `tx_gipleasedisturbhotels_domain_model_region` AS r ON r.uid = h.region
JOIN `fe_users` AS f ON f.uid = h.feuser
GROUP BY h.name
ORDER BY h.name ASC
It's a combination of GROUP_CONCAT and GROUP BY. It has to be grouped by the field which you want to have only once. To get all mm-values to one single cell you'll have to use GROUP_CONCAT on those fields in the SELECT statement.
With this query I receive the wanted result. Maybe this will be helpful to someone else as well. ;)

Related

Query SQL Select Column Matching From Another Table

edit : Sorry gurus, I have to rephrase my question since I forgot there are 3 tables in one query.
I have three tables with tbl_goods ,tbl_units and tbl_sat which looks like this :
tbl_goods, consists of sold goods
+--------+-------+-------+-------+
| goods |code |qty |unit |
+--------+-------+-------+-------+
| cigar | G001 | 1 | pack |
| cigar | G001 | 2 | pcs |
| bread | G002 | 2 | pcs |
| soap | G003 | 1 | pcs |
+--------+-------+-------+-------+
and tbl_units as below :
+--------+-------------+-------+
| code |ucode |qty |
+--------+-------------+-------+
| KG001 | U001 | 10 |
+--------+-------------+-------+
I add letter 'K' in front of code in tbl_units to differ and make sure not collide with code in tbl_goods.
and tbl_sat as below :
+--------+-------------+
| ucode | unit |
+--------+-------------+
| U001 | pack |
+--------+-------------+
| U002 | box |
+--------+-------------+
| U003 | crate | etc
so only cigar will have conversion because table units have the code
what the result I need to show as below :
+--------+-------+-------+-------+--------+
| goods |code |qty |unit | total |
+--------+-------+-------+-------+--------+
| cigar | G001 | 1 | pack | 10 |
| cigar | G001 | 2 | pcs | 2 |
| bread | G002 | 2 | pcs | 2 |
| soap | G003 | 1 | pcs | 1 |
+--------+-------+-------+-------+--------+
so if the code in goods doesn't have match in tbl_units then it will show just as qty in tbl_goods, but if they match then it will convert multiply from tbl_units
Thank you very much..really appreciated
regards
EDIT (might worked ?) :
I try to modify from #danielpr query, and this is the result
think it worked, please help to check it out
SELECT j.code,j.qty ,j.unit, IIF(j.unit=t.unit,j.qty*u.qty,j.fqty) FROM tbl_goods j
LEFT JOIN tbl_units u on u.code ='K' || j.code
LEFT JOIN tbl_sat t ON t.ucode =u.ucode [WHERE j.code='G001']
GROUP BY j.code,j.qty
[WHERE ..] optional if omitted will list all items, but if I just want to check the cigar..just put WHERE CLAUSE
If I understand correct, you are looking for a combination of LEFT JOIN and CASE WHEN or COALESCE.
Here the CASE WHEN option:
SELECT g.goods, g.code, g.qty, g.unit,
CASE WHEN u.conversion IS NULL
THEN g.qty
ELSE g.qty * u.qty
END AS total
FROM
tbl_goods g
LEFT JOIN tbl_units u
ON g.code = u.code
AND g.unit = u.conversion;
As said, COALESCE could also do and is a bit shorter:
SELECT g.goods, g.code, g.qty, g.unit,
g.qty * COALESCE(u.qty,1) AS total
FROM
tbl_goods g
LEFT JOIN tbl_units u
ON g.code = u.code
AND g.unit = u.conversion;
But I think this option has a worse readability compared to CASE WHEN.
Therefore, I would prefer CASE WHEN here.
Try out: db<>fiddle
EDIT because the authour changed the question:
According to the new description, a further table is involved and the table structure is other than described before. So, the COALESCE option is not possible at all in this case.
We require the CASE WHEN way here:
SELECT g.goods, g.code, g.qty, g.unit,
CASE WHEN u.qty IS NULL OR u.ucode IS NULL OR t.unit IS NULL
THEN g.qty
ELSE g.qty * u.qty
END AS total
FROM
tbl_goods g
LEFT JOIN tbl_units u ON u.code = CONCAT('K', g.code)
LEFT JOIN tbl_sat t ON u.ucode = t.ucode AND g.unit = t.unit;
New sample fiddle for this new situation: db<>fiddle
SELECT
tbl_goods.goods
, tbl_goods.code
, tbl_goods.qty
, tbl_goods.unit
, IF(tbl_goods.unit=tbl_units.conversion,tbl_goods.qty*tbl_units.qty,tbl_goods.qty) total
FROM tbl_goods
LEFT JOIN tbl_units ON tbl_goods.code=tbl_units.code
on total column, we can match whether the unit in tbl_goods is same with tbl_units, which is pack.
If it is the same, then we multiply the pack qty in tbl_units with the pack in tbl_goods, else, just return the qty of tbl_goods.

Oracle SQL query comparing multiple rows with same identifier

I'm honestly not sure how to title this - so apologies if it is unclear.
I have two tables I need to compare. One table contains tree names and nodes that belong to that tree. Each Tree_name/Tree_node combo will have its own line. For example:
Table: treenode
| TREE_NAME | TREE_NODE |
|-----------|-----------|
| 1 | A |
| 1 | B |
| 1 | C |
| 1 | D |
| 1 | E |
| 2 | A |
| 2 | B |
| 2 | D |
| 3 | C |
| 3 | D |
| 3 | E |
| 3 | F |
I have another table that contains names of queries and what tree_nodes they use. Example:
Table: queryrecord
| QUERY | TREE_NODE |
|---------|-----------|
| Alpha | A |
| Alpha | B |
| Alpha | D |
| BRAVO | A |
| BRAVO | B |
| BRAVO | D |
| CHARLIE | A |
| CHARLIE | B |
| CHARLIE | F |
I need to create an SQL where I input the QUERY name, and it returns any ‘TREE_NAME’ that includes all the nodes associated with the query. So if I input ‘ALPHA’, it would return TREE_NAME 1 & 2. If I ask it for CHARLIE, it would return nothing.
I only have read access, and don’t believe I can create temp tables, so I’m not sure if this is possible. Any advice would be amazing. Thank you!
You can use group by and having as follows:
Select t.tree_name
From tree_node t
join query_record q
on t.tree_node = q.tree_node
WHERE q.query = 'ALPHA'
Group by t.tree_name
Having count(distinct t.tree_node)
= (Select count(distinct q.tree_node) query_record q WHERE q.query = 'ALPHA');
Using an IN condition (a semi-join, which saves time over a join):
with prep (tree_node) as (select tree_node from queryrecord where query = :q)
select tree_name
from treenode
where tree_node in (select tree_node from prep)
group by tree_name
having count(*) = (select count(*) from prep)
;
:q in the prep subquery (in the with clause) is the bind variable to which you will assign the various QUERY values at runtime.
EDIT
I don't generally set up the test case on online engines; but in a comment below this answer, the OP said the query didn't work for him. So, I set up the example on SQLFiddle, here:
http://sqlfiddle.com/#!4/b575e/2
A couple of notes: for some reason, SQLFiddle thinks table names should be at most eight characters, so I had to change the second table name to queryrec (instead of queryrecord). I changed the name in the query, too, of course. And, second, I don't know how I can give bind values on SQLFiddle; I hard-coded the name 'Alpha'. (Note also that in the OP's sample data, this query value is not capitalized, while the other two are; of course, text values in SQL are case sensitive, so one should pay attention when testing.)
You can do this with a join and aggregation. The trick is to count the number of nodes in query_record before joining:
select qr.query, t.tree_name
from (select qr.*,
count(*) over (partition by query) as num_tree_node
from query_record qr
) qr join
tree_node t
on t.tree_node = qr.tree_node
where qr.query = 'ALPHA'
group by qr.query, t.tree_name, qr.num_tree_node
having count(*) = qr.num_tree_node;
Here is a db<>fiddle.

SQL code to find if a series of lists do NOT contain a particular value

I have two tables
Jobs
+-----+------+
| Job | Name |
+-----+------+
| 1 | Foo |
| 2 | Bar |
| 3 | Baz |
| 4 | Qwe |
+-----+------+
Job_Operations
+-----+--------------+
| Job | Work_Center |
+-----+--------------+
| 1 | SomeCenter |
| 1 | Full Kit |
| 2 | SomeCenter |
| 3 | SomeCenter |
| 3 | Full Kit |
+-----+--------------+
The tables are linked on the Job column. How can I find the entries in Jobs without a corresponding 'Full Kit' entry in Job_Operations?
Desired Results
+-----+------+
| Job | Name |
+-----+------+
| 2 | Bar |
| 4 | Qwe |
+-----+------+
This seems like a straight forward NOT EXISTS query
SELECT J.*
FROM Jobs J
WHERE NOT EXISTS(SELECT *
FROM Job_Operations JO
WHERE JO.Job = J.Job
AND JO.Work_Center = 'Full Kit')
Select * from
(
select Jobs.* , job_Operations.Work_Center as wc
from Jobs
left join Job_Operations on Jobs.Job=Job_Operations.Job and Job_Operations.Work_Center='Full Kit'
) as sub1 where wc is null
In the subselect left join tells the SQL server to give me a row for every row in the Jobs table, even if it does not find a corresponding value in the job_Operations. From job_Operations only rows that contain your 'Full Kit' are regarded for the join. If the join fails, SQLsefer just returns a null for the fields in job_Operations. The outer select just fetches those rows.
Another way is to use Exists, see how that works in the other answer. But if you want to learn SQL try to get an understanding of how left, right inner and outer/full join work.
Simple solution in code below.
Also keep in mind that "working" doesn't meant "high performance".
Check SQL-plan on your specific DB.
select j.*
from job j
where j.job not in (select jo.job
from Job_Operations jo
where jo.Work_Center = 'Full Kit');

SQL Query manipulation

I have three tables :
BookingNode , Booking AirTrip
AirTrip :
+----+------------+
| ID | Name |
+----+------------+
| 0 | One way |
| 1 | Round trip |
| 2 | Circle |
| 3 | Other |
+----+------------+
When ever we make a booking we store the data as :
BookingNode table
+--------+-------------------+------------+----------------------+
| ID | CustomerGivenName | IPAddress | Email |
+--------+-------------------+------------+----------------------+
| 177022 | xfghfh | 2130706473 | mikehussey#gmail.com |
| 177021 | cfggjfj | 2130706473 | mikehussey#gmail.com |
+--------+-------------------+------------+----------------------+
Booking Table :
+--------+---------------+-----------+------------+------------+
| ID | BookingNodeID | AirTripID | AirLineId | Provider |
+--------+---------------+-----------+------------+------------+
| 181251 | 177020 | 1 | 978 | Jet |
| 181252 | 177021 | 0 | 982 | Go |
| 181253 | 177021 | 0 | 978 | Jet |
+--------+---------------+-----------+------------+------------+
If round trip flight is booked and ProviderID is same then a single entry is done in Booking Table with AirTripID value as 1.(Booking ID : 181251 and Provider Jet )
But if providers are different for both the legs then two entries are done in Booking Table with AirTripID for both entries are one(Booking ID : 181252 and 181253 Provider Go,Jet ).In this case BookingNodeID value being same.
Prob : I have to write a query to get different type of Bookings.(Oneway, RoundTrip,Circle).But when I apply join on AirTripID , it is giving me incorrect results.
How can I write my query to give correct results knowing that BookingNodeID is going to be the same for roundtrip (both entries in Booking Table)
Sample Output
+-------------+---------------+-------------------+------------+
| AirTripName | BookingNodeID | CustomerGivenName | IPAddress |
+-------------+---------------+-------------------+------------+
| TwoWay | 177020 | xfghfh | 2130706473 |
| TwoWay | 177021 | cfggjfj | 2130706473 |
+-------------+---------------+-------------------+------------+
Basically, this code might have an error due to my laziness syntom of data entry. But, the logic of the query is, if b.AirTripID is 0, add extra condition which group by Booking. if result return more than 1 row, is actually 2 way. so AirTripType will become 1, otherwise, remain the same as b.AirTripID. You may copy below on and try fix if theres any error. i believe the logic should work based on your expected result.
select
bd.ID,
bd.CustomerGivenName,
case b.AirTripID
when 1 then 1
when 2 then 2
when 3 then 3
when 0 then
case select BookingNodeID
from Booking
where Booking.BookingNodeID = bd.ID group by BookingNodeID having Count(BookingNodeID)
when 1 then 1
else 0 end as AirTripType,
bd.IPAddress
from BookingNode bd
inner join (select BookingNodeID ,AirTripID from Booking group by BookingNodeID ,AirTripID) as b on b.BookingNodeID = bd.ID
where id=177021
Try This
WITH CTE
AS
(
SELECT
SeqNo = ROW_NUMBER() OVER(PARTITION BY BN.ID ORDER BY B.ID),
B.BookingNodeID,
BN.CustomerGivenName,
BN.IPAddress,
AirTripId = A.ID,
AirTripNm = A.Name
FROM Booking B
INNER JOIN AirTrip A
ON A.ID = B.AirTripID
LEFT JOIN BookingNode BN
ON B.BookingNodeID = BN.id
)
SELECT
C1.SeqNo,
AirTripName = CASE WHEN C2.SeqNo IS NOT NULL
THEN 'Round trip'
ELSE C1.AirTripNm END,
C1.BookingNodeID,
C1.CustomerGivenName,
C1.IPAddress
FROM CTE C1
LEFT JOIN CTE C2
ON C1.BookingNodeID = C2.BookingNodeID
AND C2.SeqNo = 2
WHERE c1.SeqNo = 1
SQL Fiddle Link Here
Select distinct bk.bookingnodeid,cst.customername,ipaddress,
case when count(airtripid)over(partition by bookingnodeid order by bookingnodeid)=2 then 'RoundTrip' else name end As AirTripName
from booking bk
inner join airlinetrip at
on bk.airtripid=at.id
inner join customer cst
on cst.id=bk.bookingnodeid

SQL Server - Given a Set of Columns, finding missing combinations within the Set

I have the following Table. What I want to get is the missing combinations of Student, Class, Book. I have a query below that does it, but I would like others to provide more efficient queries (ie possibly ones that use group by) to find the missing combos.
SQL FIDDLE HERE - http://sqlfiddle.com/#!6/16e2b/3
StudentBook Table
+---------+---------+--------------+
| Student | Class | Book |
+---------+---------+--------------+
| Albert | Math | AlgebraBook |
| Albert | Math | FractionBook |
| Bridget | Math | AlgebraBook |
| Bridget | Math | FractionBook |
| Charles | Math | AlgebraBook |
| Charles | Math | FractionBook |
| Debbie | English | NovelBook |
| Debbie | English | PoemBook |
| Edward | English | PoemBook |
| Frank | English | PoemBook |
+---------+---------+--------------+
The following Rows in the Set are the missing combinations
Correct Result of My Query Below
+---------+---------+-----------+
| Student | Class | Book |
+---------+---------+-----------+
| Edward | English | NovelBook |
| Frank | English | NovelBook |
+---------+---------+-----------+
And I can use the following Query to get the Missing Combinations, but I want a faster more efficient solutions. Basically I'm looking for other more Effective Techniques, such as possibly using Group By.
WITH CTE_ClassBooks AS
(
SELECT DISTINCT Class, Book FROM StudentBook
),
CTE_StudentClasses AS
(
SELECT DISTINCT Student, Class FROM StudentBook
),
CTE_CombosOfStudentClassBooks AS
(
SELECT DISTINCT b.Student, a.Class, a.Book
FROM CTE_ClassBooks a
INNER JOIN CTE_StudentClasses b ON a.Class = B.Class
)
SELECT * FROM CTE_CombosOfStudentClassBooks
EXCEPT
SELECT * FROM StudentBook
This might be a little faster, your route doesn't seem terribly inefficient though.
;WITH cte AS (SELECT DISTINCT Class,Book FROM Table1)
SELECT b.Student,a.*
FROM cte a
JOIN Table1 b
ON a.Class = b.Class
LEFT JOIN Table1 c
ON a.Class = c.CLass
AND a.Book = c.Book
AND b.Student = c.Student
WHERE c.Class IS NULL
Demo: SQL Fiddle
SELECT S1.STUDENT,S1.CLASS,S2.BOOK FROM
STUDENTBOOK S1,(SELECT DISTINCT CLASS,BOOK FROM STUDENTBOOK) S2
WHERE S1.CLASS = S2.CLASS
AND S1.BOOK <> S2.BOOK
EXCEPT
SELECT STUDENT,CLASS,BOOK FROM STUDENTBOOK