MS Access - Update and Inner Join with Multiple Rows - ms-access

I found answer for this problem foe SQL Server but not MS Access.
Table NE:
IP | Status | Peer
-----------------------------
10.10.10.1 | 0 | null
10.20.1.5 | 0 | null
Table Peer:
IP | Peer | Status | Flap_Count
------------------------------------------------------
10.10.10.1 | 10.100.2.3 | 0 | 0
10.10.10.1 | 10.200.1.1 | 1 | 5
10.10.10.1 | 10.50.100.7 | 10 | 9
10.10.10.1 | 10.1.1.1 | 10 | 7
10.20.1.5 | 10.20.20.7 | 10 | 5
10.20.1.5 | 10.2.2.10 | 5 | 2
Selection Rule
get record from the biggest Status AND biggest Flap_Count
Desired result:
IP | Status | Peer
-----------------------------
10.10.10.1 | 10 | 10.50.100.7
10.20.1.5 | 10 | 10.20.20.7
Sometimes this query works and sometimes it fails:
Update NE
Inner join (Select * from Peer Order by Status Desc, Flap_Count Desc) Q
On Q.IP = NE.IP
Set NE.Status = Q.Status, NE.Peer = Q.Peer
Where NE.Status = 0
That query pops up a notification (for example) that 3 rows will be affected for IP 10.10.10.1. Which one will be kept?
When I change Order by Status Desc to Order by Status Asc the result does not change.

Need a unique identifier field in Peer table - an autonumber will serve.
Do not see that NE table is even needed to produce the desired output.
SELECT * FROM Peer WHERE ID IN (
SELECT TOP 1 ID FROM Peer AS Dup WHERE Dup.IP = Peer.IP
ORDER BY Dup.Status DESC, Dup.Flap_Count DESC, Dup.ID DESC);
But if you must UPDATE NE:
UPDATE NE
INNER JOIN(
SELECT * FROM Peer WHERE ID IN (
SELECT TOP 1 ID FROM Peer AS Dup WHERE Dup.IP = Peer.IP
ORDER BY Dup.Status DESC, Dup.Flap_Count, Dup.ID DESC)) AS Q1
ON NE.IP = Q1.IP SET NE.Status = [q1].[Status], NE.Peer = [q1].[Peer];
Unfortunately, correlated subquery can perform poorly with large dataset, even crashing.
Here is alternate approach that does not need unique ID field. It should return one record for each IP if two assumptions are true:
same record has highest values for both Status and Flap_Count for each IP
Status and Flap_Count will always be unique pairs within each IP group
SELECT Peer.* FROM Peer INNER JOIN (
SELECT Peer.IP, Max(Peer.Status) AS MS, Max(Peer.Flap_Count) AS MF
FROM Peer
GROUP BY Peer.IP) AS Q1
ON Peer.IP = Q1.IP AND Peer.Status = Q1.MS AND Peer.Flap_Count = Q1.MF;
An UPDATE action SQL cannot directly use an aggregate (GROUP BY) query as data source, however, I still don't see need for NE table.

Related

First Unique Sql row

I have a MySql table of users order and it has columns such as:
user_id | timestamp | is_order_Parent | Status |
1 | 10-02-2020 | N | C |
2 | 11-02-2010 | Y | D |
3 | 11-02-2020 | N | C |
1 | 12-02-2010 | N | C |
1 | 15-02-2020 | N | C |
2 | 15-02-2010 | N | C |
I want to count number of new custmer per day defined as: a customer who orders non-parent order and his order status is C AND WHEN COUNTING A USER ONCE IN A DAY WE DONT COUNT HIM FOR OTHER DAYS
An ideal resulted table will be:
Timestamp: Day | Distinct values of User ID
10-02-2020 | 1
11-02-2010 | 1
12-02-2010 | 0 <--- already counted user_id = 1 above, so no need to count it here
15-02-2010 | 1
table name is cscart_orders
If you are running MySQL 8.0, you can do this with window functions an aggregation:
select timestamp, sum(timestamp = timestamp0) new_users
from (
select
t.*,
min(case when is_order_parent = 'N' and status = 'C' then timestamp end) over(partition by user_id) timestamp0
from mytable t
) t
group by timestamp
The window min() computes the timestamp when each user became a "new user". Then, the outer query aggregates by date, and counts how many new users were found on that date.
A nice thing about this approach is that it does not require enumerating the dates separately.
You can use two levels of aggregation:
select first_timestamp, count(*)
from (select t.user_id, min(timestamp) as first_timestamp
from t
where is_order_parent = 'N' and status = 'C'
group by t.user_id
) t
group by first_timestamp;

MySql Preferential Sorting Using FIELD

Student table :
----------------------
id | name
______________________
1 | Name 1
2 | Name 2
Course Table :
id | student_id | ctype | level
__________________________________
1 | 2 | beginner | complete
2 | 2 | advanced | current
3 | 1 | beginner | current
4 | 2 | intermed | skipped
From the above two table i am trying to get the latest user records based on the level from course table . the level should be matched such that it checks for current, complete and skipped in the same order so if the user has a level of current for any course type it should be fetched else check the level complete...
i am using the following query .
SELECT `sc`.`student_id`,
`s`.`name`,
`sc`.`id` as `course_id`,
`sc`.`ctype`,
`sc`.`level`,
FROM `course` `sc`
LEFT JOIN `students` `s` ON `s`.`id` = `sc`.`student_id`
WHERE sc.id = (SELECT ssc.id FROM course ssc WHERE ssc.student_id = sc.student_id
ORDER BY FIELD(`ssc`.`level`,"current","complete","skipped") DESC LIMIT 1,1)
GROUP BY `sc`.`student_id`
ORDER BY `sc`.`id` DESC
LIMIT 20
The problem with the above query is it displays only if there is more than one user id matching in course table . so the final output i get is it displays only the student with id 2 and ignore the student with id 1 as there is no more than one .
Result form above query
student_id | name | course_id | ctype | level |
=====================================================
2 | Name 2 | 2 | advanced | current
Expected Result
student_id | name | course_id | ctype | level |
=====================================================
2 | Name 2 | 2 | advanced | current
1 | Name 1 | 3 | beginner | current
NOTE : I have also tried FIELD_IN_SET and IN instead of FIELD im getting the same result
Change LIMIT 1,1 to LIMIT 0,1 or just LIMIT 1.
Unlike most other things in SQL, the offset field in the LIMIT clause is 0-based, not 1-based. So if there's only 1 matching row, LIMIT 1,1 skips over it. And if there are 2 or more matching rows, you're not getting the top match, you're getting the 2nd match.
Also, the ordering should be ASC, not DESC, since you want to prefer the lowest field (current), not the highest.
SELECT `sc`.`student_id`,
`s`.`name`,
`sc`.`id` as `course_id`,
`sc`.`ctype`,
`sc`.`level`
FROM `course` `sc`
LEFT JOIN `students` `s` ON `s`.`id` = `sc`.`student_id`
WHERE sc.id = (
SELECT ssc.id FROM course ssc
WHERE ssc.student_id = sc.student_id
ORDER BY FIELD(`ssc`.`level`,"current","complete","skipped") ASC
LIMIT 0,1)
GROUP BY `sc`.`student_id`
ORDER BY `sc`.`id` DESC
LIMIT 20
DEMO
There's also no need for GROUP BYsc.student_id`. The query is only returning one course ID per student, so there can't be multiple rows for each student.

Is it possible to update a mysql table only if all matching rows will get updated?

I have a table that looks like this
| id | location | status | parent_id |
-----------------------------------
| 1 | 35 | 0 | 100 |
| 2 | 35 | 1 | 100 |
| 3 | 36 | 0 | 200 |
| 4 | 36 | 0 | 200 |
I want to be able to update the group and set status = 0 of rows that match a certain parent_id if and only if all of the rows with the particular parent_id have status set to 0.
The use case is something like this:
An activity (parent_id) may be done at one of multiple locations. Each activity has some sub-activities. An activity may move from one location to another only if none of the sub-activities has started.
Would it be possible to do it transactionally in MySQL? If it is possible to do it without locking the whole table it would be much more efficient, but I am not sure if there is a single statement solution to that.
Thanks in advance.
EDIT : I missed out one important bit - the parent_id is the id to the table where the main activity is stored. Hence, a join of this table with itself on (id, parent_id) would be incorrect.
How about this. I haven't tested and the performance may not be the best.
UPDATE t
JOIN (SELECT parent_id, max(status) AS ma, min(status) AS mi FROM t2 GROUP BY parent_id HAVING ma<=0 AND mi>=-1) AS children
ON t.id=children.parent_id
SET status = 0;
EDIT
Changed to use max an min instead of sum to support the fact that status can be less than 0 and now also allowing -1 in the children to be updated.
This is what I tried and it seems to be working:
UPDATE foo
JOIN
(SELECT parent_id, COUNT(*) AS status_count FROM foo WHERE parent_id = 200 AND status IN (0, -1)
HAVING status_count = (SELECT COUNT(*) AS total_count FROM foo WHERE parent_id = 200))
AS x ON x.parent_id = foo.parent_id
SET status = 0, group_id = 98;

Order by a column in another table

I have two MySQL tables:
web_forums_threads
| tid | title |
|=================|
| 1 | News Post |
web_forums_posts
| pid | tid | content | date_created | date_modified |
|===========================================================|
| 1 | 1 | Today,.. | unix timestamp | null or timestamp |
| 2 | 1 | I agree! | unix timestamp | null or timestamp |
I want to SELECT * from web_forums_threads, and order by the most recent date_created value from web_forums_posts with the correct corresponding TID.
I feel as though the results I've found from Google may be incorrect for my case, because multiple rows can exist with the threads' TID.
The example that I tried (with no success):
SELECT * FROM web_forums_threads WHERE fid = :postfid ORDER BY (SELECT date_created FROM web_forums_posts WHERE web_forum_posts.tid = web_forums_threads.tid) DESC;
The syntax might be wrong but the concept is there. I don't want to add another column to the threads table because I'd just be storing info twice (the first post acts as the threads content).
You have to make the join between the two tables
SELECT * FROM web_forums_threads AS WFT, web_forums_posts AS WFP
WHERE WFT.tid=WFP.tid
ORDER BY WFP.date_created
It would be something like that
You can use rather JOIN.
SELECT T.tid, T.title
FROM web_forums_threads T
JOIN web_forums_posts P ON T.tid = P.tid
WHERE fid = :postfid
ORDER BY P.date_created;

Filter a mysql query with conditions / Group the result

I have a mysql table (see below) which contains a lot email addresses. I want to select X(20) entries from this table but only Y(2) from each domain. Is this possible with sql? If I use Group by only one domain will be used. But it should be variable how many domains are used per Query.
The Table
id | email | domain
---|--------------|--------
1 | foo#bar.de | bar.de
2 | baz#bar.de | bar.de
3 | admin#bar.de | bar.de
4 | info#bar.de | bar.de
5 | bar#foo.com | foo.com
6 | baz#foo.com | foo.com
The Result should be
ID: 1,2,5,6
If you only want two entries for each domain, then you can do:
select t.*
from thetable t
where (select count(*)
from thetable t2
where t2.domain = t.domain and t2.id <= t.id
) <= 2;
If you have a larger table, there are more efficient methods.
I have no idea what X(20) is supposed to mean.