Mysql Match and count in the same table - mysql

I'm having an trouble with count product with some conditions on the same table..
Table structure:
INSERT INTO `filter` (`filter_seq_id`, `group_id`, `product_seq_id`) VALUES
(1, 1, 10),
(2, 1, 11),
(3, 1, 12),
(4, 1, 13),
(5, 2, 14),
(6, 2, 15),
(7, 2, 16),
(8, 2, 17),
(9, 3, 18),
(10, 3, 19),
(11, 3, 20),
(12, 3, 21),
(13, 4, 20),
(14, 4, 11),
(15, 4, 27),
(16, 4, 29),
(17, 5, 11),
(18, 5, 20),
(19, 5, 27),
(20, 5, 13);
Here i want count distinct product_seq_id for the group_id (1,2,3) only if product_seq_id also exits in both (4,5) group id..
for example:
group_id -> 1 found product_seq_id 11 in 4,5 so distinct count is 1
group_id -> 2 found nothing
group_id -> 3 found product_seq_id 20 in 4,5 so distinct count is 1
i have tried below query its not returning has i expect
its counting if product_seq_id exists in any one of (4,5)
And i want to count only if product_seq_id exits on both "4" and "5" group_id
SELECT
`f`.`group_id`, count(distinct f.product_seq_id) as count
FROM
filter f
JOIN
filter ff ON `ff`.`product_seq_id` = `f`.`product_seq_id`
AND `f`.`group_id` IN (1,2,3)
AND `ff`.`group_id` IN (4,5)
GROUP BY `f`.`group_id`
http://sqlfiddle.com/#!2/996c1/1

Is this what you are looking for?
select f123.GROUP_ID, count(f123.PRODUCT_SEQ_ID)
from (select product_seq_id from filter where group_id=4) f4
inner join (select product_seq_id from filter where group_id=5) f5
on f4.product_seq_id = f5.product_seq_id
inner join (select group_id, product_seq_id from filter where group_id<4) f123
on f123.product_seq_id = f5.product_seq_id
group by GROUP_ID order by GROUP_ID
The first two subselects selects all of the product_seq_id which are in filter in the group 4 or 5. Those 2 list are joined together only if both contain the same product_seq_id.
The Result of this is all the product_seq_id which are both in group 4 and group 5 (in this example 11 and 20)
Next this result is joined with the last subselect, which selects all group_id and product_seq_id in the groups 1 to 3. They are only joined if they contain any product_seq_id, which is in the previous result of the other join (so if they contain the product_seq_id 11 or 20). The Result of this looks like this:
Group_ID, Product_Seq_ID
1 11
3 20
This result is then grouped by the Group_ID and the amount of product_seq_id in each group is counted
EDIT: Added Explanation

Related

MYSQL JOIN with lower-than / greater-than conditions (minimum_quantity, valid_from)

I have
order table with columns
id
date
supplier_id
order_lineitem table with columns
id
order_id
article_id
order_quantity
order_price
a prices table with columns
id
article_id
supplier_id
valid_until
minimum_order_quantity
list_price
The prices table doesn't necessarily have to have a matching / valid entry, so this one would have to be joined via an outer join.
I'd like to compare order_prices against list_prices.
Therefore I need to somehow join
SELECT
o.id,
o.date,
ol.article_id,
ol.order_quantity,
ol.order_price,
p.list_price
FROM
`order` o JOIN order_lineitem ol on ol.order_id = o.id
LEFT OUTER JOIN prices p on
p.article_id = ol.article_id
AND p.supplier_id = o.supplier_id
AND p.minimum_order_quantity <= ol.order_quantity
AND IFNULL(p.valid_until, DATE('2099-12-31')) >= o.date
/* here comes the fun part that doesn't work (reliably) */
ORDER BY
IFNULL(p.valid_until, DATE('2099-12-31')) asc,
p.minimum_order_quantity desc
GROUP BY o.id, ol.id, p.article_id
/* ... trying to get only THAT price from the prices table that applies for the
(a) the given article
(b) from the given supplier
(c) that was valid at the time of purchase (i.e. has the smallest "valid_until" date that is greater than the purchase date)
(d) when ordering the given quantity (prices can also increase with higher quantities, so it has to be the price with the largest minimum_order_quantity that is smaller than the ordered quantity)
*/
I particularly don't want to fall into the trap (which I dug for myself here) of using group by to limit the results to 1 record from the prices table based on a previous sorting, since
(i) as per MySQL documentation it is non-deterministic which record will actually get returned (although it may in effect often work and this is a frequently suggested route to go) - also see this excellent explanation on the issue: https://stackoverflow.com/a/14770936/9818188 and
(ii) this concept wouldn't work on other SQL implementations like SQL Server, Maria DB & Co.
The question is not around putting in a nested query in order to be able to ORDER first and then GROUP subsequently. It's more about how to really properly get the correct row--ideally also working on other SQL implementations like SQL Server, Maria DB or Google BigQuery.
And since I can't really rely on prices being cheaper the more I buy I also can't simply get the min(list_price).
How can this can be achieved?
Since the output of this query is required for downstream processing, I can't slice & dice the task but need a full list of all orders with respective list prices.
EDIT
Here is a SQL fiddle - the desired prices are shown in column order_price, the prices incorrectly determined by the JOIN (excluding the order byclause - as this would cause non-deterministic results) are shown in column list_price:
http://sqlfiddle.com/#!9/f03a4f/2
CREATE TABLE `order`
(`id` int, `date` datetime, `supplier_id` int)
;
INSERT INTO `order`
(`id`, `date`, `supplier_id`)
VALUES
(1, '2022-01-15 00:00:00', 1),
(2, '2022-02-15 00:00:00', 1),
(3, '2022-03-15 00:00:00', 1),
(4, '2022-01-15 00:00:00', 2),
(5, '2022-02-15 00:00:00', 2),
(6, '2022-03-15 00:00:00', 2)
;
CREATE TABLE order_lineitem
(`id` int, `order_id` int, `article_id` int, `order_quantity` int, `order_price` int)
;
INSERT INTO order_lineitem
(`id`, `order_id`, `article_id`, `order_quantity`, `order_price`)
VALUES
(1, 1, 1, 1, 11),
(2, 1, 1, 10, 8),
(3, 1, 1, 100, 9),
(4, 2, 1, 1, 15),
(5, 2, 1, 10, 12),
(6, 2, 1, 100, 13),
(7, 3, 1, 1, 17),
(8, 3, 1, 10, 14),
(9, 3, 1, 100, 16),
(10, 4, 1, 1, 10),
(11, 4, 1, 10, 80),
(12, 4, 1, 100, 80),
(13, 5, 1, 1, 10),
(14, 5, 1, 10, 80),
(15, 5, 1, 100, 80),
(16, 6, 1, 1, 10),
(17, 6, 1, 10, 10),
(18, 6, 1, 100, 10)
;
CREATE TABLE prices
(`id` int, `article_id` int, `supplier_id` int, `valid_until` varchar(10), `minimum_order_quantity` int, `list_price` int)
;
INSERT INTO prices
(`id`, `article_id`, `supplier_id`, `valid_until`, `minimum_order_quantity`, `list_price`)
VALUES
(1, 1, 1, '2022-01-31', 1, 11),
(2, 1, 1, '2022-01-31', 10, 8),
(3, 1, 1, '2022-01-31', 100, 9),
(4, 1, 2, NULL, 1, 10),
(5, 1, 1, '2022-02-31', 1, 15),
(6, 1, 1, '2022-02-31', 10, 12),
(7, 1, 1, '2022-02-31', 100, 13),
(8, 1, 1, NULL, 1, 17),
(9, 1, 1, NULL, 10, 14),
(10, 1, 1, NULL, 100, 16),
(11, 2, 1, NULL, 1, 99),
(12, 1, 2, '2022-02-31', 10, 80)
;
SELECT
o.id,
o.supplier_id,
o.date,
ol.article_id,
ol.order_quantity,
ol.order_price,
p.list_price
FROM
`order` o JOIN order_lineitem ol on ol.order_id = o.id
LEFT OUTER JOIN prices p on
p.article_id = ol.article_id
AND p.supplier_id = o.supplier_id
AND p.minimum_order_quantity <= ol.order_quantity
AND IFNULL(p.valid_until, DATE('2099-12-31')) >= o.date
/* here comes the fun part that doesn't work (reliably) */
/* NOTE: I am purposesly commenting out the ORDER BY clause here, because
(a) it would have to go after GROUP BY - requiring a nested table which I would like to prevent AND, more importantly,
(b) limiting the numer of rows returned to 1 by GROUPing with an incomplete set of columns on a sorted table may return non-deterministic results as per the MySQL documentation.
see also https://stackoverflow.com/a/14770936/9818188 explaining the issue with GROUP BY in this context
#
# ORDER BY
# IFNULL(p.valid_until, DATE('2099-12-31')) asc,
# p.minimum_order_quantity desc
*/
GROUP BY o.id, ol.id, p.article_id
/* ... trying to get only THAT price from the prices table that applies for the
(a) the given article
(b) from the given supplier
(c) that was valid at the time of purchase (i.e. has the smallest "valid_until" date that is greater than the purchase date)
(d) when ordering the given quantity (prices can also increase with higher quantities, so it has to be the price with the largest minimum_order_quantity that is smaller than the ordered quantity)
*/
If you are interrestd in the highest listprice, you would do it like the.
If you need also other columns from theprices table, you need to SQL select only rows with max value on a column
as you have to join the sub querys for all articles
SELECT
o.id,
o.date,
ol.article_id,
ol.order_quantity,
ol.order_price,
(SELECT `list_price` FROM prices p WHERE
p.article_id = ol.article_id
AND p.supplier_id = o.supplier_id
AND p.minimum_order_quantity <= ol.order_quantity
AND IFNULL(p.valid_until, DATE('2099-12-31')) >= o.date
ORDER BY `list_price` DESC
LIMIT 1
) list_price
FROM
`order` o JOIN order_lineitem ol on ol.order_id = o.id

How do I build a query to get the latest row per user where a third criteria is in a separate table?

I have three tables
CREATE TABLE `LineItems` (
`LineItemID` int NOT NULL,
`OrderID` int NOT NULL,
`ProductID` int NOT NULL
);
INSERT INTO `LineItems` (`LineItemID`, `OrderID`, `ProductID`) VALUES
(1, 1, 2),
(2, 1, 1),
(3, 2, 3),
(4, 2, 4),
(5, 3, 1),
(6, 4, 2),
(7, 5, 4),
(8, 5, 2),
(9, 5, 3),
(10, 6, 1),
(11, 6, 4),
(12, 7, 4),
(13, 7, 1),
(14, 7, 2),
(15, 8, 1),
(16, 9, 3),
(17, 9, 4),
(18, 10, 3);
CREATE TABLE `Orders` (
`OrderID` int NOT NULL,
`UserID` int NOT NULL,
`OrderDate` datetime NOT NULL
);
INSERT INTO `Orders` (`OrderID`, `UserID`, `OrderDate`) VALUES
(1, 21, '2021-05-01 00:00:00'),
(2, 21, '2021-05-03 00:00:00'),
(3, 24, '2021-05-06 00:00:00'),
(4, 23, '2021-05-12 00:00:00'),
(5, 21, '2021-05-14 00:00:00'),
(6, 22, '2021-05-16 00:00:00'),
(7, 23, '2021-05-20 00:00:00'),
(8, 21, '2021-05-22 00:00:00'),
(9, 24, '2021-05-23 00:00:00'),
(10, 23, '2021-05-26 00:00:00');
CREATE TABLE `Products` (
`ProductID` int NOT NULL,
`ProductTitle` VARCHAR(250) NOT NULL,
`ProductType` enum('doors','windows','flooring') NOT NULL
);
INSERT INTO `Products` (`ProductID`, `ProductTitle`, `ProductType`) VALUES
(1, 'French Doors','doors'),
(2, 'Sash Windows','windows'),
(3, 'Sliding Doors','doors'),
(4, 'Parquet Floor','flooring');
SQL Fiddle:
Orders - contains an order date and a user id
LineItems - Foreign key to the orders table, contains product ids that are in the order
Products - Contains details of the products (including if they are a door, window, or flooring)
I have figured out how to get the latest order per user with
SELECT O.* FROM Orders O LEFT JOIN Orders O2
ON O2.UserID=O.UserID AND O.OrderDate < O2.OrderDate
WHERE O2.OrderDate IS NULL;
This works fine and is included in the SQL fiddle, along with a query that returns a complete picture for reference.
I am trying to figure out how to get the latest order with flooring per user, but I'm not having any luck.
In the SQL fiddle linked above, the intended output for what I am after would be
OrderID | UserID | OrderDate
6 | 22 | 2021-05-16T00:00:00Z
5 | 21 | 2021-05-14T00:00:00Z
9 | 24 | 2021-05-23T00:00:00Z
7 | 23 | 2021-05-20T00:00:00Z
EDIT: To clarify, in the intended result, two rows (for users 21 and 23) are different than in the query that gets just latest order per user. This is because order IDs 8 and 10 (from the latest order per user query) do not include flooring. The intended query has to find the latest order with flooring from each user to return in the result set.
You need to add the LineItems and Products tables to your query to find orders where flooring was purchased:
SELECT DISTINCT O.*
FROM Orders O
LEFT JOIN Orders O2
ON O2.UserID=O.UserID AND
O.OrderDate < O2.OrderDate
INNER JOIN LineItems i
ON i.OrderID = O.OrderID
INNER JOIN Products p
ON p.ProductID = i.ProductID
WHERE O2.OrderDate IS NULL AND
p.ProductType = 'flooring'
db<>fiddle here

Balance the values using SQL in PostgreSQL

My Postgresql table looks like,
CREATE TABLE foo(man_id, subgroup, power, grp)
AS VALUES
(1, 'Sub_A', 4, 'Group_A'),
(2, 'Sub_B', -1, 'Group_A'),
(3, 'Sub_A', -1, 'Group_B'),
(4, 'Sub_B', 6, 'Group_B'),
(5, 'Sub_A', 5, 'Group_A'),
(6, 'Sub_B', 1, 'Group_A'),
(7, 'Sub_A', -1, 'Group_B'),
(8, 'Sub_B', 2, 'Group_B'),
(9, 'Sub_C', 2, 'Group_B');
The power calculation works like this:
Total Power of Subgroup Sub_A in the grp Group_A is (4 + 5 ) = 9
Total Power of Subgroup Sub_B in the grp Group_A is ((-1) + 1 ) = 0
Total Power of Subgroup Sub_A in the grp Group_B is ((-1) + (-1) ) = -2
Total Power of Subgroup Sub_B in the grp Group_B is (6 + 2 ) = 8
So the power of Sub_A in the Group_A is not equal to power of Sub_A in the Group_B
So the power of Sub_B in the Group_A is not equal to power of Sub_B in the Group_B
I can query the database and fetch the data where for a same subgroup name total power is not equal across all the other grp names.
SELECT f.*
FROM (
SELECT subgroup
FROM (
SELECT subgroup, grp, sum(power) AS total_power
FROM foo
GROUP BY subgroup, grp
) sub
GROUP BY 1
HAVING min(total_power) <> max(total_power)
) sg
JOIN foo f USING (subgroup);
I also want to make the sum value identical. For a same subgroup name total power should be equal across all the other grp names.
We can fetch the records where sum is not equal from the above query. Then we can find the difference of sum(power) value and add this difference value into the power of any subgroup where power is smaller under that particular grp.
MySQL solution will be accepted as well.
The above query will return this data because for a same subgroup total power is not equal to across grp s,
(1, 'Sub_A', 4, 'Group_A')
(5, 'Sub_A', 5, 'Group_A')
(3, 'Sub_A', -1, 'Group_B')
(7, 'Sub_A', -1, 'Group_B')
(2, 'Sub_B', -1, 'Group_A')
(6, 'Sub_B', 1, 'Group_A')
(4, 'Sub_B', 6, 'Group_B')
(8, 'Sub_B', 2, 'Group_B')
Now, I want to modify the values of power to make the sum identical,
As an example, for Sub_A total power difference between Group_A and Group_B is (9-(-1-1)) = 11 , So we will add 11 into any of the Sub_A power value under Group_B, let's say we modify this record ,
(3, 'Sub_A', -1, 'Group_B') converted to (3, 'Sub_A', 10, 'Group_B')
The same thing we will do for others also, wherever there is an unbalanced.
Below query will produce the desired result
with foo(man_id, subgroup, power, grp) as (
select * from
(
VALUES
(1, 'Sub_A', 4, 'Group_A'),
(2, 'Sub_B', -1, 'Group_A'),
(3, 'Sub_A', -1, 'Group_B'),
(4, 'Sub_B', 6, 'Group_B'),
(5, 'Sub_A', 5, 'Group_A'),
(6, 'Sub_B', 1, 'Group_A'),
(7, 'Sub_A', -1, 'Group_B'),
(8, 'Sub_B', 2, 'Group_B'),
(9, 'Sub_C', 2, 'Group_B')
) as x(man_id, subgroup, power, grp)
), sub_per_group as (
select
subgroup,
grp,
sum(power) tot_per_grp
from foo
group by subgroup,grp
), sub_calc as (
select
subgroup,
max(tot_per_grp) as max,
json_agg(
json_build_object(
'grp',grp,
'tot_per_grp',tot_per_grp
)
) as grps_tot
from sub_per_group
group by subgroup
having count(distinct tot_per_grp)!=1
)
select f.man_id,f.subgroup,
case
when rn=1 then
(
power+
(
coalesce(max,0)-
coalesce((
select (v->>'tot_per_grp')::int
from
json_array_elements(grps_tot) as v where (v->>'grp')::text =f.grp),0)
)
)
else
power
end,
f.grp
from sub_calc sc
right join (
select
row_number() over(partition by subgroup,grp) as rn,
foo.*
from foo
) f on f.subgroup=sc.subgroup and f.rn=1
order by subgroup,grp

SQL - adding total by searching for another value relating to the amount

been given this problem to solve
Write a function to get the payment sales figures from any given shop. The shop should be searched for by name.
i have this so far
SELECT shopname SUM ( amount ) AS sales
FROM frs_Shop, frs_Payment
WHERE shopname = "shop name"
i have the amount to add up but when i go to compile it adds up every amount in this table
INSERT INTO frs_Payment
(payid, amount, paydatetime, empnin, custid, pstatusid, ptid)
values
(101, "3.99", "2015-10-26 16:26:15", "NIN001", 1, 1, 1),
(95, "15.96", "2015-09-24 16:26:15", "NIN001", 1, 1, 1),
(102, "3.99", "2015-10-11 13:25:31", "NIN003", 2, 1, 1),
(11, "11.97", "2015-06-12 19:37:59", "NIN010", 3, 1, 1),
(7, "11.97", "2015-04-11 12:41:28", "NIN010", 3, 2, 4),
(8, "7.98", "2015-05-05 22:49:02", "NIN010", 3, 1, 1),
(32, "15.96", "2015-07-19 02:26:49", "NIN024", 5, 2, 4),
(83, "7.98", "2015-08-20 16:21:08", "NIN011", 5, 2, 4),
(6, "15.96", "2015-03-04 10:51:03", "NIN027", 6, 2, 4),
(17, "3.99", "2015-10-03 01:06:15", "NIN028", 6, 1, 1),
(39, "11.97", "2015-03-24 20:03:05", "NIN027", 6, 1, 1),
(103, "3.99", "2015-10-27 14:45:11", "NIN009", 7, 1, 1),
(62, "15.96", "2015-10-12 14:23:23", "NIN001", 8, 1, 1),
i want it to only add up the ones i specify with the empnin
You can use an explicit join, and a GROUP BY. I made an assumption on the primary/foreign key relationship between frs_Shop and frs_Payment.
SELECT s.shopname
, p.empnin
, SUM ( p.amount ) AS sales
FROM frs_Shop s
INNER JOIN frs_Payment p on s.empnin = p.empnin
WHERE s.shopname = "shop name"
GROUP BY s.shopname, p.empnin

GROUP BY with only first row of sequence of one column?

at first here is the alpha version of what I want: http://sqlfiddle.com/#!2/45c89/2
However I don't want to count all representative_id, but only this rows with the lowest id, eg:
(`id`, `economy_id`, `representative_id`)
(1, 1, 5), <-this one, lowest id through the same economy_id=1
(2, 1, 6),
(3, 1, 7),
(4, 1, 8),
(5, 1, 3),
(6, 1, 4),
(7, 1, 1),
(8, 1, 2),
(9, 1, 102),
(10, 2, 7), <-this one, lowest id through the same economy_id=2
(11, 2, 8),
(12, 2, 102),
(13, 2, 1),
(14, 2, 2),
(15, 2, 3),
(16, 2, 4),
(17, 3, 3), <-this one, lowest id through the same economy_id=3
(18, 3, 4),
(19, 3, 1),
(20, 3, 2),
(21, 3, 102),
(22, 4, 1), <-this one, lowest id through the same economy_id=4
(23, 4, 2),
(24, 4, 102),
(25, 5, 1), <-this one, lowest id through the same economy_id=5
(26, 5, 2),
(27, 5, 102),
(28, 5, 7),
(29, 6, 1), <-this one, lowest id through the same economy_id=6
The output should be:
representative_id, count()
According to above example:
5, 1
7, 1
3, 1
1, 3
Is it possible only in SQL?
If I'm understanding your question correctly, I think this should work using min in a subquery and joining back to itself:
select s.representative_id, count(*)
from stl_parliament s
join
(
select min(id) minid
from stl_parliament
group by economy_id
) t on s.id = t.minid
group by s.representative_id
Updated Fiddle Demo
SELECT x.representative_id
, COUNT(*) total
FROM stl_parliament x
JOIN
( SELECT economy_id
, MIN(id) min_id
FROM stl_parliament
GROUP
BY economy_id
) y
ON y.economy_id = x.economy_id
AND y.min_id = x.id
GROUP
BY representative_id;
http://sqlfiddle.com/#!2/45c89/34
You question is a bit confusing.
is it what do you want?
http://sqlfiddle.com/#!2/a58c7/19
select count(economy_id), min_rep_id
from
(
SELECT economy_id, min(representative_id) as min_rep_id
from stl_parliament
GROUP BY economy_id
) as x
GROUP BY min_rep_id
A little complex but should work :
select m.col1, m.col2, count(m.col2) from (
select t.economy_id as col1, l.representative_id as col2 from stl_parliament l,
(select economy_id,MIN(id) as minid from stl_parliament GROUP BY economy_id) t
where t.economy_id=l.economy_id and t.minid=l.id) m group by m.col2
Lots of subqueries, I am not so good with joins
Please try the below query.
SELECT a.rep_id,count(a.rep_id)
FROM(Select min(id) as mid,representative_id as rep_id,economy_id
FROM stl_parliament
GROUP BY economy_id) as a
GROUP BY a.rep_id