Comparing 2 rows in SQL - mysql

I have a database that is a history of parts replaced in vehicles. So over time a vehicle may have the same part swapped several times. I am looking to pull out ONLY the most recent part swaps and the data surrounding only the most recent swaps. Here's a sample of my table:
Part
Install_Date
NewSerial Number
Vehicle
A
2023-2-17
123456
Bus1
A
2022-12-3
789101
Bus1
A
2021-11-4
X2349
Bus1
A
2022-10-6
XTY76
Bus2
A
2020-9-6
X56678
Bus2
B
2022-6-7
123456
Bus1
B
2020-5-31
56gh6
Bus1
The table goes on as it's much bigger but on my sort I want to have an output that looks like this, with only
the most recent part swaps:
Part
Install_Date
NewSerial Number
Vehicle
A
2023-2-17
123456
Bus1
A
2022-10-6
XTY76
Bus2
B
2022-6-7
123456
Bus1
I am using MariaDB in a phpMyAdmin shell. I have tried numerous ways. I also do not think MariaDB supports the LAG or LEAD function which is one of the ways I tried it. Any ideas?
Tried LAG and LEAD, tried various options using JOIN, WHERE clauses, just can't seem to get syntax right?

An "old-fashioned" query without window functions would always be possible:
select A.*
from
Table A
inner join
(
select
Vehicle,
Part,
Max(Install_Date) as MaxOfInstallDate
from
Table
group by
Vehicle,
Part
) B
on A.Vehicle = B.Vehicle,
and A.Part = B.Part
and A.Install_Date = B.MaxOfInstallDate;

Related

Compare differences in 2 tables

I am running a MySQL Server on Ubuntu, patched up to date...
In MySQL, I have 2 tables in a database. I am trying to get a stock query change working and it kind of is, but it's not :(
What I have is a table (table A) that holds the last time I have checked stock levels, and another table (table B) that holds current stock levels. Each table has identical column names and types.
What I want to do is report on the changes from table B. The reason is that there are about 1/2 million items in this table - and I cannot just update each item using the table as a source as I am limited to 100 changes at a time. So, ideally, I want to get the changes - store them in a temporary table, and use that table to update our system with just those changes...
The following below brings back the changes but shows both Table A and Table B.
I have tried using a Left Join to only report back on Table B but I'm not a mysql (or any SQL) guy, and googling all this... Can anyone help please. TIA. Stuart
SELECT StockItemName,StockLevel
FROM (
SELECT StockItemName,StockLevel FROM stock
UNION ALL
SELECT StockItemName,StockLevel FROM stock_copy
) tbl
GROUP BY StockItemName,StockLevel
HAVING count(*) = 1
ORDER BY StockItemName;
The query below spit out records that have different stock level in both table.
SELECT s.StockItemName, s.StockLevel, sc.StockLevel
FROM stock s
LEFT JOIN stock_copy sc ON sc.Id = s.Id AND sc.StockLevel <> s.StockLevel
ORDER BY s.StockItemName
ok - I solved it - as there wasn't a unique ID on each table that could be matched, and rather than make one, I used 3 colums to create the unique ID and left joined on that.
SELECT sc.StockItem, sc.StockItemName, sc.Warehouse, sc.stocklevel
FROM stock s
LEFT JOIN stock_copy sc ON (sc.StockItem = s.StockItem AND sc.StockItemName = s.StockItemName AND sc.Warehouse = s.Warehouse AND sc.StockLevel <> s.StockLevel)
having sc.StockLevel is not Null;

Something wrong with Inner Join on Teradata

I had to find a particular number on consumers on a network. Client suggested to use table "abcd" and to make sure that manufacturer='big_company" is met. So I ran below query on Teradata.
select count(*)from(select tel_num, manufacturer from abcd
where manufacturer='big_company'
and tel_num is not null)pqr
This query ran properly and the total number of record were 600 million.
The another question client had was, Out of the consumers on network how many of them are choosing a particular service. I was being asked to use table "wxyz" and ensure postpaid=1 condition is met. To achieve this I had to create inner join between abcd and wxyz on tel_num. Below was the query I used:
select cast (count (*)as bigint) from (select a.tel_num, b.postpaid from
abcd as a inner join wxyz as b on a.tel_num=b.tel_num
where a.manufacturer='big_company'
and b.postpaid=1) xyz
The above query generates 5 billion records.
This seems very strange because, since I have used inner join the number of records in the second query should be less than 600 million. I'm just not able to figure out where I'm going wrong.
As #useless'MJ already put it in a comment, you are probably getting multiple results per tel_num from table wxyz. You could avoid JOINand distinct altogether by using EXISTS like in
select cast (count (*)as bigint)
from abcd a
where exists (select 1 from wxyz b
where a.tel_num=b.tel_num and b.postpaid=1)
and a.manufacturer='big_company'

MySQL Query Performance with a Derived Query

I am looking at a few queries for performance and made a change to a query, which is based on the following examples. The change turned a 6 minute query into one which completes in few seconds and I was wondering why? How has this altered things to such an extent?
In the example, please assume the BOOK table to contain the general details for all books in a library and the FORMATS table contains details, such as HARDBACK, PAPERBACK and eBOOK (allowing for new formats to be added) where there is a key (called FORMATID) linking the two tables.
Query executes in 6 minutes
select b.bookid, f.formatname
from book b
inner join formats f on f.formatid = b.formatid
select b.bookid, f.formatname
from book b
left join formats f on f.formatid = b.formatid
Query executes in 12 seconds
select b.bookid, (select f.formatname from formats f where f.formatid = b.formatid)
from book b
where b.formatid is not null
select b.bookid, (select f.formatname from formats f where f.formatid = b.formatid)
from book b
In the above, the first query of each pair achieves INNER JOIN results and the second, achieves LEFT JOIN. The results difference on my database is 295166 and 295376 rows; the ties differences remain pretty much the same.
[added] For confirmation; I have tested this (with the same results) by creating the two test tables mentioned herein, populating the BOOKS table with ~1 million rows and NOT applying any index or other optimisation.

How to retrieve report figure between two dates

For implicity, I am going to say we have 5 tables for SQL
1) Client Accounts
2) Leads
3) Calls
4) Report
Below is a SQL example I made up to get an idea how it works. My question is, how can I retrieve records that received a business report from date of the phone calls and 5 days onwards.
Example
Today is 8.06.2017, I made a phone call to a Lead. This lead is ABC. ABC is interested in XYZ. I sent a report to XYZ on 12.06.17. This is within 5 days. So this record should be retrieved and counted as I potentially find more than 100 records within 5 days.
SELECT Id, Name, DateOfCall
FROM Lead, Call, ClientAccounts
ON Lead.id = Call.id AND Lead.id = ClientAccounts.id
WHERE DATEDIFF (DateOfReport, StartOfCall, getDate()) < 5
If I execute this Sql statement, it should retrieve the above report, the name of ClientAccount and the Lead associated with that ClientAccount.
I hope this make sense and I am very beginner in SQL, but I am aware my syntaxes for SQL is wrong but the idea is understanding this and worry about syntax a little later as I do not have SQL server to execute this.
You aren't far off from your goal.
You need to use proper JOIN syntax; what you have is a bit fractured.
SELECT ... some columns ...
FROM Lead L
JOIN Call C ON L.id = C.id
JOIN ClientAccounts CA ON L.id = CA.id
That assigns the alias names L, C, and CA to your three tables.
You can try running this with SELECT * to see what you get. But using * in a finished query is a rookie mistake.
Next you need to give the names, with table aliases, of the columns you wish to SELECT. I have to guess which table each column comes from, because you didn't show us your table definitions. So, I guess...
SELECT L.id, L.Name, C.DateOfCall
FROM Lead L
JOIN Call C ON L.id = C.id
JOIN ClientAccounts CA ON L.id = CA.id
Run that. See what you get. If my example has the table or column names wrong, fix them. (Please don't ask us to fix them here on SO; we don't know your table layouts.)
Finally you need to select the date range. Try
WHERE C.StartOfCall >= CURDATE() - INTERVAL 5 DAY
This will get all rows where the call started on or after midnight five days ago.

Using Joins, Group By and Sub Queries, Oh My!

I have a database with a table for details of ponies, another for details of contacts (owners and breeders), and then several other small tables for parameters (colours, counties, area codes, etc.). To give me a list of existing pony profiles, with their various details given, i use the following query:
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts
ON profiles.ProfileOwnerID = contacts.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
ORDER BY profiles.ProfileYearOfBirth ASC $limit
In the above sample, the 'profiles' table is my primary table (holding the Ponies info), 'contacts' is second in importance holding as it does the owner and breeder info. The lesser parameter tables can be identified by their prm_ prefix. The above query works fine, but i want to do more.
The first big issue is that I wish to GROUP the results by gender: Stallions, Mares, Geldings... I used << GROUP BY prm_breedgender.BreedGender >> or << GROUP BY ProfileBreedGenderID >> before my ORDER BY line, but than only returns two results from all my available profiles. I have read up on this, and apparantly need to reorganise my query to accomodate GROUP within my primary SELECT clause. How to do this however, gets me verrrrrrry confused. Step by step help here would be fantabulous.
As a further note on the above - You may have noticed the $limit var at the end of my query. This is for pagination, a feature I want to keep. I shouldn't think that's an issue however.
My secondary issue is more of an organisational one. You can see where I have pulled my Owner information from the contacts table here:
LEFT JOIN contacts
ON profiles.ProfileOwnerID = contacts.ContactID
I could add another stipulation:
AND profiles.ProfileBreederID = contacts.ContactID
with the intention of being able to list a pony's Owner and Breeder, where info on either is available. I'm not sure how to echo out this info though, as $row['ContactName'] could apply in either the capacity of owner OR breeder.
Is this a case of simply running two queries rather than one? Assigning a variable $foo to the first run of the query, then just run another separate query altogether and assign $bar to those results? Or is there a smarter way of doing it all in the one query (e.g. $row['ContactName']First-iteration, $row['ContactName']Second-iteration)? Advice here would be much appreciated.
And That's it! I've tried to be as clear as possible, and do really appreciate any help or advice at all you can give. Thanks in advance.
##########################################################################EDIT
My query currently stands as an amalgam of that provided by Cularis and Symcbean:
SELECT *
FROM (
profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts AS owners
ON profiles.ProfileOwnerID = owners.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
)
LEFT JOIN contacts AS breeders
ON profiles.ProfileBreederID = breeders.ContactID
ORDER BY prm_breedgender.BreedGender ASC, profiles.ProfileYearOfBirth ASC $limit
It works insofar as the results are being arranged as I had hoped: i.e. by age and gender. However, I cannot seem to get the alias' to work in relation to the contacts queries (breeder and owner). No error is displayed, and neither are any Owners or Breeders. Any further clarification on this would be hugely appreciated.
P.s. I dropped the alias given to the final LEFT JOIN by Symcbean's example, as I could not get the resulting ORDER BY statement to work for me - my own fault, I'm certain. Nonetheless, it works now although this may be what is causing the issue with the contacts query.
GROUP in SQL terms means using aggregate functions over a group of entries. I guess what you want is order by gender:
ORDER BY prm_breedgender.BreedGender ASC, profiles.ProfileYearOfBirth ASC $limit
This will output all Stallions, etc. next to each other.
To also get the breeders contact, you need to join with the contacts table again, using an alias:
LEFT JOIN contacts AS owners
ON profiles.ProfileOwnerID = owners.ContactID
LEFT JOIN contacts AS breeders
ON profiles.ProfileBreederID = breeders.ContactID
To further expand on what #cularis stated, group by is for aggregations down to the lowest level of "grouping" criteria. For example, and I'm not doing per your specific tables, but you'll see the impact. Say you want to show a page grouped by Breed. Then, a user picks a breed and they can see all entries of that breed.
PonyID ProfileGenderID Breeder
1 1 1
2 1 1
3 2 2
4 3 3
5 1 2
6 1 3
7 2 3
Assuming your Gender table is a lookup where ex:
BreedGenderID Description
1 Stallion
2 Mare
3 Geldings
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
select
BG.Description,
count(*) as CountPerBreed
from
Profiles P
join prm_BreedGender BG
on p.ProfileGenderID = BG.BreedGenderID
group by
BG.Description
order by
BG.Description
would result in something like (counts are only coincidentally sequential)
Description CountPerBreed
Geldings 1
Mare 2
Stallion 4
change the "order by" clause to "order by CountsPerBreed Desc" (for descending) and you would get
Description CountPerBreed
Stallion 4
Mare 2
Geldings 1
To expand, if you wanted the aggregations to be broken down per breeder... It is a best practice to group by all things that are NOT AGGREGATES (such as MIN(), MAX(), AVG(), COUNT(), SUM(), etc)
select
BG.Description,
BR.BreaderName,
count(*) as CountPerBreed
from
Profiles P
join prm_BreedGender BG
on p.ProfileGenderID = BG.BreedGenderID
join Breeders BR
on p.Breeder = BR.BreaderID
group by
BG.Description,
BR.BreaderName
order by
BG.Description
would result in something like (counts are only coincidentally sequential)
Description BreaderName CountPerBreed
Geldings Bill 1
Mare John 1
Mare Sally 1
Stallion George 2
Stallion Tom 1
Stallion Wayne 1
As you can see, the more granularity you provide to the group by, the aggregation per that level is smaller.
Your join conditions otherwise are obviously understood from what you've provided. Hopefully this sample clearly provides what the querying process will do. Your group by does not have to be the same as the final order... its just common to see so someone looking at the results is not trying to guess how the data was organized.
In your sample, you had an order by the birth year. When doing an aggregation, you will never have the specific birth year of a single pony to so order by... UNLESS.... You included the YEAR( ProfileYearOfBirth ) as BirthYear as a column, and included that WITH your group by... Such as having 100 ponies 1 yr old and 37 at 2 yrs old of a given breed.
It would have been helpful if you'd provided details of the table structure and approximate numbers of rows. Also using '*' for a SELECT is a messy practice - and will cause you problems later (see below).
What version of MySQL is this?
apparantly need to reorganise my query to accomodate GROUP within my primary SELECT clause
Not necessarily since v4 (? IIRC), you could just wrap your query in a consolidating select (but move the limit into the outer select:
SELECT ProfileGenderID, COUNT(*)
FROM (
[your query without the LIMIT]
) ilv
GROUP BY ProfileGenderID
LIMIT $limit;
(note you can't ORDER BY ilv.ProfileYearOfBirth since it is not a selected column / group by expression)
How many records/columns do you have in prm_breedgender? Is it just Stallions, Mares, Geldings...? Do you think this list is likely to change? Do you have ponies with multiple genders? I suspect that this domain would be better represented by an enum in the profiles table.
with the intention of being able to list a pony's Owner and Breeder,
Using the code you suggest, you'll only get returned instances where the owner and breeder are the same! You need to add a second instance of the contacts table with a different alias to get them all, e.g.
SELECT *
FROM (
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts ownerContact
ON profiles.ProfileOwnerID = ownerContact.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
) ilv LEFT JOIN contacts breederContact
ON ilv.ProfileBreederID = breederContact.ContactID
ORDER BY ilv.ProfileYearOfBirth ASC $limit