I have a set of data that lists when a User changes a Product, we look at who changed it, when, the old cost and new cost, and the percentage price difference.
I want to use a group by, where statement, or case to group by and exclude products that filters out changes were the change occurred in the same day and resulted in the original price staying.
So the situation I want to exclude would look like this:
| product | Changed By | Old Price | New Price | % diff | Day Changed |
|----------|------------|-----------|-----------|--------|-------------|
| blue hat | me | 94.00 | 95.00 | 1.05 | 2016-11-28 |
| blue hat | me | 95.00 | 94.00 | 1.05 | 2016-11-28 |
Any ideas how to do this with MySql?
Here is a working version for anyone who wants to see this done using subqueries, where's, and group by's.
This query looks at the changes to an Item's cost by a User for the span of 1 day, where it pulls in all the results from "yesterday". It lists all the changes for that day one asc and one desc and compares the price changes that way. If they are the same from the oldest change to the newest change of that say then it is exempted.
SELECT
us.Name,
it.Name,
pal.CS_PA_Line_Supplier__c as supplier_name,
newest.NewValue as new_value,
oldest.OldValue as old_value,
((newest.NewValue - oldest.OldValue) / oldest.OldValue) * 100 as Percentage
FROM
(
SELECT Id, Name, KNDY4__Item__c, CS_PA_Line_Supplier__c
FROM KNDY4__Contract_Line__c
) pal
LEFT JOIN
(
SELECT *
FROM
(
SELECT
ParentId,
CreatedById,
CreatedDate,
Field,
NewValue
FROM KNDY4__Contract_Line__History
WHERE CreatedDate >= CURDATE() - INTERVAL 1 DAY
AND CreatedDate < CURDATE()
AND Field='KNDY4__Negotiated_Price__c'
ORDER BY CreatedDate DESC
) cd
GROUP BY ParentId
) newest
ON newest.ParentId=pal.Id
LEFT JOIN
(
SELECT * FROM (
SELECT
ParentId,
OldValue
FROM KNDY4__Contract_Line__History
WHERE CreatedDate >= CURDATE() - INTERVAL 1 DAY
AND CreatedDate < CURDATE()
AND Field='KNDY4__Negotiated_Price__c'
ORDER BY CreatedDate ASC
) cd
GROUP BY ParentId
) oldest
ON oldest.ParentId=pal.Id
LEFT JOIN
(
SELECT Id, Name
FROM User
) us
ON us.Id = newest.CreatedById
LEFT JOIN
(
SELECT Id,Name
FROM KNDY4__Item__c
) it
ON it.Id=pal.KNDY4__Item__c
WHERE newest.ParentId IS NOT NULL
AND oldest.OldValue IS NOT NULL
AND newest.NewValue != oldest.OldValue
GROUP BY pal.KNDY4__Item__c, pal.CS_PA_Line_Supplier__c
ORDER BY it.Name ASC
Related
I would like to count how many new unique users the database gets each day for all days recorded.
There will not be any duplicate ids per day, but there will be duplicates over multiple days.
If my table looks like this :
ID | DATE
---------
1 | 2022-05-21
1 | 2022-05-22
2 | 2022-05-22
1 | 2022-05-23
2 | 2022-05-23
1 | 2022-05-24
2 | 2022-05-24
3 | 2022-05-24
I would like the results to look like this :
DATE | NEW UNIQUE IDs
---------------------------
2022-05-21 | 1
2022-05-22 | 1
2022-05-23 | 0
2022-05-24 | 1
A query such as :
SELECT `date` , COUNT( DISTINCT id)
FROM tbl
GROUP BY DATE( `date` )
Will return the count per day and will not take into account previous days.
Any assistance would be appreciated.
Edit : Using MySQL 8
The user is new when the date is the least date for this user.
So you need in something like
SELECT date, COUNT(new_users.id)
FROM calendar
LEFT JOIN ( SELECT id, MIN(date) date
FROM test
GROUP BY id ) new_users USING (date)
GROUP BY date
calendar is either static or dynamically generated table with needed dates list. It can be even SELECT DISTINCT date FROM test subquery.
Start with a subquery showing the earliest date where each id appears.
SELECT MIN(`date`) `firstdate`, id
FROM tbl
GROUP BY id
Then do your count on that subquery. here.
SELECT firstdate, COUNT(*)
FROM (
SELECT MIN(`date`) `firstdate`, id
FROM tbl
GROUP BY id
) m
GROUP BY firstdate
That gives you what you want.
But it doesn't have rows for the dates where no new user ids first appeared.
Only count (and sum) the rows where the left join fails:
SELECT
m1.`DATE` ,
sum(CASE WHEN m2.id is null THEN 1 ELSE 0 END) as C
FROM mytable m1
LEFT JOIN mytable m2 ON m2.`DATE`<m1.`DATE` AND m2.ID=m1.ID
GROUP BY m1.`DATE`
see: DBFIDDLE
I have a table. It has the following structure
goods_receiving_items
id
item_id
quantity
created_at
I am trying to fetch rows against which have the following conditions
Has one item_id
When the sum of the quantity column equals a certain value
So for example I have the following data
+----+---------+----------+------------+
| id | item_id | quantity | created_at |
+----+---------+----------+------------+
| 1 | 2 | 11 | 2019-10-10 |
| 2 | 3 | 110 | 2019-10-11 |
| 3 | 2 | 20 | 2019-11-09 |
| 4 | 2 | 5 | 2019-11-10 |
| 5 | 2 | 1 | 2019-11-11 |
+----+---------+----------+------------+
I have tried the following query:
SET #sum:= 0;
SELECT item_id, created_at, (#sum:= #sum + quantity) AS SUM, quantity
FROM goods_receiving_items
WHERE item_id = 2 AND #sum<= 6
ORDER BY created_at DESC
If I don't use ORDER BY, then the query will give me ID '1'. But if I use ORDER BY it will return all the rows with item_id = 2.
What should be returned are IDs '5' and '4' exclusively in this order
I can't seem to resolve this and ORDER BY is essential to my task.
Any help would be appreciated
You should use the order by on the resulting set
you could do this using a subquery
SET #sum:= 0;
select t.*
from t (
SELECT item_id
, created_at
, (#sum:= #sum + quantity) as sum
, quantity
FROM goods_receiving_items
WHERE item_id = 2 AND #sum<= 6
) t
ORDER BY created_at DESC
You should try an INNER JOIN with SELECT min(created_at) or SELECT max(created_at)
From MYSQL docs:
...the selection of values from each group cannot be influenced by
adding an ORDER BY clause. Sorting of the result set occurs after
values have been chosen, and ORDER BY does not affect which values the
server chooses.
The answers on the following might help in more detail: MYSQL GROUP BY and ORDER BY not working together as expected
After searching around, I have made up the following query
SELECT
t.id, t.quantity, t.created_at, t.sum
FROM
( SELECT
*,
#bal := #bal + quantity AS sum,
IF(#bal >= $search_number, #doneHere := #doneHere + 1 , #doneHere) AS whereToStop
FROM goods_receiving_items
CROSS JOIN (SELECT #bal := 0.0 , #doneHere := 0) var
WHERE item_id = $item_id
ORDER BY created_at DESC) AS t
WHERE t.whereToStop <= 1
ORDER BY t.created_at ASC
In the above query, $search_number is a variable that holds the value that has to be reached. $item_id is the item we are searching against.
This will return all rows for which the sum of the column quantity makes up the required sum. The sum will be made with rows in descending order by created_at and then will be rearranged in ascending order.
I was using this query to calculate the cost when a certain amount of items are being used in an inventory management system; so this might help someone else do the same. I took most of the query from another question here on StackOverflow
Consider this UNION of two (or more) queries:
SELECT activity AS activity1, completion AS status1, date_end AS date1
FROM activities
WHERE (
matter LIKE '%JR161167'
AND activity LIKE '1.1 %'
AND DATEDIFF( CURDATE( ) , date_end ) <120
)
UNION
SELECT activity AS activity2, completion AS status2, date_end AS date2
FROM activities
WHERE (
matter LIKE '%JR161167'
AND activity LIKE '1.2 %'
AND DATEDIFF( CURDATE( ) , date_end ) <120
)
The activity column will contain a variable string value, but the string will reliably be prepended with 1.1 or 1.2 or 1.3 all the way to 1.9.
The matter column is also a string, but will reliably be appended with an account number that resembles JR161167.
I want to query all records that have date_end from within the past 120 days (that's the DATEFIFF part), matching the specified account number (JR161167), but where the activity field contains any and all substrings "1.n".
Using the UNION returns the right data from a single table, "activities", but there seems to be no way around heading the result set under the columns or aliases of the very first query. Instead, I want something like:
activity1 status1 date1 activity2 status2 date2
--------------------------------------------------------------------------------
1.1 Step 1 begins... Incomplete 2017-4-16 1.2 Step 2 begins... Incomplete 2017-4-30
Instead, my query stacks all results, no matter if they contain 1.1, 1.2, 1.3, etc., under one alias column heading, activity1. Same thing with the date and completion field data, it doesn't bother nicely sorting into the corresponding date1, date2, etc., or status1, status2, etc.
I recently read an example that looks something like this, which looks like the logic I want to implement, and I wonder if anyone can understand its intent and help me formulate the right query:
SELECT date_end
IF( WHERE description LIKE '1.1 %') AS date1,
IF( WHERE description LIKE '1.2 %') AS date2`
FROM activities
SQL Fiddle
Query 1:
SELECT a1.*, a2.*
FROM (
SELECT matter, activity, completion, date_end
FROM activities
WHERE
matter LIKE '%JR16116'
AND activity = '1.1'
AND DATEDIFF(CURDATE(), date_end) <120
) a1
JOIN (
SELECT matter, activity, completion, date_end
FROM activities
WHERE
matter LIKE '%JR16116'
AND activity = '1.2'
AND DATEDIFF(CURDATE(), date_end) <120
) a2
ON a1.matter = a2.matter
Results:
| matter | activity | completion | date_end | matter | activity | completion | date_end |
|---------|----------|------------|------------|---------|----------|------------|------------|
| JR16116 | 1.1 | 1 | 2017-04-22 | JR16116 | 1.2 | 1 | 2017-04-23 |
Query 2:
-- other way without sub request but clean join
SELECT
a.matter, a.activity, a.completion, a.date_end
,a2.activity, a2.completion, a2.date_end
-- ,a3.activity, a3.completion, a3.date_end
FROM activities a
JOIN activities a2
ON (
a.matter = a2.matter
AND a2.activity = '1.2'
AND DATEDIFF(CURDATE(), a2.date_end) <120
)
-- JOIN activities a3
-- ON (
-- a.matter = a3.matter
-- AND a3.activity = '1.3'
-- AND DATEDIFF(CURDATE(), a3.date_end) <120
-- )
WHERE
a.matter LIKE '%JR16116'
AND a.activity = '1.1'
AND DATEDIFF(CURDATE(), a.date_end) <120
Results:
| matter | activity | completion | date_end | activity | completion | date_end |
|---------|----------|------------|------------|----------|------------|------------|
| JR16116 | 1.1 | 1 | 2017-04-22 | 1.2 | 1 | 2017-04-23 |
Table:
----------------------------------------------------
ID | field_name | field_value | timestamp
----------------------------------------------------
2 | postcode | LS1 | 2016-11-09 16:45:15
2 | age | 34 | 2016-11-09 16:45:22
2 | job | Scientist | 2016-11-09 16:45:27
2 | age | 38 | 2016-11-09 16:46:40
7 | postcode | LS5 | 2016-11-09 16:47:05
7 | age | 24 | 2016-11-09 16:47:44
I wonder if anyone could give me a few pointers, based on the above data, I would like to query by ID 2, return a row for each unique field_name (if more than one row exists under the same id with the same field_name then just return the row with the latest timestamp).
I have managed to almost achieve this by grouping the field_name, which will return a list of unique rows but not necessarily the latest row.
SELECT * FROM fragment WHERE (id = :id) GROUP BY field_name
I would really be grateful for any pointers on what exactly I should do here, and how I could fit something along the lines of MAX(timestamp) in this query,
Many thanks!
Consider you first need a set of data for each ID, FieldName with the max time stamp. (generate that set) as an inline view (B below). Then, join this set (B) back to your base set allowing the inner join to eliminate the unwanted rows.
SELECT A.ID, A.field_name, A.field_value, A.timestamp
FROM Table A
INNER JOIN (SELECT ID, field_name, MAX(timestamp) TS
FROM table
GROUP BY ID, field_name) B
on A.ID = B.ID
and A.field_name = B.field_name
and A.timestamp = B.TS
Outside of MySQL this could be done using window/analytical functions as you would be able to assign a row number to each record and eliminate those > 1 something like....
SELECT B.*
FROM (SELECT A.ID
, A.field_name
, A.field_Vale
, A.timestamp
, Rownumber() over (Order by A.timestamp Desc) RN
FROM Table A ) B
WHERE B.RN = 1
or using a cross apply with a limit or top.
The Simpliest way to do:
SELECT *
FROM fragment fra1
WHERE (id = :id)
and timestamp = (select max(timestamp)
from fragment fra2
where fra2.id = fra1.id
and fra2.field_name = fra1.field_name)
GROUP BY field_name
Suppose I have some data like:
id status activity_date
--- ------ -------------
101 R 2014-01-12
101 Mt 2014-04-27
101 R 2014-05-18
102 R 2014-02-19
Note that for rows with id = 101 we have activity between 2014-01-12 to 2014-04-26 and 2014-05-18 to current date.
Now I need to select that data where status = 'R' and the date is the most current date as of a given date, e.g. if I search for 2014-02-02, I would find the status row created on 2014-01-12, because that was the status that was still valid at the time for entity ID 101.
If I understand correctly:
Step 1: Convert the start and end date rows into columns. For this, you must join the table with itself based on this criteria:
SELECT
dates_fr.id,
dates_fr.activity_date AS date_fr,
MIN(dates_to.activity_date) AS date_to
FROM test AS dates_fr
LEFT JOIN test AS dates_to ON
dates_to.id = dates_fr.id AND
dates_to.status = 'Mt' AND
dates_to.activity_date > dates_fr.activity_date
WHERE dates_fr.status = 'R'
GROUP BY dates_fr.id, dates_fr.activity_date
+------+------------+------------+
| id | date_fr | date_to |
+------+------------+------------+
| 101 | 2014-01-12 | 2014-04-27 |
| 101 | 2014-05-18 | NULL |
| 102 | 2014-02-19 | NULL |
+------+------------+------------+
Step 2: The rest is simple. Wrap the query inside another query and use appropriate where clause:
SELECT * FROM (
SELECT
dates_fr.id,
dates_fr.activity_date AS date_fr,
MIN(dates_to.activity_date) AS date_to
FROM test AS dates_fr
LEFT JOIN test AS dates_to ON
dates_to.id = dates_fr.id AND
dates_to.status = 'Mt' AND
dates_to.activity_date > dates_fr.activity_date
WHERE dates_fr.status = 'R'
GROUP BY dates_fr.id, dates_fr.activity_date
) AS temp WHERE '2014-02-02' >= temp.date_fr and ('2014-02-02' < temp.date_to OR temp.date_to IS NULL)
+------+------------+------------+
| id | date_fr | date_to |
+------+------------+------------+
| 101 | 2014-01-12 | 2014-04-27 |
+------+------------+------------+
SQL Fiddle
You can try
select id, status, activity_date
from TABLE
where status = "R" and activity_date = "2014-02-02"
where TABLE is name of your table
I think you need following ans
SELECT id,MAX(CAST(ACTIVITY_DATE AS date),MIN(CAST (ACTIVITY_DATE AS date)
FROM Table_Name WHERE CAST('2014-02-02' AS date)
BETWEEN MIN(CAST (ACTIVITY_DATE AS date) AND MAX(CAST(ACTIVITY_DATE AS date)
AND Status='R'
GROUP BY id
Try this:
select * from yourtable
where status='R' and activity_date= '2014-02-02'
You can make a query to effectively give you the most status as of a date, e.g.
SELECT
id,
substr(max(concat(activity_date, status)),11) as status,
max(activity_date) as activity_date
FROM table
WHERE activity_date <= '2014-02-02'
GROUP by id;
Then, similar to Salman's answer, you can use this result inside another query and look for all those results with a status of 'R'
SELECT * from (
SELECT
id,
substr(max(concat(activity_date, status)),11) as status,
max(activity_date) as activity_date
FROM table
WHERE activity_date <= '2014-02-02'
GROUP by id
) AS temp WHERE temp.status = 'R';
Edit: Rather than use the questionable method of sorting the statuses, you could identify the relevant maximum record with a sub-query, so the original query would become
SELECT join1.* FROM table AS join1
INNER JOIN (
SELECT id, max(activity_date) as max_activity_date
FROM table
WHERE activity_date < '2014-02-02'
GROUP BY id
) AS join2
ON join1.id = join2.id AND join1.activity_date = join2.max_activity_date;
and the full query
SELECT * from (
SELECT join1.* FROM table AS join1
INNER JOIN (
SELECT id, max(activity_date) as max_activity_date
FROM table
WHERE activity_date < '2014-02-02'
GROUP BY id
) AS join2
ON join1.id = join2.id AND join1.activity_date = join2.max_activity_date
) AS temp WHERE temp.status = 'R';
try the following
SELECT *
FROM your_relation
WHERE status='R'
AND activity_data="2014-02-02"
I completely agree with Salman's response, the table could be designed in a fashion that allows for greater query accuracy and extensibility. However, the question asked, with regards to a query selecting information based on status and date range can be expressed as.
SELECT * FROM Table_1
WHERE ((status = 'R')
AND ((activity_date BETWEEN '2014-01-12' AND '2014-04-26')
OR activity_date > CONVERT(DATETIME, '2014-05-17')))
This will select all data with a status of 'R' and will use the BETWEEN operator for the range desired; moreover, the conversion of the final operator is because the expression is evaluated as a mathematical expression and requires explicit conversion.