Simplify my mysql query - php

I currently have the following tables with:
TABLE klusbonnen_deelnemers:
bonnummer (varchar) - order number
adres (varchar) - order adres
deelnemer (varchar) - user
binnen (date) - date order received
klaar (date) - original order milestone
datum_gereed (date) - date order completed
gereed (varchar) - YES or NO (YES= completed NO= Not yet completed)
datum_factuur (date) - date when user marked order completed (button clicked)
factuur (varchar) - weeknumber order completed
One order(bonnummer) can have multiple users (deelnemer) who all have to mark the order "completed" (datum_gereed). Only when ALL users (deelnemer) have marked an order (bonnummer) "completed" (datum_gereed) the order IS "completed".
I am trying to write a query that gives me:
All completed orders (bonnummer) in a given timespan (last month).
However...
The completion date (datum_gereed) should hold the LAST date (as that is the actual total completion date).
The list should have the Order (bonnummer) with the latest "marked completed" date (datum_factuur) on top (sort DESC) (of course only when all users (deelnemer) have completed the order (all users(deelnemers) having gereed="YES")
So far i have this:
SELECT DISTINCT tbl1.bonnummer AS 'KLUSBONNUMMER', tbl1.adres AS 'ADRES',
tbl1.binnen AS 'BINNENGEKOMEN OP', tbl1.klaar AS 'ORIGINELE STREEFDATUM',
tbl1.datum_gereed AS 'GEREEDGEKOMEN OP', tbl1.factuur AS 'WEEKNUMMER'
FROM klusbonnen_deelnemers AS tbl1
INNER JOIN
( SELECT tbl2.bonnummer
FROM klusbonnen_deelnemers AS tbl2
WHERE tbl2.bonnummer NOT IN (
SELECT tbl3.bonnummer
FROM klusbonnen_deelnemers AS tbl3
WHERE tbl3.gereed = 'NEE')
) AS tbl4 ON tbl1.bonnummer = tbl4.bonnummer
INNER JOIN
( SELECT bonnummer, MAX(datum_gereed) AS 'MAXDATUM'
FROM klusbonnen_deelnemers
GROUP BY bonnummer
) MAXFILTER ON tbl1.bonnummer = MAXFILTER.bonnummer
AND tbl1.datum_gereed = MAXFILTER.MAXDATUM
WHERE tbl1.datum_factuur BETWEEN NOW() - INTERVAL 2 MONTH AND NOW()
ORDER BY tbl1.bonnummer DESC
This query DOES work, however i think this can be done in a much simpler way.
On top of that the query only works in my navicat editor. Calling this query on my "live" website gives an error (subquery in WHERE clause...) (i do have all login correct as other queries DO work).
Anyone out there who can help (simplify) this query? Thx...

this part:
INNER JOIN (SELECT tbl2.bonnummer
FROM klusbonnen_deelnemers AS tbl2
WHERE tbl2.bonnummer NOT IN
(SELECT tbl3.bonnummer
FROM klusbonnen_deelnemers AS tbl3
WHERE tbl3.gereed = 'NEE')) AS tbl4
ON tbl1.bonnummer = tbl4.bonnummer
seems like useless. try to use gereed <> 'NEE' in the "very-bottom"-WHERE
SELECT DISTINCT
kd.bonnummer AS 'KLUSBONNUMMER',
kd.adres AS 'ADRES',
kd.binnen AS 'BINNENGEKOMEN OP',
kd.klaar AS 'ORIGINELE STREEFDATUM',
kd.datum_gereed AS 'GEREEDGEKOMEN OP',
kd.factuur AS 'WEEKNUMMER'
FROM klusbonnen_deelnemers AS kd
INNER JOIN (
SELECT bonnummer, MAX(datum_gereed) AS 'MAXDATUM'
FROM klusbonnen_deelnemers
GROUP BY bonnummer
) AS MAXFILTER
ON (kd.bonnummer = MAXFILTER.bonnummer AND kd.datum_gereed = MAXFILTER.MAXDATUM)
WHERE
kd.gereed <> 'NEE'
kd.datum_factuur BETWEEN NOW() - INTERVAL 2 MONTH AND NOW()
ORDER BY
kd.bonnummer DESC

Related

Join a 3rd table when the result of the initial join has NULL values (that I want retained!)

I have 3 tables:
event_timestamps with colums Race_number, timestamp
event_entry with Race_number, User_id
user with user_id, Firstname, lastname
I want to find race_numbers that are not linked to a user_id, and count laps while I'm at it by Joining event_timestamps and event_entry.
select event_entry.user_id, max(timestamp), event_timestamps.race_number, count(event_timestamps.race_number)
from event_timestamps
left join event_entry on event_timestamps.race_number = event_entry.race_number and event_entry.event_id=430
where timestamp > '2022-05-28 11:50:00' and timestamp < '2022-05-29'
group by event_timestamps.race_number
order by count(event_timestamps.race_number) desc , max(timestamp);
Output
user_id
max(timestamp)
race_number
count(event...)
NULL
2022-05-28 12:30:01
1000
5
14694
2022-05-28 12:30:02
32
5
37617
2022-05-28 12:30:17
44
5
16134
2022-05-28 12:34:37
24
5
But when I join tbl.user the Null value disappears. I want to display the NULL so I can see if we are missing user data.
This query sort of works but the NULL value is not displaying:
select user.firstname, user.lastname, max(timestamp), event_timestamps.race_number, count(event_timestamps.race_number)
from event_timestamps
left join event_entry on event_timestamps.race_number = event_entry.race_number
inner join user on user.user_id = event_entry.user_id
where timestamp > '2022-05-28 11:50:00' and timestamp < '2022-05-29' and event_entry.event_id=430
group by event_timestamps.race_number
order by count(event_timestamps.race_number) desc , max(timestamp);```
firstname
lastname
max(timestamp)
race_number
count(event....)
Albert
Coles
12:30:02
32
5
Vince
Butre
12:30:17
44
5
John
Plessis
12:34:37
24
5
So I want race_number 1000 (for example) to display as well with NULL values in firstname, lastname.
Any assistance would be much appreciated because this is breaking my novice brain!
The events_timestamps has multiple occurances of the same race_number as the user completes laps. We count the laps and creat rank by using the last lap time (MAX timestamp) and sorting from there.
Try it like this:
SELECT user.firstname, user.lastname, s.timestamp, s.race_number, s.user_id, s.count
FROM (
SELECT event_entry.user_id as user_id, max(timestamp) as timestamp, event_timestamps.race_number as race_number, count(event_timestamps.race_number) as count
from event_timestamps
Left join event_entry on event_timestamps.race_number = event_entry.race_number and event_entry.event_id=430
Where timestamp > '2022-05-28 11:50:00' and timestamp < '2022-05-29'
group by event_timestamps.race_number, event_entry.user_id
) s
LEFT JOIN user on user.user_id = s.user_id
order by s.count desc , s.timestamp
like #Marshal_c said, inner join doesn't work, cause it gets rid of NULLs, viz. SQL JOIN and different types of JOINs
also like #Honk_der_Hase points out, your code is missing group by for all columns. When your SQL works, i asume, that you work in MariaDB, which takes in this case first found element (equivalent of ORALCE's TOP())
In your scenario, you should also have some kind of integrity rule, which will prevent from having multiple users (user_id) with same race number (race_number). If you had thouse records in your database, the rows would start duplicating.
Also be aware, that some databases (like MariaDB) can have problems with table called user because table named like that is used for authentication into database (in information_scheme)

Sum columns on different tables and multiply by value of a column on another table

I need to compute employees' monthly salaries based on meetings attended, deductions and bonuses given;
Employees have different pay per meeting based on their job position.
The solution is:
salary = (Pay_per_minute * meetings_attended) + bonuses - deductions ;
I have four tables:
Jobs: Id, title, pay_per_meeting
Employees: Id, Name, job_id
Bonuses: Id, amount, employee_id, date
Deductions: Id, amount, employee_id, date
Meetings: Id, employee_id, date
SELECT
COUNT(meetings.employee_id) as meetings_attended,
COUNT(deductions.amount) as debt,
COUNT(bonuses.amount) bonus,
(SELECT jobs.pay_per_attendance from jobs where jobs.id = (select job_id from employees where id=meetings.employee_id)) as pay,
((meetings_attended * pay) + bonus - debt) as salary
FROM meetings
JOIN deductions ON deductions.employee_id = meetings.employee_id
JOIN bonuses ON bonuses.employee_id = meetings.employee_id
WHERE meetings.employee_id = 1
GROUP BY MONTH(meetings.date), MONTH(deductions.date), MONTH(bonuses.date)
The above query returns many incorrect values whenever i remove the salary line but gives error of unknown column pay, meetings_attended, debt and bonus, am sure something is wrong with the grouping but i can't just see it.
You can't refer to column aliases in the same select list as they're defined, you need to refer to the underlying column. And a subquery can't access an aggregate calculated in the main query. You need to repeat the aggregate expression, or move everything into a subquery and do the calculation with it in an outer query.
Also, all your COUNT() expressions are going to return the same thing, since they're just counting rows (I assume none of the values can be NULL). You probably want COUNT(DISTINCT <column>) to get different counts, and you need to use a column that's unique, so they should be the primary key column, e.g. COUNT(DISTINCT deductions.id).
Another problem is that when you try to sum and count values when you have multiple joins, you end up with a result that's too high, because rows get duplicated in the cross product of all the tables. See Join tables with SUM issue in MYSQL. The solution is to calculate the sums from each table in subqueries.
SELECT m.month, m.meetings_attended, d.debt, b.bonus,
m.meetings_attended * j.pay_per_meeting + b.amount - d.amount AS salary
FROM (
SELECT MONTH(date) AS month, COUNT(*) AS meetings_attended
FROM meetings
WHERE employee_id = 1
GROUP BY month) AS m
JOIN (
SELECT MONTH(date) AS month, COUNT(*) AS bonus, SUM(amount) AS amount
FROM bonuses
WHERE employee_id = 1
GROUP BY month) AS b ON m.month = b.month
JOIN (
SELECT MONTH(date) AS month, COUNT(*) AS debt, SUM(amount) AS amount
FROM deductions
WHERE employee_id = 1
GROUP BY month) AS d ON m.month = d.month
CROSS JOIN employees AS e
JOIN jobs AS j ON j.id = e.job_id
WHERE e.employee_id = 1

Get data if insert are in the same second

I have this table :
id idm date_play
1 5 2017-08-23 12:12:12
2 5 2017-08-23 12:12:12
3 6 2017-08-23 12:14:13
I want to identify if user has more then one insert in the same second. In the case describe I want to get the user id that is 5.
I tried like this :
SELECT `idm`, MAX(`s`) `conseq` FROM
(
SELECT
#s := IF(#u = `idm` AND (UNIX_TIMESTAMP(`date_play`) - #pt) BETWEEN 1 AND 100000, #s + 1, 0) s,
#u := `idm` `idm`,
#pt := UNIX_TIMESTAMP(`date_play`) pt
FROM table
WHERE date_play >= '2017-08-23 00:00:00'
AND date_play <= '2017-08-23 23:59:59'
ORDER BY `date_play`
) AS t
GROUP BY `idm`
Can you help me please ? Thx in advance and sorry for my english.
Assuming your dates are accurate down to the second level, you can do this with a single aggregation:
select idm
from t
group by idm
having count(*) > count(distinct date_play);
If date_play has fractional seconds, then you would need to remove those (say by converting to a string).
If you want the play dates where there are duplicates:
select idm, date_play
from t
group by idm, date_play
having count(*) >= 2;
Or, for just the idms, you could use select distinct with group by:
select distinct idm
from t
group by idm, date_play
having count(*) >= 2;
(I only mention this because this is the only type of problem that I know of where using select distinct with group by makes sense.)
If you want all the rows that are duplicated, I would go for exists instead:
select t.*
from t
where exists (select 1
from t t2
where t2.idm = t.idm and t2.date_play = t.date_play and
t2.id <> t.id
);
This should have reasonable performance with an index on (idm, date_play, id).
If your table is called mytable, the following should work:
SELECT t.`idm`
FROM mytable t INNER JOIN mytable t2
ON t.`idm`=t2.`idm` AND t.`date_play`=t2.`date_play` AND t.`id`!=t2.`id`
GROUP BY t.`idm`
Basically we join the table with itself, pairing records that have the same idm and date_play, but not the same id. This will have the effect of matching up any two records with the same user and datetime. We then group results by user so you don't get the same user id listed multiple times.
Edit:
Gordon Linoff and tadman's suggestions led me to this probably much more efficient query (credit to them)
SELECT t.`idm`
FROM mytable t
GROUP BY t.`date_play`
HAVING COUNT(t.`id`)>1

combine two select queries from the same mysql table

We have a table of users with column membership_end which is, as the name say - the date when users membership end. Now some users never purchase membership so that field is empty. For some users, that field is in the past (membership expired).
What I need is to sort the active members first (ordered by the membership_end date desc), then the rest of the users in some independent sorting order. Basically we need to show active members on top.
SELECT * FROM accounts where membership_end>=now()
order by membership_end desc
union
???
Should we use union or something else?
What would be a second query to avoid duplicates?
Thanks in advance
Ok, to clear things up a bit.
SELECT * FROM accounts where membership_end>=now()
order by membership_end desc
gives us 12 active members and this is fine. we then have this:
SELECT * FROM accounts where membership_end<now()
order by id desc
we simply need to combine these two in one query. that's all.
EDIT:
Actually i figured it out. It's quite simple.
SELECT id, membership_end
FROM accounts
ORDER BY membership_end >= NOW( ) DESC , id DESC
no unions. no complicated queries :-)
You can sort null values to be first, so you could avoid unions :
SELECT membership_end FROM accounts
where membership_end>=now() or membership_end is null
ORDER BY [membership_end] IS NULL DESC, [membership_end] DESC
If you do not want duplicates I would recommend to do this. I don't know what condition you want to use to filter members with null membership_end filed but if all I would do something like that:
SELECT membership_end FROM accounts where membership_end>=now() or membership_end is null order by membership_end desc
Descending ordering should put members with null membership_end in the end of list so you may want to add additional ordering.
Try something like this:
SELECT membership_end FROM table1 where membership_end>=now()
Union all
SELECT membership_end FROM table2 where membership_end>=now()
this will combine table1 and table2
The important bit is 1 field for table1 and 1 field for table2 only. It must be the same.
Two tricks here 1) associate ids with dates by creating a combined date + id field, historic and nulls have been given a seed date in the future to avoid overlaps with future dated members (you may wish to push this out) 2) allocate a srce number to the unioned statements.
/*
create table m (id int, enddte date);
truncate table m;
insert into m values
(1,null),(2,'2016-10-01'),(3,'2016-11-10'),(4,'2016-02-01'),(5,'2016-03-01'),(6,null);
*/
select * from
(
select 1 as srce,m.id,m.enddte ,
year(m.enddte) * 10000 + month(m.enddte) * 100 + m.id as uniqueid
from m
where m.enddte >= now()
) s
union
(
select 2,id,enddte,
year(cast('2050-01-01' as date)) * 10000 + month(cast('2050-01-01' as date)) * 100 + id
from m
where m.enddte < now() or m.enddte is null
)
order by srce,uniqueid desc
or you could do this without a union
select * from
(
select case
when enddte >= now() then 1
else 2
end as srce,
id, enddte,
case
when enddte >= now() then year(m.enddte) * 10000 + month(m.enddte) * 100 + m.id
else year(cast('2050-01-01' as date)) * 10000 + month(cast('2050-01-01' as date)) * 100 + id
end as uniqueid
from m
) s order by srce,uniqueid desc

MySQL Need to show 0 when no value in right hand table of join, with cumulative

I have one table with a list of number of sales per month against product code and another with a list of months that can extend before or after the months that had a sale in. I need to results to show 0 sales if there were no sales in the month and for the cumulative to add this up. I have tried using case and if and getting it to put 0 if sales.sales was null but this did not work and I still just had blanks.
create table summary as (SELECT
q1.productid As productid,
q1.date AS Month_View,
q1.sales AS Monthly_Units_Sold,
(#runtot_sales := #runtot_sales + q1.sales) AS Cumulative_Sales
FROM
(SELECT
sales.productid,
dates.date,
if(sales.date is null,0,sales.sales) as sales
from
dates
left join sales on dates.date = sales.date
where
sales.productid = '$input1'
group by dates.date
ORDER BY date) AS q1);
";
Try COALESCE() function to return the first non-NULL value of a list Also see demo here
CREATE TABLE summary AS
(SELECT
q1.productid AS productid,
q1.date AS Month_View,
q1.sales AS Monthly_Units_Sold,
(
#runtot_sales := #runtot_sales + q1.sales
) AS Cumulative_Sales
FROM
(SELECT
sales.productid,
dates.date,
COALESCE(sales.sales, 0) AS sales
FROM
dates
LEFT JOIN sales
ON dates.date = sales.date
WHERE sales.productid = '$input1'
GROUP BY dates.date
ORDER BY DATE) AS q1) ;
MySQL COALESCE() function
You are misusing GROUP BY and therefore getting indeterminate results. See this: http://dev.mysql.com/doc/refman/5.5/en/group-by-extensions.html
If you're aggregating your items by product and date you probably want something like this.
SELECT sales.productid,
dates.date,
SUM(sales.sales) as sales
FROM dates
LEFT JOIN sales ON dates.date = sales.date
WHERE sales.productid = '$input1'
GROUP BY sales.productid, dates.date
ORDER BY /* i'm not sure what you're trying to do with the running total */
Note that SUM(sales.sales) handles the NULL values from your LEFT JOIN correctly. If the date doesn't join a sales row then sales.sales will be NULL.
If you're trying to do a month-by-month summary you need more logic than you have. See this writeup: http://www.plumislandmedia.net/mysql/sql-reporting-time-intervals/

Categories