Set Count Values from one table to Another - mysql

I am trying to count matching values from customer column on table 'Customers' and update their values on the Count Column in table 'Summary'. I also want to Check if the Date is <= Todays Date.
Table "Customers":
ID
Customer
Date
1
John
2022-01-01
2
John
2022-01-01
3
Mary
2022-01-01
4
Mary
2022-01-01
.......+2000 More Customers
Table "Summary":
ID
Customer
Count
DateInput
1
John
2
2021-01-01
2
Mary
2
2021-01-01
.........+100 More Customers
I can update one row at a time like this:
update Summary
set Count = (SELECT COUNT(*)
FROM Customers
WHERE Customer = "John" AND Date <=CURRENT_DATE())
WHERE Customer = "John";
Is there a way to use the above query to update the count column for John, mary, etc, etc without doing Multiple individual requests?

Is this something you are looking for?
UPDATE
Summary s
INNER JOIN Customers c ON s.Customer = c.Customer
SET
s.Count = (
SELECT
COUNT(*)
FROM
Customers c2
WHERE
c2.Customer = s.Customer
AND c2.Date <= CURRENT_DATE()
)
If you are going to test the query, please test it on a small dataset before applying it to the entire table since it may not achieve the results you are expecting.

Given that your count values will change, you should consider creating a view instead of updating a table:
CREATE VIEW summary AS
SELECT ID, Customer, COALESCE(COUNT(CASE WHEN Date <= CURRENT_DATE() THEN 1 END), 0) AS cnt
FROM Customers
GROUP BY ID, Customer
If you really want to have a table and update it every time, you need such UPDATE statement:
WITH cte AS (
SELECT ID, Customer, COUNT(*) AS count
FROM Customers
WHERE Date <= CURRENT_DATE()
GROUP BY ID, Customer
)
UPDATE Summary
INNER JOIN cte ON Summary.ID = cte.ID AND Summary.Customer = cte.Customer
SET Summary.count = cte.count

You can do it as follows :
UPDATE Summary s
INNER JOIN (
SELECT Customer, count(1) as _count
FROM Customers
where Date <=CURRENT_DATE()
group by Customer
) as c on s.Customer = c.Customer
set s.Count = c._count ;
I have used inner join to join a list of customers and their counts.
and the relation is Customer.

Related

Finding missing data in a sequence in MySQL

Is there an efficient way to find missing data not just in one sequence, but many sequences?
This is probably unavoidably O(N**2), so efficient here is defined as relatively few queries using MySQL
Let's say I have a table of temporary employees and their starting and ending months.
employees | start_month | end_month
------------------------------------
Jane 2017-05 2017-07
Bob 2017-10 2017-12
And there is a related table of monthly payments to those employees
employee | paid_month
---------------------
Jane 2017-05
Jane 2017-07
Bob 2017-11
Bob 2017-12
Now, it's clear that we're missing a month for Jane (2017-06) and one for Bob too (2017-10).
Is there a way to somehow find the gaps in their payment record, without lots of trips back and forth?
In the case where there's just one sequence to check, some people generate a temporary table of valid values, and then LEFT JOIN to find the gaps. But here we have different sequences for each employee.
One possibility is that we could do an aggregate query to find the COUNT() of paid_months for each employee, and then check it versus the expected delta of months. Unfortunately the data here is a bit dirty so we actually have payment dates that could be before or after that employee start or end date. But we're verifying that the official sequence definitely has payments.
Form a Cartesian product of employees and months, then left join the actual data to that, then the missing data is revealed when there is no matched payment to the Cartesian product.
You need a list of every months. This might come from a "calendar table" you already have, OR, it MIGHT be possible using a subquery if every month is represented in the source data)
e.g.
select
m.paid_month, e.employee
from (select distinct paid_month from payments) m
cross join (select employee from employees) e
left join payments p on m.paid_month = p.paid_month and e.employee = p.employee
where p.employee is null
The subquery m can be substituted by the calendar table or some other technique for generating a series of months. e.g.
select
DATE_FORMAT(m1, '%Y-%m')
from (
select
'2017-01-01'+ INTERVAL m MONTH as m1
from (
select #rownum:=#rownum+1 as m
from (select 1 union select 2 union select 3 union select 4) t1
cross join (select 1 union select 2 union select 3 union select 4) t2
## cross join (select 1 union select 2 union select 3 union select 4) t3
## cross join (select 1 union select 2 union select 3 union select 4) t4
cross join(select #rownum:=-1) t0
) d1
) d2
where m1 < '2018-01-01'
order by m1
The subquery e could contain other logic (e.g. to determine which employees are still currently employed, or that are "temporary employees")
First we need to get all the months between start date and end_date in a temporary table then need do a left outer join with the payments table on paid month filtering all non matching months ( payment employee name is null )
select e.employee, e.yearmonth as missing_paid_month from (
with t as (
select e.employee, to_date(e.start_date, 'YYYY-MM') as start_date, to_date(e.end_date, 'YYYY-MM') as end_date from employees e
)
select distinct t.employee,
to_char(add_months(trunc(start_date,'MM'),level - 1),'YYYY-MM') yearmonth
from t
connect by trunc(end_date,'mm') >= add_months(trunc(start_date,'mm'),level - 1)
order by t.employee, yearmonth
) e
left outer join payments p
on p.paid_month = e.yearmonth
where p.employee is null
output
EMPLOYEE MISSING_PAID_MONTH
Bob 2017-10
Jane 2017-06
SQL Fiddle http://sqlfiddle.com/#!4/2b2857/35

Customer partitioning in sql query

I have a table with following format -
Customer_id Purchase_date
c1 2015-01-11
c2 2015-02-12
c3 2015-11-12
c1 2016-01-01
c2 2016-12-29
c4 2016-11-28
c4 2015-03-15
... ...
The table essentially contains customer_id with their purchase_date. The customer_id is repetitive based on the purchase made on purchase_date. The above is just a sample data and the table contains about 100,000 records.
Is there a way to partition the customer based on pre-defined category data
Category Partitioning
- Category-1: Customer who has not made purchase in last 10 weeks, but made a purchase before that
- Category-2: Customer who as not made a purchase in last 5 weeks, but made purchase before that
- Category-3: Customer who has made one or more purchase in last 4 weeks or it has been 8 weeks since the first purchase
- Category-4: Customer who has made only one purchase in the last 1 week
- Category-5: Customer who has made only one purchase
What I'm looking for is a query that tells customer and their category -
Customer_id Category
C1 Category-1
... ...
The query can adhere to - oracle, postgres, sqlserver
From your question it seems that a customer can fall in multiple categories. So lets find out the customers in each category and then take UNION of the results.
SELECT DISTINCT Customer_Id, 'CATEGORY-1' AS Category FROM mytable GROUP BY
Customer_Id HAVING DATEDIFF(ww,MAX(Purchase_date),GETDATE()) > 10
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-2' AS Category FROM mytable GROUP BY
Customer_Id HAVING DATEDIFF(ww,MAX(Purchase_date),GETDATE()) > 5
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-3' AS Category FROM mytable GROUP BY
Customer_Id HAVING DATEDIFF(ww,MAX(Purchase_date),GETDATE()) < 4 OR
DATEDIFF(ww,MIN(Purchase_date),GETDATE()) =8
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-4' AS Category FROM mytable WHERE
DATEDIFF(ww,Purchase_date,GETDATE())<=1 GROUP BY Customer_Id having
COUNT(*) =1
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-5' AS Category FROM mytable GROUP BY
Customer_Id HAVING COUNT(*) =1
ORDER BY Category
Hope this serves your purpose.
Thanks
you can use something like this
with myTab as (
SELECT Customer_id ,MIN(Purchase_date) AS Min_Purchase_date,MAX(Purchase_date) AS Max_Purchase_date
, SUM(CASE WHEN Purchase_date>= DATEADD(WEEk ,-1,GETDATE()) THEN 1 ELSE 0 END ) AS Count_LastWeek
, COUNT(*) AS Count_All
FROM Purchases_Table
GROUP BY Customer_id
)
SELECT Customer_id
, CASE WHEN Max_Purchase_date < DATEADD(WEEK,-10,GETDATE()) THEN 'Category-1'
WHEN Max_Purchase_date < DATEADD(WEEK,-5,GETDATE()) THEN 'Category-2'
WHEN Max_Purchase_date >= DATEADD(WEEK,-4,GETDATE())
OR DATEDIFF(WEEK, Min_Purchase_date,Max_Purchase_date) >= 8 THEN 'Category-3'
WHEN Count_LastWeek = 1 THEN 'Category-4'
WHEN Count_All = 1 THEN 'Category-5'
ELSE 'No Category'
END
FROM myTab

How to Obtain First and Last record ? One Step Solution?

I have the following data table.
Record Date Price
A 3/1/2015 5
A 3/2/2015 6
A 3/3/2015 7
A 3/4/2015 10
B 2/1/2015 4
B 2/2/2015 6
B 2/3/2015 15
B 2/4/2015 2
How can I output a table that only shows the First price and the last price for each record for the first date in the table and the last date in the table. Output columns would be Record, First Price, Last Price. I am looking for a one step solution that is easy to implement in order to create a custom view.
The output desired would be:
Record FirstPrice LastPrice
A 5 10
B 4 2
Perhaps something like this is what you are looking for?
select R.Record, FD.Price as MinPrice, LD.Price as MaxPrice
from Records R
join (
select Price, R1.Record
from Records R1
where Date = (select MIN(DATE) from Records R2 where R2.Record = R1.Record)
) FD on FD.Record = R.Record
join (
select Price, R1.Record
from Records R1
where Date = (select MAX(DATE) from Records R2 where R2.Record = R1.Record)
) LD on LD.Record = R.Record
group by R.Record
http://sqlfiddle.com/#!9/d047b/26
Get the min and max aggregate dates grouped by the record field and join back to the root data. If you can have multiple records for the same record field on the same date, you will have to use min, max or avg to get just one value for that date.
SQLFiddle: http://sqlfiddle.com/#!9/1158b/3
SELECT anchorData.Record
, firstRecord.Price
, lastRecord.Price
FROM (
SELECT Record
, MIN(Date) AS FirstDate
, MAX(Date) AS LastDate
FROM Table1
GROUP BY Record
) AS anchorData
JOIN Table1 AS firstRecord
ON firstRecord.Record = anchorData.Record
AND firstRecord.Date = anchorData.FirstDate
JOIN Table1 AS lastRecord
ON lastRecord.Record = anchorData.Record
AND lastRecord.Date = anchorData.LastDate
"in order to create a custom view."...are you looking to do this in Oracle/MySql as a CREATE VIEW or just a query/select statement?

How to get rows with max date when grouping in MySQL?

I have a table with prices and dates on product:
id
product
price
date
I create a new record when price change. And I have a table like this:
id product price date
1 1 10 2014-01-01
2 1 20 2014-02-17
3 1 5 2014-03-28
4 2 25 2014-01-05
5 2 12 2014-02-08
6 2 30 2014-03-12
I want to get last price for all products. But when I group with "product", I can't get a price from a row with maximum date.
I can use MAX(), MIN() or COUNT() function in request, but I need a result based on other value.
I want something like this in final:
product price date
1 5 2014-03-28
2 30 2014-03-12
But I don't know how. May be like this:
SELECT product, {price with max date}, {max date}
FROM table
GROUP BY product
Alternatively, you can have subquery to get the latest get for every product and join the result on the table itself to get the other columns.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT product, MAX(date) mxdate
FROM tableName
GROUP BY product
) b ON a.product = b.product
AND a.date = b.mxdate
I think the easiest way is a substring_index()/group_concat() trick:
SELECT product,
substring_index(group_concat(price order by date desc), ',', 1) as PriceOnMaxDate
max(date)
FROM table
GROUP BY product;
Another way, that might be more efficient than a group by is:
select p.*
from table t
where not exists (select 1
from table t2
where t2.product = t.product and
t2.date > t.date
);
This says: "Get me all rows from the table where the same product does not have a larger date." That is a fancy way of saying "get me the row with the maximum date for each product."
Note that there is a subtle difference: the second form will return all rows that on the maximum date, if there are duplicates.
Also, for performance an index on table(product, date) is recommended.
You can use a subquery that groups by product and return the maximum date for every product, and join this subquery back to the products table:
SELECT
p.product,
p.price,
p.date
FROM
products p INNER JOIN (
SELECT
product,
MAX(date) AS max_date
FROM
products
GROUP BY
product) m
ON p.product = m.product AND p.date = m.max_date
SELECT
product,
price,
date
FROM
(SELECT
product,
price,
date
FROM table_name ORDER BY date DESC) AS t1
GROUP BY product;

SQL query that reports N or more consecutive absents from attendance table

I have a table that looks like this:
studentID | subjectID | attendanceStatus | classDate | classTime | lecturerID |
12345678 1234 1 2012-06-05 15:30:00
87654321
12345678 1234 0 2012-06-08 02:30:00
I want a query that reports if a student has been absent for 3 or more consecutive classes. based on studentID and a specific subject between 2 specific dates as well. Each class can have a different time. The schema for that table is:
PK(`studentID`, `classDate`, `classTime`, `subjectID, `lecturerID`)
Attendance Status: 1 = Present, 0 = Absent
Edit: Worded question so that it is more accurate and really describes what was my intention.
I wasn't able to create an SQL query for this. So instead, I tried a PHP solution:
Select all rows from table, ordered by student, subject and date
Create a running counter for absents, initialized to 0
Iterate over each record:
If student and/or subject is different from previous row
Reset the counter to 0 (present) or 1 (absent)
Else, that is when student and subject are same
Set the counter to 0 (present) or plus 1 (absent)
I then realized that this logic can easily be implemented using MySQL variables, so:
SET #studentID = 0;
SET #subjectID = 0;
SET #absentRun = 0;
SELECT *,
CASE
WHEN (#studentID = studentID) AND (#subjectID = subjectID) THEN #absentRun := IF(attendanceStatus = 1, 0, #absentRun + 1)
WHEN (#studentID := studentID) AND (#subjectID := subjectID) THEN #absentRun := IF(attendanceStatus = 1, 0, 1)
END AS absentRun
FROM table4
ORDER BY studentID, subjectID, classDate
You can probably nest this query inside another query that selects records where absentRun >= 3.
SQL Fiddle
This query works for intended result:
SELECT DISTINCT first_day.studentID
FROM student_visits first_day
LEFT JOIN student_visits second_day
ON first_day.studentID = second_day.studentID
AND DATE(second_day.classDate) - INTERVAL 1 DAY = date(first_day.classDate)
LEFT JOIN student_visits third_day
ON first_day.studentID = third_day.studentID
AND DATE(third_day.classDate) - INTERVAL 2 DAY = date(first_day.classDate)
WHERE first_day.attendanceStatus = 0 AND second_day.attendanceStatus = 0 AND third_day.attendanceStatus = 0
It's joining table 'student_visits' (let's name your original table so) to itself step by step on consecutive 3 dates for each student and finally checks the absence on these days. Distinct makes sure that result willn't contain duplicate results for more than 3 consecutive days of absence.
This query doesn't consider absence on specific subject - just consectuive absence for each student for 3 or more days. To consider subject simply add .subjectID in each ON clause:
ON first_day.subjectID = second_day.subjectID
P.S.: not sure that it's the fastest way (at least it's not the only).
Unfortunately, mysql does not support windows functions. This would be much easier with row_number() or better yet cumulative sums (as supported in Oracle).
I will describe the solution. Imagine that you have two additional columns in your table:
ClassSeqNum -- a sequence starting at 1 and incrementing by 1 for each class date.
AbsentSeqNum -- a sequence starting a 1 each time a student misses a class and then increments by 1 on each subsequent absence.
The key observation is that the difference between these two values is constant for consecutive absences. Because you are using mysql, you might consider adding these columns to the table. They are big challenging to add in the query, which is why this answer is so long.
Given the key observation, the answer to your question is provided by the following query:
select studentid, subjectid, absenceid, count(*) as cnt
from (select a.*, (ClassSeqNum - AbsentSeqNum) as absenceid
from Attendance a
) a
group by studentid, subjectid, absenceid
having count(*) > 2
(Okay, this gives every sequence of absences for a student for each subject, but I think you can figure out how to whittle this down just to a list of students.)
How do you assign the sequence numbers? In mysql, you need to do a self join. So, the following adds the ClassSeqNum:
select a.StudentId, a.SubjectId, count(*) as ClassSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= s1.classDate
group by a.StudentId, a.SubjectId
And the following adds the absence sequence number:
select a.StudentId, a.SubjectId, count(*) as AbsenceSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= a1.classDate
where AttendanceStatus = 0
group by a.StudentId, a.SubjectId
So the final query looks like:
with cs as (
select a.StudentId, a.SubjectId, count(*) as ClassSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= s1.classDate
group by a.StudentId, a.SubjectId
),
a as (
select a.StudentId, a.SubjectId, count(*) as AbsenceSeqNum
from Attendance a join
Attendance a1
on a.studentid = a1.studentid and a.SubjectId = a1.Subjectid and
a.ClassDate >= s1.classDate
where AttendanceStatus = 0
group by a.StudentId, a.SubjectId
)
select studentid, subjectid, absenceid, count(*) as cnt
from (select cs.studentid, cs.subjectid,
(cs.ClassSeqNum - a.AbsentSeqNum) as absenceid
from cs join
a
on cs.studentid = a.studentid and cs.subjectid = as.subjectid
) a
group by studentid, subjectid, absenceid
having count(*) > 2