MySQL query, two tables with order group by - mysql

I have two tables, bills and linesbill. I need all the products that a customer has ever bought. I've gotten this to work:
SELECT referencia, codcliente, pvpunitario, t2.fecha FROM
lineasfacturascli T1
INNER JOIN facturascli T2 ON T1.idfactura = T2.idfactura
WHERE T2.codcliente = "000001"
GROUP BY referencia
But I need get the last price that the customer has paid for each product. I'm trying to order by "fecha"->(date) but it does not work.
Tables structure
facturascli
idfactura(id bill),
codcliente(client id),
fecha(date)
lineasfacturascli
referencia(name of product),
idfactura(id bill)
pvpunitario(price)
Edit
DRapp solution works but I also need to handle the case that a customer buys it in the same day get only the lower price:
With the solution provided the result is:
|Referencia| |MostRecentDatePerItem| |MostRecentPricePerItem|
| pendrive | | 2017-03-02 | | 50 |
| pendrive | | 2017-03-02 | | 10 |
| samsung | | 2017-03-02 | | 50 |
| linux car| | 2017-04-26 | | 9.99 |
I need:
|Referencia| |MostRecentDatePerItem| |MostRecentPricePerItem|
| pendrive | | 2017-03-02 | | 10 |
| samsung | | 2017-03-02 | | 50 |
| linux car| | 2017-04-26 | | 9.99 |
Thanks

I would start with an inner pre-query of all line items for a specific person with a max date per item as a group by. So if a person ordered the 10 things multiple times over say... 50 orders, you would still have the final list of 10 things, but also the most recent date the thing was ordered.
The following is based on not exactly knowing your structures, nor sample data (please provide for future). Also, you should always qualify your table columns in a query with the corresponding table alias reference so users know which field comes from what table. I have to assume the "pvpunitario" column is from the line item details as the price, but basic translation appears to be "unit" not price. You will have to adjust accordingly if I am inaccurate on my impression.
select
T1.referencia,
max( t2.fecha ) as MostRecentDatePerItem
FROM
lineasfacturascli T1
INNER JOIN facturascli T2
ON T1.idfactura = T2.idfactura
WHERE
T2.codcliente = "000001"
GROUP BY
T1.referencia
So this will give us just the products and the maximum date ever ordered by a single client. Now, we take this result as a basis to the original query, re-joined to the line items / order headers that specifically match the corresponding MostRecentDatePerItem.
select
TT1.Referencia,
PQ.MostRecentDatePerItem,
TT1.pvpunitario as MostRecentPricePerItem
from
lineasfacturascli TT1
JOIN
(select
T1.referencia,
max( t2.fecha ) as MostRecentDatePerItem
FROM
lineasfacturascli T1
INNER JOIN facturascli T2
ON T1.idfactura = T2.idfactura
WHERE
T2.codcliente = "000001"
GROUP BY
T1.referencia ) PQ
on TT1.Referencia = PQ.Referencia
JOIN facturascli TT2
ON TT1.idfactura = TT2.idfactura
AND PQ.MostRecentDatePerItem = TT2.Fecha
where
TT2.codcliente = "000001"
To clarify what is going on. The inner query (now alias "PQ" -- PreQuery), is just those qualifying items for the one client in question with the most recent date said item was purchased.
So now back to the original list of all order line items joined to this table keeps the reference product ID linked. Now, we go again to the order header table and still apply the same client code, but ALSO joined on the same FETCHA date as the maximum date found for the transaction. So only THEN do we want to grab the detail level price / unit information for said product.
Hopefully this helps direct your final solution. If I am incorrect on any pieces, you should EDIT your original question and supply the additional missing details / alias references / sample data. Then you can reply comment for follow-up support.
Answer per Comment.
To get the minimum price, you would just adjust the outer select and add a group by. Since the item is the same, the group by will only group for the prices on that specific day. Change the above to...
select
TT1.Referencia,
PQ.MostRecentDatePerItem,
MIN( TT1.pvpunitario ) as LeastPricePerItemOnThisDate
(same rest of query)
GROUP BY
TT1.Referencia,
PQ.MostRecentDatePerItem

Related

Return preferred record when there is more than one record for the same user

I have a table where it stores the types of discounts that a user can have.
Some users will get the standard discount, but some will get a bigger and better discount. For users who have the biggest and best discount, there will be two records in the database, one for the default discount and the other for the biggest and best discount. The biggest and best discount will be preferred in the search.
I would like to do a SELECT that would return the record with the highest discount and if you don't find it, return it with the standard discount for me to avoid making two queries in the database or having to filter in the source code.
Ex:
| id | user_id | country | discount | cashback | free_trial |
|-----------------------------------------------------------------------|
| 1 | 1 | EUA | DEFAULT | 10 | false |
| 2 | 1 | EUA | CHRISTMAS | 20 | true |
| 3 | 3 | EUA | DEFAULT | 10 | false |
SELECT *
FROM users
WHERE country = 'EUA'
AND (discount = 'CHRISTMAS' OR discount = 'DEFAULT');
In this example above for user 1 it would return the record with the discount equal to "CHRISTMAS" and for user 3 it would return "DEFAULT" because it is the only one that has. Can you help me please?
You can use the row_number() window function to do this. This function includes a PARTITION BY that lets you start the numbering over with each user, as well as it's own ORDER BY that lets you determine which rows will sort first within each user/partition.
Then you nest this inside another SELECT to limit to rows where the row_number() result is 1 (the discount that sorted best):
SELECT *
FROM (
SELECT *, row_number() OVER (PARTITION BY id, ORDER BY cashback desc) rn
FROM users
WHERE country = 'EUA'
) u
WHERE rn = 1
You could also use a LATERAL JOIN, which is usually better than the correlated join in the other answer, but not as good as the window function.
You can using GROUP BY to do it
SELECT u1.*
FROM users u1
JOIN
(
SELECT COUNT(id) AS cnt,user_id
FROM users WHERE country = 'EUA'
GROUP BY user_id
) u2 ON u1.user_id=u2.user_id
WHERE IF(u2.cnt=1,u1.discount='DEFAULT',u1.discount='CHRISTMAS')
DB Fiddle Demo

SQL: Select a product based on minimum value of key

I'm looking for a way to select Category with lowest CustKey value as seen in below table 1. I want it to be displayed in a column called SignupCategory. I have also linked to my current SQL code which I cant make display the Category rather than the CustKey. I appreciate any suggestions as I am terribly stuck atm. Code is semi-dummy code. Note: Given that I have 10.000 CustomerIDs I would want all 10.000 customers SignupCategory.
Table 1:
| CustKey | CustomerID | Category |
|---------|------------|----------|
| 1 | Cust1 | Paying |
| 2 | Cust1 | Unpaying |
| 3 | Cust1 | Barred |
Result should show SignupCategory 'Paying'
SQL Code:
Select c.AgreementNumber, SignupCategory
FROM Customer c
Following is the WIP from another thread I found on stackoverflow:
INNER JOIN
(SELECT AgreementNumber, MIN(CustKey) As SignupCategory
FROM Customer
GROUP BY AgreementNumber, Category) X
ON c.AgreementNumber = X.AgreementNumber and c.Category = TRY_CONVERT(nvarchar,X.SignupCategory)
Following code works but displays CustKey (similar to what I found on stackoverflow):
INNER JOIN
(SELECT AgreementNumber, MIN(CustKey) As SignupCategory
FROM Customer
GROUP BY AgreementNumber) X
ON c.AgreementNumber = X.AgreementNumber AND c.CustKey = X.SignupCategory
For all customers respectively and if you have huge amount of data then use EXISTS instead of IN:-
SELECT category as SignupCategory FROM Customer WHERE CustKey IN (SELECT MIN(CustKey) FROM Customer group by CustomerID);

How to group by relation property b if property a is null

I have a table containing items, each item belongs to an store, e.g:
id | name | store_id | price
1 | hat | 1 | 110
2 | bag | 1 | 120
3 | coat | 2 | 130
A Store can be canonical or a duplicate. A canonical Store has canonical_id equal to null, and a duplicate Store has canonical_id equal to the ID of the canonical Store, e.g:
id | name | canonical_id
1 | NYC | null
2 | Bronx | 1
I need to group items by their Store to get the total stock value of all items at the store, e.g:
SELECT store_id, SUM(price) as `stock_value` FROM items GROUP BY store_id
This would produce 2 results, Store 1 has a stock_value of 230 and Store 2 has a stock_value of 130.
Because Store 2 is a duplicate of Store 1 the items from Store 2 should be included in the total for Store 1. The goal is for this example to provide a single result of 360.
I think the correct implementation would involve some sort of join which retrieves the Store ID from stores by using IFNULL to get either the canonical_id or id after selecting based on id from the items table, but I'm struggling to implement a solution that works.
Any pointers would be greatly appreciated, thank you.
Edit: my attempt is as follows, it appears to meet my needs, are there any caveats / issues with my approach?
SELECT SUM(price) as `stock_value`, IFNULL(stores.canonical_id, store_id) as `store`
FROM items
JOIN stores on stores.id = items.store_id
GROUP BY store
I just realize you don't wanted to keep the stock_values of the "children" or "related" stores. However the next approach take those into account too:
SELECT
s.*,
(SELECT
SUM(i.price)
FROM
items AS i
INNER JOIN
stores AS s1 ON s1.id = i.store_id
WHERE
s1.cannonical_id = s.id
OR
s.id = i.store_id) AS "stock_value"
FROM
stores AS s
Online example: DB-Fiddle
If you don't want they, you just could filter the previous query using the condition WHERE s.cannonical_id is NULL like this:
SELECT
s.*,
(SELECT
SUM(i.price)
FROM
items AS i
INNER JOIN
stores AS s1 ON s1.id = i.store_id
WHERE
s1.cannonical_id = s.id
OR
s.id = i.store_id) AS "stock_value"
FROM
stores AS s
WHERE
s.cannonical_id is NULL
But, you should note that the query you posted on the updated question will be better in performance than this approach.

MySQL query - select from one, count from another table

I have database with two tables - 'Warehouses' and 'Boxes'.
Each box has field with warehouse code, each Warehouse - 'capacity' field.
The purpose is to find only Warehouses that are "overfilled" (capacity of warehouse is less then number of all boxes with this warehouse code).
So, I count all boxes and join warehouse capacity by this query:
SELECT Warehouses.Code, Warehouses.Capacity, COUNT(Boxes.Code)
FROM `Warehouses` RIGHT JOIN
`Boxes`
on Warehouses.Code = Boxes.Warehouse
GROUP BY Boxes.Warehouse
Result:
------------------------------
Code | Capacity | COUNT
------------------------------
1 | 3 | 4
------------------------------
2 | 4 | 2
------------------------------
3 | 7 | 2
------------------------------
4 | 2 | 1
------------------------------
That returns me warehouse's capacity and counts boxes in it, but I don't know how and where to compare these numbers.
You do this in a HAVING clause:
SELECT w.Code, w.Capacity, COUNT(b.Code)
FROM `Warehouses` w LEFT JOIN
`Boxes` b
on w.Code = b.Warehouse
GROUP BY w.Code, w.Capacity
HAVING w.Capacity < COUNT(b.Code);
Notes:
LEFT JOIN is generally much easier to understand than RIGHT JOIN ("Keep all rows in the first table" versus "keep all rows in the last table, which I haven't read yet"). However, this query probably only needs an INNER JOIN.
Presumably, Warehouses should be the first table, because your question is about this entity.
The HAVING clause does the comparison after the aggregation.

How to filter duplicates within row using Distinct/group by with JOINS

For simplicity, I will give a quick example of what i am trying to achieve:
Table 1 - Members
ID | Name
--------------------
1 | John
2 | Mike
3 | Sam
Table 1 - Member_Selections
ID | planID
--------------------
1 | 1
1 | 2
1 | 1
2 | 2
2 | 3
3 | 2
3 | 1
Table 3 - Selection_Details
planID | Cost
--------------------
1 | 5
2 | 10
3 | 12
When i run my query, I want to return the sum of the all member selections grouped by member. The issue I face however (e.g. table 2 data) is that some members may have duplicate information within the system by mistake. While we do our best to filter this data up front, sometimes it slips through the cracks so when I make the necessary calls to the system to pull information, I also want to filter this data.
the results SHOULD show:
Results Table
ID | Name | Total_Cost
-----------------------------
1 | John | 15
2 | Mike | 22
3 | Sam | 15
but instead have John as $20 because he has plan ID #1 inserted twice by mistake.
My query is currently:
SELECT
sq.ID, sq.name, SUM(sq.premium) AS total_cost
FROM
(
SELECT
m.id, m.name, g.premium
FROM members m
INNER JOIN member_selections s USING(ID)
INNER JOIN selection_details g USING(planid)
) sq group by sq.agent
Adding DISTINCT s.planID filters the results incorrectly as it will only show a single PlanID 1 sold (even though members 1 and 3 bought it).
Any help is appreciated.
EDIT
There is also another table I forgot to mention which is the agent table (the agent who sold the plans to members).
the final group by statement groups ALL items sold by the agent ID (which turns the final results into a single row).
Perhaps the simplest solution is to put a unique composite key on the member_selections table:
alter table member_selections add unique key ms_key (ID, planID);
which would prevent any records from being added where the unique combo of ID/planID already exist elsewhere in the table. That'd allow only a single (1,1)
comment followup:
just saw your comment about the 'alter ignore...'. That's work fine, but you'd still be left with the bad duplicates in the table. I'd suggest doing the unique key, then manually cleaning up the table. The query I put in the comments should find all the duplicates for you, which you can then weed out by hand. once the table's clean, there'll be no need for the duplicate-handling version of the query.
Use UNIQUE keys to prevent accidental duplicate entries. This will eliminate the problem at the source, instead of when it starts to show symptoms. It also makes later queries easier, because you can count on having a consistent database.
What about:
SELECT
sq.ID, sq.name, SUM(sq.premium) AS total_cost
FROM
(
SELECT
m.id, m.name, g.premium
FROM members m
INNER JOIN
(select distinct ID, PlanID from member_selections) s
USING(ID)
INNER JOIN selection_details g USING(planid)
) sq group by sq.agent
By the way, is there a reason you don't have a primary key on member_selections that will prevent these duplicates from happening in the first place?
You can add a group by clause into the inner query, which groups by all three columns, basically returning only unique rows. (I also changed 'premium' to 'cost' to match your example tables, and dropped the agent part)
SELECT
sq.ID,
sq.name,
SUM(sq.Cost) AS total_cost
FROM
(
SELECT
m.id,
m.name,
g.Cost
FROM
members m
INNER JOIN member_selections s USING(ID)
INNER JOIN selection_details g USING(planid)
GROUP BY
m.ID,
m.NAME,
g.Cost
) sq
group by
sq.ID,
sq.NAME