Self Join MYSQL Parent Child - mysql

For some reason this is eluding me tonight. I have 3 tables, appointments, clients, tasks. I am trying to get all the appointments for today or tomorrow, check the users table to pull the current clients, and any family members, and then pull tasks that meet another criteria to get a full list of tasks for the day or past due.
On the client table we are importing information from another datasource maintaining the original id as reference_id, if they don't have a parent_id, they are the parent, otherwise they are children.
appointment table
id | user_id | client_id | start_at
1 1 2 2018-02-15
2 1 1 2018-02-15
3 1 2 2018-02-15
4 2 4 2018-02-15
clients table:
id | reference_id | parent_id | user_id
1 35 null 1
2 36 35 1
3 37 35 1
4 35 null 2
5 36 35 2
tasks table
id | client_id | task | due_date
1 2 do something 2018-02-15
2 4 do something 2018-01-10
3 2 do something 2018-02-01
4 2 do something 2018-05-15
I got started trying to join the table onto itself, but when I try combining the parent it ends up killing all results, but only when I add WHERE parent_id > 0 to the query. Before that I am getting about 40 results, but should be well over 100. In the dataset, I have 50 appointments, some of the users have 2-5 children / relationship but I am not sure why since I am doing LEFT JOIN instead of inner join, if anything I should start ending up with redundant extra results.
SELECT
start_at,
clients.id,
clients.reference_id,
clients.parent_id,
clients.first_name,
clients.last_name,
children.user_id,
children.first_name,
children.last_name,
children.reference_id,
children.parent_id,
parents.last_name
FROM
`reminderdental2`.`appointments` AS appointment
INNER JOIN clients AS clients
ON appointment.patient_id = clients.id
INNER JOIN clients AS children
ON clients.parent_id = children.parent_id
LEFT JOIN
(SELECT
reference_id,
user_id,
last_name,
first_name,
parent_id
FROM
clients WHERE parent_id > 0) AS parents
ON clients.parent_id = parents.reference_id
WHERE clients.user_id = '27'
AND children.user_id = '27'
AND parents.user_id = '27'
AND start_at > NOW() - INTERVAL 1 DAY
AND start_at < NOW() + INTERVAL 1 DAY
Is there a better way to design this than joining the client table 3 times? Why did it kill the query adding the WHERE parent_id > 0?

Related

configure query to bring rows which have more than 1 entries

How to get those entries which have more than 1 records?
If it doesn't make sense... let me explain:
From the below table I want to access the sum of the commission of all rows where type is joining and "they have more than 1 entry with same downmem_id".
I have this query but it doesn't consider more entries scenario...
$search = "SELECT sum(commission) as income FROM `$database`.`$memcom` where type='joining'";
Here's the table:
id mem_id commission downmem_id type time
2 1 3250 2 joining 2019-09-22 13:24:40
3 45 500 2 egbvegr new time
4 32 20 2 vnsjkdv other time
5 23 2222 2 vfdvfvf some other time
6 43 42 3 joining time
7 32 353 5 joining time
8 54 35 5 vsdvsdd time
Here's the expected result: it should be the sum of the id no 2, 7 only
ie. 3250+353=whatever.
It shouldn't include id no 6 because it has only 1 row with the same downmem_id.
Please help me to make this query.
Another approach is two levels of aggregation:
select sum(t.commission) income
from (select sum(case when type = 'joining' then commission end) as commission
from t
group by downmem_id
having count(*) > 1
) t;
The main advantage to this approach is that this more readily supports more complex conditions on the other members of each group -- such as at most one "joining" record or both "joining" records and no more than two "vnsjkdv" records.
Use EXISTS:
select sum(t.commission) income
from tablename t
where t.type = 'joining'
and exists (
select 1 from tablename
where id <> t.id and downmem_id = t.downmem_id
)
See the demo.
Results:
| income |
| ----- |
| 3603 |
You can use subquery that will find all downmem_id having more than one occurrence in the table.
SELECT Sum(commission) AS income
FROM tablename
WHERE type = 'joining'
AND downmem_id IN (SELECT downmem_id
FROM tablename t
GROUP BY downmem_id
HAVING Count(id) > 1);
DEMO

How can I fix my table to make a expiration of medicine?

I'm doing an inventory system of drug store. And now I'm trying to do the expiration of medicine. I want to show you my table. Maybe you have an idea of how to fix my tables. I put the expiration date in tbl_received_order. When I received a medicine it will insert into tbl_medicine. What if the customer bought 50 pieces of paracetamol or supplier_med_id is equal to 3. And after two months the medicine received will be expired. I use DATE(NOW()) > tbl_received_order.expiration_date to get all the expired medicine.
My problem is that the qty is not tally to rec_qty when the medicine expired. Just for below example, I have 100 pcs. on my initial inventory. But later on, 50 pcs. was sold. Why is it 100 pcs.is still reflecting on my inventory of medicines for expiration, instead of 50pcs. How can I fix my table? Can somebody help me with my problem?
The is my table
tbl_medicine
med_id supplier_med_id qty status branch_id
1 3 50 Active 1
2 7 100 Active 1
3 9 100 Active 1
tbl_received_order
received_id user_id purchase_order_id date_received
RO20190001 1 PO20190001 2019-05-04
tbl_received_order_details
id received_id supplier_med_id rec_qty expiration_date
1 RO20190001 3 100 2019-11-04
2 RO20190001 7 100 2019-11-04
3 RO20190001 9 100 2019-11-04
1 RO20190002 3 50 2019-11-04
2 RO20190002 7 50 2019-11-04
3 RO20190002 9 50 2019-11-04
tbl_transaction
transaction_id customer_id user_id transaction_date branch_id
0001 CU2018001 1 2019-09-04 1
0002 CU2018001 1 2019-09-04 1
0003 CU2018001 1 2019-09-04 1
tbl_transaction_details
details_id transaction_id supplier_med_id qty price total_price
1 0001 3 50 10.00 500
2 0002 3 10 10.00 100
3 0003 3 10 10.00 100
You can get the accurate quantity by using a JOIN.
SELECT orderd.expiration_date, orderd.rec_qty - SUM(trans.qty) AS inventory_left
FROM tbl_received_order_details AS orderd
JOIN tbl_transaction_details AS trans
ON orderd.supplier_med_id = trans.supplier_med_id
WHERE orderd.supplier_med_id = 3
AND orderd.expiration_date < '2019-11-05'
Join tbl_received_order_details and tbl_transaction_details using supplier_med_id.
Filter the records with medicine id '3' and an expiration date.
Since there can be more than 1 transaction for a particular medicine, we need to SUM the quantity sold and subtract it from total quantity to get what's left.
Update 1
Above query will get expired medicines for the ones sold. To get quantity left for all inventories sold, use GROUP BY.
SELECT orderd.expiration_date, orderd.rec_qty - SUM(trans.qty) AS inventory_left
FROM tbl_received_order_details AS orderd
JOIN tbl_transaction_details AS trans
ON orderd.supplier_med_id = trans.supplier_med_id
WHERE orderd.expiration_date < '2019-11-05'
GROUP BY orderd.supplier_med_id
UPDATE 2
To get all left inventories that are expired whether they are sold or not, use LEFT JOIN. COALESCE(trans.qty, 0) will return first NOT NULL value. So if a medicine is never sold, it will not have any entry in transaction table which will return qty as NULL. In such cases, we will subtract zero from rec_qty which in turn will get complete rec_qty as left overs.
SELECT orderd.expiration_date, orderd.rec_qty - SUM(COALESCE(trans.qty, 0)) AS inventory_left
FROM tbl_received_order_details AS orderd
LEFT JOIN tbl_transaction_details AS trans
ON orderd.supplier_med_id = trans.supplier_med_id
WHERE orderd.expiration_date < '2019-11-05'
GROUP BY orderd.supplier_med_id

MySQL query to get data between and not between where clause

This MySQL query is to get data from two tables exam_dates and section_strength and join them to get total persons from which sections has completed there exam in march quarter:
select s.section, count(section), r.strength
from exam_dates s
left join section_strength r
on s.section=r.section_name
where s.semester_exam between '2017-01-01' and '2017-03-31'
group by s.section
But this query is showing only those sections name who have completed the exam in march quarter. I want all the name of sections in left side and number of persons completed next column and then the strength of that section.
first table contains=> details of student completed exam on "so and so dates"
example:
id| personal_no| section_name| semester_dates
1 | 777878 | hrm |2017-01-12
2 | 748587 |it |2017-05-10
another table having strength of individual sections:
id|section_name |strength
1 | hrm | 10
2 | it | 15
Now I want my query to show result of all sections name in left side and then total student completed test in march quarter and then total strength of that section.
for eg.
id|section_name|total_completed|strength
1 | hrm | 2 | 10
2 | it | 5 | 15
Your second date is before your first date. Try reversing the order of the dates:
select s.section, count(section), r.strength
from exam_dates s
left join section_strength r on s.section=r.section_name
where s.semester_exam between '2017-03-31' and '2017-01-01'
group by s.section

Super complicated mysql query

The database (Mysql) witch i do query comes from an telephony system, and i need to read how many agents (event_parties.agent_id) is logged into different group (event_groups.group_id).
Each time an agent logges in to an group an new record is entered inside events table with event_id=29, if logout event_id=30 at the same time an new entry in table event_parties appears with same g_event_id and the agent_id representing the agent,
also in table events_groups an new entry appears with same g_event_id and group_id representing th egroup that the agents logges in/out to(inside the table event_groups the same g_event_id could the same for more than one entry if agent logges in/out more than one group at the same time).
So my thinking is that i could get the logged in agents in and group_id by selecting all records where there are no newer entry (event_time) with same event_groups.group_id and same event_parties.agent_id and the events.event_id is between 29 and 30.
events.event_id =29 means that agents logges in.
events.event_id =30 means that agents logges out.
I have some serious difficulties to design such an mysql select :(
Here are some example data in each table.
Table:
events
g_event_id event_id event_time
---------- -------- ----------
7816 31 2016-11-03 09:46:18
7815 30 2016-11-03 09:45:18
7814 31 2016-11-03 09:44:18
7813 29 2016-11-03 09:43:18
7812 30 2016-11-03 09:42:18
7811 29 2016-11-03 09:41:18
7810 31 2016-11-03 09:40:18
7809 29 2016-11-03 09:39:18
7808 31 2016-11-03 09:38:18
7807 7 2016-11-03 09:37:18
7806 29 2016-11-03 09:36:18
7805 30 2016-11-03 09:35:18
7804 30 2016-11-03 09:34:18
7803 29 2016-11-03 09:33:18
7802 29 2016-11-03 09:32:18
Table:
event_parties
g_event_id agent_id
---------- --------
7816 1
7815 1
7814 1
7813 1
7812 1
7811 1
7810 2
7809 2
7808 2
7807 3
7806 3
7805 3
7804 3
7803 3
7802 3
Table:
event_groups
g_event_id group_id
---------- --------
7816 1
7815 1
7814 1
7813 1
7813 2
7813 3
7813 4
7812 1
7811 1
7810 1
7809 1
7808 1
7807 1
7806 1
7806 3
7805 4
7804 1
7804 2
7803 4
7802 1
7802 2
From tables above i want my select statement result to be:
group_id agent_id
-------- --------
4 1
3 1
2 1
1 2
1 3
3 3
Is such a query possible, is there any sql genius out there :)
/ Kristian
SELECT group_id, agent_id
FROM (SELECT agent_id, eg.group_id, if(event_id = 29, 1, -1) AS transitions
FROM event_parties ep
JOIN `events` e ON ep.g_event_id = e.g_event_id
JOIN event_groups eg ON ep.g_event_id = eg.g_event_id
WHERE e.event_id IN (29, 30)) AS t
GROUP BY agent_id, group_id
HAVING sum(transitions) > 0
ORDER BY agent_id, group_id DESC
Link to SQL Fiddle
I think that this will do what you are saying. For every agent/group combination, it sets number of transitions to 1 if they login and -1 if they log out. Looking over the whole data set, if they have logged in and then logged out, the sum will be 0 for a specific agent group, which is calculated in the outer query.
This does depend on not starting with a log out event for a specific agent/group combination. If the data set you are looking starts with a log out event, then the user will never appear to be logged out.
Alternatively, you could get the same result by looking at the last record, and determining if it's a 29 or a 30, and only displaying the ones that are 29.
SELECT group_id, agent_id
FROM (SELECT agent_id, group_id, max(e.g_event_id) AS last_event_id
FROM event_parties ep
JOIN `events` e ON ep.g_event_id = e.g_event_id
JOIN event_groups eg ON ep.g_event_id = eg.g_event_id
WHERE e.event_id IN (29, 30)
GROUP BY agent_id, group_id) AS last_event
JOIN `events` e ON e.g_event_id = last_event.last_event_id
WHERE e.event_id = 29;
This is less dependent on where you are starting in the series, but the join is slightly more complex.
Link to SQL Fiddle
FWIW syntax style change using natural join:
select group_id, agent_id
from ( select agent_id, group_id,
max( g_event_id ) as g_event_id
from event_parties natural join `events` natural join event_groups
where event_id in (29, 30)
group
by agent_id, group_id ) as last_event
natural join `events`
where event_id = 29;
I just make a Demo now clear me what you want
select g.group_id,p.agent_id from event_groups g
join event_parties p on g.g_event_id=p.g_event_id
join events e on p.g_event_id =e.g_event_id where e.event_id=29

select particular items ordered n times

i have two tables, one invoice and the other the details where i need to select products ordered n times by a particular customer within a date range
the tables in part looks like this
Invoice
invid | custid | invdate
----------------------------
101 | 11 | 2014-2-10
102 | 22 | 2014-2-15
103 | 22 | 2014-3-01
104 | 11 | 2014-3-14
Details
invid | item
------------
101 | bread
102 | bread
103 | chips
104 | chips
102 | bread
103 | bread
104 | chips
101 | bread
from the code above, i need to select say all customers who ordered the same items 2 times or more within 2014-2-10 and 2014-3-09, excluding any customer who purchased the same item in the week 2014-3-10 to 2014-3-14
for example
if customer orders bread 2 times between date1 and date2 and did not order the same bread between date3 and date4 then it should be in the output
and date the expected output should be
custid | item | item_count
22 | bread | 2
the custid 11 would have NOT fit the list, because they also purchased in the week 2014-3-10 to 2014-3-14, but it they did not purchased the same item in the passed dates
this is what i tried
SELECT
i.custid, d.ITEM,COUNT(d.ITEM) as orders
From `details` d
LEFT JOIN `invoices` i on i.invid= d.invid
WHERE
i.invdate >= '2014-2-10' AND
i.invdate <= '2014-3-14' AND
i.custid NOT IN
(SELECT custid FROM `invoices` WHERE invdate >= '2014-3-10')
Group By i.invid, d.ITEM
HAVING COUNT(d.ITEM) >= 2
when i run again the full table, i get 1 item instead of 6. I did manually using excel through a number of functions to be sure, in this case none
Typical MySQL error. You mistakenly group by invid instead of custid.
SELECT
i.custid, d.ITEM, COUNT(d.ITEM) as orders
From `details` d
LEFT JOIN `invoices` i on i.invid= d.invid
WHERE
i.invdate >= '2014-2-10' AND
i.invdate <= '2014-3-14' AND
i.custid NOT IN
(SELECT custid FROM `invoices` WHERE invdate >= '2014-3-10')
Group By i.custid, d.ITEM
HAVING COUNT(d.ITEM) >= 2;
EDIT: Okay, here is a closer look at it.
Correct the GROUP BY as already mentioned.
You outer join invoices although there should be no details record without an invoices record. Change this to INNER JOIN.
You are confusing dates. The purchase date shall be between '2014-2-10' and '2014-3-09' and must not be between '2014-3-10' to '2014-3-14'
Then: You don't want to exclude customers who bought something in that latter week. You want to exclude customer-item combinations that occured then.
My suggestion: select from both date ranges and check then if all macthes for a customer-item combination are within the desired week and still have a count of at least two:
select
i.custid,
d.item,
count(d.item) as orders
from invoices i
inner join details d on d.invid = i.invid
where i.invdate between '2014-2-10' and '2014-3-09'
or i.invdate between '2014-3-10' and '2014-3-14'
group by i.custid, d.item
having count(*) >= 2 and max(i.invdate) between '2014-2-10' and '2014-3-09;
SELECT i1.custid, d1.ITEM, COUNT(*) orders
FROM (invoices i1 JOIN details d1 USING (invid))
LEFT JOIN (invoices i2 JOIN details d2 USING (invid))
ON i2.custid = i1.custid
AND d2.ITEM = d1.ITEM
AND i2.invdate BETWEEN '2014-03-10' AND '2014-03-14'
WHERE i1.invdate BETWEEN '2014-02-10' AND '2014-03-09'
AND i2.custid IS NULL
GROUP BY i1.custid, d1.ITEM
HAVING orders >= 2
See it on sqlfiddle.