How to get all entries with SQL query with join - mysql

kon
id | name
1 alex
2 peter
3 john
ticket
id | amount | kon_id | package
122 13 1 234
123 12 1 234
124 20 2 NULL
125 23 2 235
126 19 1 236
I would like to get a list of all contacts with the sum of the amount, except tickets, where the package entry is NULL.
My problem is, that I only get the contacts which have a ticket, because of the WHERE clause.
SELECT
kon.id,
kon.name,
SUM(ticket.amount)
FROM kon LEFT JOIN ticket ON kon.id = ticket.kon_id
WHERE ticket.package IS NOT NULL
GROUP BY kon.id
At the moment, the output looks like this
1 alex 44
2 peter 23
but it should look like this
1 alex 44
3 john NULL
2 peter 23
I use a MySQL Server.
Is it possible to solve this?

Replace Where with AND
SELECT
kon.id,
kon.name,
SUM(ticket.amount)
FROM kon LEFT JOIN ticket ON kon.id = ticket.kon_id AND ticket.package IS NOT NULL
GROUP BY kon.id

Check This.
SELECT
k.id,
k.name ,
coalesce (SUM(t.amount) ,0)
FROM kon k LEFT JOIN
( select id,amount,kon_id,package from ticket where package is not null ) t
ON k.id = t.kon_id
GROUP BY k.id, k.name
OutPut :

Begin Tran
Create Table #Kon (id INt , name Nvarchar(255))
Insert into #Kon
Select 1,'alex' UNION ALL
Select 2,'peter' UNION ALL
Select 3,'john'
Create Table #Ticket (id int,amount int,Kon_Id Int,Package Int)
INSERT INTO #Ticket
SELECT 122,13,1,234 UNION ALL
SELECT 123,12,1,234 UNION ALL
SELECT 124,20,2,NULL UNION ALL
SELECT 125,23,2,235 UNION ALL
SELECT 126,19,1,236
SELECT K.id, Name,SUM(amount) amount
FROM #Kon k
LEFT JOIN #Ticket T ON K.id=T.Kon_Id
GROUP BY K.id,Name
RollBAck Tran

Generally, "ticket.package IS NOT NULL" is wrong condition: your query becomes inner join from left join. If ticket.package should be NOT NULL to add from amount, it should be not in condition, but inside SUM agregate function.
working example for MS SQL
SELECT
kon.id,
min(kon.name),
SUM(case when package is NULL then 0 else ticket.amount end)
FROM #kon kon LEFT JOIN #ticket ticket ON kon.id = ticket.kon_id
GROUP BY kon.id
Answer from Mr. Bhosale is right too, but for big tables will have worse performance (the reason is subquery)

the following query return your expected result
SELECT
kon.id,
kon.name,
SUM(ticket.amount) as 'amount'
FROM kon LEFT JOIN ticket ON kon.id = ticket.kon_id
GROUP BY kon.id, kon.name
attached image shows the result

I figured out the fastest way to solve the problem. It takes about 0.2s compared to the other solutions (2s - 2min). The CAST is important, otherwise the summation of double variables is wrong (float string problem).
SELECT
kon1,
kon2,
SUM(CAST(kon3 AS DECIMAL(7,2)))
FROM (
SELECT k.id kon1, k.name kon2, t.amount kon3 FROM kon as k
LEFT JOIN ticket t ON k.id = t.ticket_kon
WHERE t.package IS NOT NULL
UNION ALL
SELECT k.id kon1, k.name kon2, NULL kon3 FROM kon k WHERE) t1
GROUP BY kon1, kon2

Related

How to SELECT rows with MIN(DateTime column), GROUP by another column and DISTINCT by another column in SQL?

My table is:
id
student_id
exam_date
license
result
1
101
01-11-2020
B2
FAILED
2
102
15-11-2020
A
PASSED
3
103
22-11-2020
D
FAILED
4
101
01-10-2020
D
PASSED
5
104
01-12-2020
A
PASSED
6
103
29-11-2020
D
PASSED
7
101
01-12-2020
B2
PASSED
8
105
01-09-2020
B2
FAILED
9
104
01-11-2020
A
FAILED
10
105
01-11-2020
B2
PASSED
I need to select the results that would have the first result according to the exam date according to each student id and the license column. If the same student takes different license exam, these two results need to come up as well. But I need only one result row for each student id and license value.
The result should look like this:
id
student_id
exam_date
license
result
1
101
01-11-2020
B2
FAILED
2
102
15-11-2020
A
PASSED
3
103
22-11-2020
D
FAILED
4
101
01-10-2020
D
PASSED
8
105
01-09-2020
B2
FAILED
9
104
01-11-2020
A
FAILED
I've done the research and queries and so far I only got 1 row for student_id although the student takes two different license examination.
The following is my query:
SELECT scct_outer.id, scct_outer.stud_id, scct_outer.exam_date, scct_outer.license, scct_outer.result
FROM stud_cdl_comp_test AS scct_outer
INNER JOIN
(SELECT stud_id, MIN(exam_date) AS MinExamDate
FROM stud_cdl_comp_test AS scct
INNER JOIN stud AS s ON scct.stud_id = s.id
INNER JOIN agent_profile AS ap ON s.agent_profile_id = ap.id
GROUP BY stud_id) groupedscct
ON scct_outer.stud_id = groupedscct.stud_id
AND scct_outer.exam_date = groupedscct.MinExamDate
The problem with you original code is that it is missing a correlartion on the licences between the outer query and the subquery. You would phrase it as:
select s.*
from stud_cdl_comp_test s
inner join (
select student_id, licence, min(exam_date) as minexamdate
from stud_cdl_comp_test as scct
group by stud_id, licence
) s1 on s1.student_id = s.student_id and s1.license = s.license and s1.minexamdate = s.date
I have no idea what stud and agent_profile are, so I removed the from the query.
That said, this is not the method I would recommend - a simple and efficient option is to filter with a subquery:
select *
from stud_cdl_comp_test s
where s.exam_date = (
select min(s1.exam_date)
from stud_cdl_comp_test s1
where s1.student_id = s.student_id and s1.license = s.license
)
This can take advantage of an index on (student_id, license, exam_date).
Alternatively, you can use row_number(), available in MySL 8.0:
select *
from (
select s.*,
row_number() over(partition by student_id, licence order by exam_date) rn
from stud_cdl_comp_test s
) s
where rn = 1
Thinking that you are grouping by student_id in this case is almost incorrect in this case. What are actually grouping by is student + license. Let's call this key combination individual_license.
Here's what the solution will look like:
SELECT
st.id,
st.stud_id,
st.exam_date,
st.license,
st.result
FROM stud_cdl_comp_test AS st
INNER JOIN
(SELECT
MIN(exam_date) AS min_date,
st_inner.student_id,
st_inner.license
FROM stud_cdl_comp_test AS st_inner
GROUP BY st_inner.student_id, st_inner.license
) grouped_inner
ON grouped_inner.student_id = st.student_id
AND grouped_inner.license = st.license
AND grouped_inner.min_date = st.exam_date;
This should work.

Getting max date from a joined table with group by

I've got 3 tables - entryrecord, employee and employee_entryrecord (linking table).
The query I'd like is for it to return the most recent (max time) inout record for each employee.
employee
id employee
1 John
2 Tom
entryrecord
id created_date inout
1 2016-07-22 16:01:38 1
2 2016-07-22 16:03:22 1
3 2016-07-22 16:05:22 2
4 2016-07-22 16:07:22 2
5 2016-07-22 16:09:22 1
I'd like the follow output
created_date employee inout entryrecordid
2016-07-22 16:09:22 John 1 5
2016-07-22 16:05:22 Tom 2 3
However, in the sqlfiddle below you can see it does not return the correct inout and entryrecordid values.
I've created a sqlfiddle to view what I've done.
SQL Fiddle
Any help would be great.
Thanks.
Please give it a try:
SELECT
finalALias.created_date,
E.employee,
finalALias.inout,
finalALias.id AS entryrecordid
FROM employee E
INNER JOIN
(
SELECT
*
FROM entryrecord entryR
INNER JOIN
(
SELECT
EER.employeeid,
MAX(created_date) max_time
FROM entryrecord ER
INNER JOIN employee_entryrecord EER ON ER.id = EER.entryrecordid
GROUP BY EER.employeeid
) t
ON t.max_time=entryR.created_date
) AS finalALias
ON E.id = finalALias.employeeid
ORDER BY finalALias.created_date DESC;
WORKING DEMO
Just a gentle reminder:
E -> employee
ER -> entryrecord
ERR -> employee_entryrecord
The problem is that grouping happens before ordering. You will have to do a sub query. You always want to try and keep your sub queries to a minimum as they put a heavy toll on the SQL server.
I changed your LEFT JOINS to INNER JOINS because it looked like you wanted to only get employees that were in the other tables.
SELECT
entryrecord.created_date,
employee.employee,
entryrecord.inout,
entryrecord.id
FROM
entryrecord
INNER JOIN
employee_entryrecord ON entryrecord.id = employee_entryrecord.entryrecordid
INNER JOIN
employee ON employee_entryrecord.employeeid = employee.id
WHERE
entryrecord.inout in (1,2)
AND entryrecord.id = (
SELECT er2.id
FROM employee_entryrecord eer2, entryrecord er2
WHERE eer2.employeeid = employee.id
AND er2.id = eer2.entryrecordid
ORDER BY er2.created_date DESC LIMIT 1
)

I need help regarding JOIN query in mysql

I have started learning MySQL and I'm having a problem with JOIN.
I have two tables: purchase and sales
purchase
--------------
p_id date p_cost p_quantity
---------------------------------------
1 2014-03-21 100 5
2 2014-03-21 20 2
sales
--------------
s_id date s_cost s_quantity
---------------------------------------
1 2014-03-21 90 9
2 2014-03-22 20 2
I want these two tables to be joined where purchase.date=sales.date to get one of the following results:
Option 1:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 1 2014-03-21 90 9
2 2014-03-21 20 2 NULL NULL NULL NULL
NULL NULL NULL NULL 2 2014-03-22 20 2
Option 2:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 NULL NULL NULL NULL
2 2014-03-21 20 2 1 2014-03-21 90 9
NULL NULL NULL NULL 2 2014-03-22 20 2
the main problem lies in the 2nd row of the first result. I don't want the values
2014-03-21, 90, 9 again in row 2... I want NULL instead.
I don't know whether it is possible to do this. It would be kind enough if anyone helps me out.
I tried using left join
SELECT *
FROM sales
LEFT JOIN purchase ON sales.date = purchase.date
output:
s_id date s_cost s_quantity p_id date p_cost p_quantity
1 2014-03-21 90 9 1 2014-03-21 100 5
1 2014-03-21 90 9 2 2014-03-21 20 2
2 2014-03-22 20 2 NULL NULL NULL NULL
but I want 1st 4 values of 2nd row to be NULL
Since there are no common table expressions or full outer joins to work with, the query will have some duplication and instead need to use a left join unioned with a right join;
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p LEFT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
UNION
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p RIGHT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
An SQLfiddle to test with.
In a general sense, what you're looking for is called a FULL OUTER JOIN, which is not directly available in MySQL. Instead you only get LEFT JOIN and RIGHT JOIN, which you can UNION together to get essentially the same result. For a very thorough discussion on this subject, see Full Outer Join in MySQL.
If you need help understanding the different ways to JOIN a table, I recommend A Visual Explanation of SQL Joins.
The way this is different from a regular FULL OUTER JOIN is that you're only including any particular row from either table at most once in the JOIN result. The problem being, if you have one purchase record and two sales records on a particular day, which sales record is the purchase record associated with? What is the relationship you're trying to represent between these two tables?
It doesn't sound like there's any particular relationship between purchase and sales records, except that some of them happened to take place on the same day. In which case, you're using the wrong tool for the job. If all you want to do is display these tables side by side and line the rows up by date, you don't need a JOIN at all. Instead, you should SELECT each table separately and do your formatting with some other tool (or manually).
Here's another way to get the same result, but the EXPLAIN for this is horrendous; and performance with large sets is going to be atrocious.
This is essentially two queries UNIONed together. The first query is essentially "purchase LEFT JOIN sales", the second query is essentially "sales ANTI JOIN purchase".
Because there is no foreign key relationship between the two tables, other than rows matching on date, we have to "invent" a key we can join on; we use user variables to assign ascending integer values to each row within a given date, so we can match row 1 from purchase to row 1 from sales, etc.
I wouldn't normally generate this type of result using SQL; it's not a typical JOIN operation, in the sense of how we traditionally join tables.
But, if I had to produce the specified resultset using MySQL, I would do it like this:
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #pl_i := IF(pl.date = #pl_prev_date,#pl_i+1,1) AS i
, #pl_prev_date := pl.date AS p_date
, pl.p_id
, pl.p_cost
, pl.p_quantity
FROM purchase pl
JOIN ( SELECT #pl_i := 0, #pl_prev_date := NULL ) pld
ORDER BY pl.date, pl.p_id
) p
LEFT
JOIN ( SELECT #sr_i := IF(sr.date = #sr_prev_date,#sr_i+1,1) AS i
, #sr_prev_date := sr.date AS s_date
, sr.s_id
, sr.s_cost
, sr.s_quantity
FROM sales sr
JOIN ( SELECT #sr_i := 0, #sr_prev_date := NULL ) srd
ORDER BY sr.date, sr.s_id
) s
ON s.s_date = p.p_date
AND s.i = p.i
UNION ALL
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #sl_i := IF(sl.date = #sl_prev_date,#sl_i+1,1) AS i
, #sl_prev_date := sl.date AS s_date
, sl.s_id
, sl.s_cost
, sl.s_quantity
FROM sales sl
JOIN ( SELECT #sl_i := 0, #sl_prev_date := NULL ) sld
ORDER BY sl.date, sl.s_id
) s
LEFT
JOIN ( SELECT #pr_i := IF(pr.date = #pr_prev_date,#pr_i+1,1) AS i
, #pr_prev_date := pr.date AS p_date
, pr.p_id
, pr.p_cost
, pr.p_quantity
FROM purchase pr
JOIN ( SELECT #pr_i := 0, #pr_prev_date := NULL ) prd
ORDER BY pr.date, pr.p_id
) p
ON p.p_date = s.s_date
AND p.i = s.i
WHERE p.p_date IS NULL
ORDER BY COALESCE(p_date,s_date),COALESCE(p_id,s_id)

Challenging LEFT OUTER JOIN query grouping by MAX

I have the following two tables:
BillingMatrixDefinition
- id
- amount
BillingMatrix
- definition (FK to table above)
- service_id (FK)
- provider_id (FK)
- amount (Decimal)
I need to get all BillingMatrixDefinitions that have the service_id and provider_id that I specify. Here is the SQL query I currently have:
select def.id, service_id, provider_id,
(case when matrix.amount is not null then matrix.amount else def.amount end) amount
from billing_billingdefinition def
left outer join billing_billingmatrix matrix
on matrix.definition_id=def.id
where (service_id = 25 or service_id is null)
and (provider_id = 24 or provider_id is null)
This gives me the following results:
id service_id provider_id amount
1 25 24 200.00
1 NULL 24 300.00
2 NULL 24 800.00
3 NULL NULL 750.00
5 NULL NULL 450.00
6 NULL NULL 750.00
However, I need to get the billing amount per id, so I can only get ONE item/amount for each id. In which case, I want to get the item where the service_id=24, and if that doesn't exist, then get it where service_id=NULL.
The correct query should give me the following results:
id service_id provider_id amount
1 25 24 200.00
2 NULL 24 800.00
3 NULL NULL 750.00
5 NULL NULL 450.00
6 NULL NULL 750.00
Notice how now there is no duplicate entry for 1, and I use the line item where a service_id has been entered (use that one if it exists, else use NULL). What would be the correct query to do this?
Anothr way:
SELECT
def.id AS id,
COALESCE(matrix.service_id, matrix2.service_id) AS service_id,
COALESCE(matrix.provider_id, matrix2.provider_id) AS provider_id,
COALESCE(matrix.amount, matrix2.amount, def.amount) AS amount
FROM
billing_billingdefinition AS def
LEFT JOIN
billing_billingmatrix AS matrix
ON matrix.definition_id = def.id
AND matrix.service_id = 25
AND matrix.provider_id = 24
LEFT JOIN
billing_billingmatrix AS matrix2
ON matrix2.definition_id = def.id
AND matrix2.service_id IS NULL
AND matrix2.provider_id = 24 ;
Try something along these lines (utilizing a temporary table):
CREATE TEMPORARY TABLE Results
select def.id, service_id, provider_id,
(case when matrix.amount is not null then matrix.amount else def.amount end) amount
from billing_billingdefinition def
left outer join billing_billingmatrix matrix
on matrix.definition_id=def.id
where (service_id = 25 or service_id is null)
and (provider_id = 24 or provider_id is null);
SELECT *
FROM Results r1
WHERE IFNULL(r1.service_id, 0) =
( SELECT MAX(IFNULL(r2.service_id, 0))
FROM Results r2
WHERE r2.id = r1.id
);
SQL Fiddle for the 2nd part only (uses already created Results table)
You need to aggregate the amount using max() (and of course add a group-by clause) so you get the non-null value if one exists:
select
def.id, service_id, provider_id,
max(case when matrix.amount is not null then matrix.amount else def.amount end) amount
from billing_billingdefinition def
left outer join billing_billingmatrix matrix
on matrix.definition_id=def.id
where (service_id = 25 or service_id is null)
and (provider_id = 24 or provider_id is null)
group by def.id, service_id, provider_id
Something like this might work as well.
select def.id, service_id, provider_id, IFNULL(matrix.amount,def.amount) amount
from billing_billingdefinition def
left outer join (select definition_id, max(service_id) as maxsid from billing_billingmatrix matrix group by definition_id) as t1
on def.id = t1.definition_id
left outer join billing_billingmatrix matrix
on matrix.definition_id=def.id and maxsid <=> service_id
I believe PM 771's answer works here as well, but I decided to use a subselect in the OUTER JOIN table, to pre-filter the results before joining.
Here is the final SQL that worked for this:
SELECT *,
(CASE WHEN matrix.amount IS NOT NULL THEN matrix.amount ELSE def.amount END) calculated_amount,
FROM billing_billingdefinition def
LEFT OUTER JOIN
(SELECT t.* FROM (
select * from billing_billingmatrix
where (provider_id=25
or provider_id is null)
and (service_id=24 or service_id is null)
ORDER BY service_id DESC
) t GROUP BY t.definition_id) matrix
ON matrix.definition_id=def.id
Try this:
SELECT BMD.id, BM.service_id, BM.provider_id, IFNULL(BM.amount, BMD.amount) AS amount
FROM BillingMatrixDefinition BMD
LEFT JOIN BillingMatrix BM ON BMD.id = BM.definition_id AND (BM.service_id = 25 OR BM.provider_id = 24)
GROUP BY BMD.id;

Select Count of Rows with Joined Tables

I have two tables with a one to many relationship. I join the tables by an id column. My problem is that I need a count of all matching entries from the second (tablekey_id) table but I need the information from the row marked with the boolean is_basedomain. As a note there is only one row with is_basedomain = 1 per set of rows with the same tablekey_id.
Table: tablekey
id linkdata_id timestamp
22 9495028175 2013-03-10 01:13:46
23 8392740179 2013-03-10 21:23:25
Table: searched_domains.
NOTE: tablekey_id is the foreign key to the id in the tablekey table.
id tablekey_id domain is_basedomain
1 22 somesite.com 1
2 22 yahoo.com 0
3 23 red.com 1
4 23 blue.com 0
5 23 green.com 0
Heres the query Im working with. I was trying to use a sub query but I cant seem to select only the count for the current tablekey_id so this does not work.
SELECT `tablekey_id`, `linkdata_id`, `timestamp`, `domain`, `is_basedomain`,
(SELECT COUNT(1) AS other FROM `searched_domains` AS dd
ON dd.tablekey_id = d.tablekey_id GROUP BY `tablekey_id`) AS count
FROM `tablekey` AS k
JOIN `searched_domains` AS d
ON k.id = d.tablekey_id
WHERE `is_basedomain` = 1 GROUP BY `tablekey_id`
The result that I would like to get back is:
tablekey_id linkdata_id timestamp domain is_basedomain count
22 9495028175 2013-03-10 01:13:46 somesite.com 1 2
23 8392740179 2013-03-10 21:23:25 red.com 1 3
Can anyone help me get this into one query?
You can treat the searched_domains rows that have is_basedomain=1 as a separate table in the query and join it with another instance of searched_domains (to get the count):
SELECT
d.tablekey_id,
k.linkdata_id,
k.timestamp,
d.domain,
d.is_basedomain,
COUNT(*) as 'count'
FROM
tablekey AS k
join searched_domains AS d on d.tablekey_id=k.id
join searched_domains AS d2 on d2.tablekey_id=d.tablekey_id
WHERE
d.is_basedomain = 1
GROUP BY
d.tablekey_id,
k.linkdata_id,
k.timestamp,
d.domain,
d.is_basedomain
you have an error when using ON instead use WHERE
try this
SELECT `tablekey_id`, `linkdata_id`, `timestamp`, `domain`, `is_basedomain`,
(SELECT COUNT(1) AS other FROM `searched_domains` AS dd
where dd.tablekey_id = d.tablekey_id GROUP BY `tablekey_id`) AS count
FROM `tablekey` AS k
JOIN `searched_domains` AS d
ON k.id = d.tablekey_id
WHERE `is_basedomain` = 1 GROUP BY `tablekey_id`
DEMO HERE
There is no reason to use subquery, or what is your opinion?
SELECT
`tablekey_id`,
`linkdata_id`,
`timestamp`,
`domain`,
`is_basedomain`,
COUNT(*) as count
FROM
`tablekey` AS k ,
`searched_domains` AS d
WHERE
k.id = d.tablekey_id AND
`is_basedomain` = 1
GROUP BY
`tablekey_id`,
`linkdata_id`,
`timestamp`,
`domain`,
`is_basedomain`
If you want only latest timestamp use MAX(timestamp) as timestamp and remove it from group by.