Selecting that contains almost duplicated rows - mysql

I have two tables. price_code table has a foreign key that references on site table.
price_code
___________
priceCodeID
siteID
price
and
site
______________
siteID
operatorName
country
I need to select the rows that, for example, country = "Peru" and have a price = 0 as I want, but in price_code there are some rows that have the same siteID and have the two validations.
I need not consider a row if the siteID has a price different than zero in any register.
SELECT s.siteID, pc1.price, s.country, s.operatorName FROM price_code AS pc1
INNER JOIN site AS s ON s.siteID = pc1.siteID
WHERE country = "Peru"
AND operatorName = "Movistar" AND price = 0
AND pc1.siteID NOT IN (
SELECT siteID FROM price_code WHERE pc1.price <> 0
);
Some data on price_code:
priceCodeID | siteID | price
_____________________________
1000 | 64 | 0
1001 | 64 | 100
1002 | 27 | 0
1003 | 18 | 100
1004 | 17 | 1
And for site
siteID | operatorName | country
___________________________________
64 | Peru | Movistar
27 | Peru | Movistar
18 | Argentina | Movistar
27 | Bolivia | Claro
And my result might be:
siteID | price | country | operatorName
____________________________________________
27 | 0 | Peru | Movistar

Use NOT EXISTS:
SELECT s.siteID, p.price, s.country, s.operatorName
FROM price_code AS p INNER JOIN site AS s
ON s.siteID = p.siteID
WHERE s.country = "Peru" AND s.operatorName = "Movistar" AND p.price = 0
AND NOT EXISTS (
SELECT 1 FROM price_code
WHERE siteID = p.siteID AND price <> 0
)
See the demo.
Results:
> siteID | price | country | operatorName
> -----: | ----: | :------ | :-----------
> 27 | 0 | Peru | Movistar

Your query is almost correct. You need to change WHERE pc1.price_code <> 0 to WHERE price_code <> 0. By using the pc1 prefix you're making this a correlated subquery, so it's testing the row in the main query, rather than filtering rows to be returned by the subquery.
You can make it clearer by adding an alias to the subquery:
AND pc1.siteID NOT IN (
SELECT siteID FROM price_code AS pc2 WHERE pc2.price <> 0
);

Related

MySQL select N latest rows for each product from 3 relational tables

Now i have this code which return latest record for each product. But i don't know how to modify this to get for example 3 latest rows for each product.
I want to compare latest product prices and i need few latest rows of each.
shops
id | shopId
-----------
1 | 2345
2 | 6573
products
id | shopId | title | active | pDateAdded | pDateUpdate
---------------------------------------------------------------------------
18 | 1 | Honda | 1 | 2021-03-07 01:56:34 | 2021-03-07 04:36:34
19 | 2 | Subaru | 1 | 2021-03-07 03:43:34 | 2021-03-08 04:36:34
20 | 1 | VW | 1 | 2021-03-07 07:21:34 | 2021-03-09 04:36:34
21 | 2 | Ford | 0 | 2021-03-07 11:37:34 | 2021-03-10 04:36:34
prices
id | shopId | productId | price | dDateAdded
-----------------------------------------------------
224 | 1 | 18 | 2385 | 2021-03-09 12:39:57
225 | 2 | 19 | 1523 | 2021-03-09 13:14:44
226 | 1 | 20 | 5489 | 2021-03-09 17:32:18
227 | 1 | 18 | 2256 | 2021-03-10 18:22:13
228 | 2 | 19 | 1600 | 2021-03-10 21:33:21
229 | 1 | 20 | 5321 | 2021-03-10 14:15:56
230 | 1 | 18 | 2137 | 2021-03-11 05:55:25
231 | 2 | 19 | 1666 | 2021-03-11 17:31:49
232 | 1 | 20 | 5001 | 2021-03-11 20:18:01
This command return only 1 latest record from prices table for every product from products table for specific shopId
SELECT s.*, c.*, d.*
FROM shops AS s
LEFT JOIN products AS c ON c.shopId = s.id
LEFT JOIN (
SELECT productId, MAX(dDateAdded) MaxDate
FROM prices
GROUP BY productId
) MaxDates
ON MaxDates.productId = c.id
LEFT JOIN prices AS d ON d.productId = c.id AND d.shopId = s.id AND MaxDates.MaxDate = d.dDateAdded
WHERE s.id = ".$shopId."
For example if shopId=1 this command get only that records (I omitted here the data from the other tables that are retrieved):
230 | 1 | 18 | 2137 | 2021-03-11 05:55:25
232 | 1 | 20 | 5001 | 2021-03-11 20:18:01
But i want to get for example 2 latest records for every product where shopId=1, so the records which i want to get:
(shops)id | (shops)shopId | title | active | price | dDateAdded
1 | 2345 | Honda | 1 | 2256 | 2021-03-10 18:22:13
1 | 2345 | Honda | 1 | 2137 | 2021-03-10 14:15:56
1 | 2345 | VW | 1 | 5321 | 2021-03-11 05:55:25
1 | 2345 | VW | 1 | 5001 | 2021-03-11 20:18:01
To select N latest rows needs to allocate row number and to filter by N rows. However, the ROW_NUMBER function is not supported in MySQL 5.7.
So that you need to simulate the ROW_NUMBER function like the follwing:
You can get the desired result by adding subquery with row number to your query like the below:
DB Fiddle
SELECT
s.id,
s.shopId,
c.title,
c.active,
d.price,
d.dDateAdded
FROM shops AS s
LEFT JOIN products AS c ON c.shopId = s.id
LEFT JOIN prices AS d ON d.productId = c.id AND d.shopId = s.id
--
LEFT JOIN (
SELECT
p1.id,
COUNT(p2.dDateAdded) + 1 row_num
FROM prices p1 LEFT JOIN prices p2
ON p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
GROUP BY p1.id, p1.shopId, p1.productId, p1.dDateAdded
) AS w
ON d.id=w.id
--
WHERE
s.id = 1 AND
w.row_num <= 2
DB Fiddle
SELECT
id,
shopId,
productId,
price,
dDateAdded
FROM (
SELECT p1.*,
(
SELECT COUNT(*)+1 FROM prices p2
WHERE
p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
) row_num
FROM prices p1
) p
WHERE
shopId = 1 AND
row_num <= 2
ORDER BY id
DB Fiddle
SELECT p.* FROM prices p
INNER JOIN (
SELECT
p1.id,
COUNT(p2.dDateAdded) + 1 row_num
FROM prices p1 LEFT JOIN prices p2
ON p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
GROUP BY
p1.id,
p1.shopId,
p1.productId,
p1.dDateAdded
) w
ON p.id=w.id
WHERE
p.shopId = 1 AND
w.row_num <= 2
ORDER BY p.id
Other way using a variable

How to select sum of specific id in select query MySQL, Beego

I want to get a result like
result
-------------------------------------------------------
id | uuid | user_id |created_date | amount | name
-------------------------------------------------------
1 | ABC | 1 | 2019/5/1 | 5 | xa
2 | PQR | 2 | 2019/5/5 | 150 | xb
A query that I trying to use
SELECT(SELECT SUM(paid_amount) WHERE ID = t1.**HERE**) AS sub1,
(t1.amount - sub1) AS sub2
FROM invoice t1 CROSS JOIN
invoice_paid t2;
Table struct in my DB
table invoice_paid
------------------------------------
id | uuid | paid_date | paid_amount
------------------------------------
1 | ABC | 2019/5/1 | 15
2 | ABC | 2019/5/5 | 80
table invoice
-------------------------------------------------------
id | uuid | user_id |created_date | amount | name
-------------------------------------------------------
1 | ABC | 1 | 2019/5/1 | 100 | xa
2 | PQR | 2 | 2019/5/5 | 150 | xb
I can use sum only 1 condition like where id = 1 but how do I combine this query in select query with a join query.
I use beego(golang), MariaDB
You can use this query. It JOINs the invoice table to a derived table of SUMs of all the amounts paid per invoice from invoice_paid, subtracting that total from the invoice amount to get the outstanding amount:
SELECT i.id, i.uuid, i.user_id, i.created_date, i.amount - COALESCE(p.amount, 0) AS amount, i.name
FROM invoice i
LEFT JOIN (SELECT uuid, SUM(paid_amount) AS amount
FROM invoice_paid
GROUP BY uuid) p ON p.uuid = i.uuid
ORDER BY i.id
Output:
id uuid user_id created_date name amount
1 ABC 1 2019-05-01 00:00:00 xa 5
2 PQR 2 2019-05-05 00:00:00 xb 150
Demo on dbfiddle

Joining tables but needs 0 for empty rows

I don't know how to explain the scenario using words. So am writing the examples:
I have a table named tblType:
type_id | type_name
---------------------
1 | abb
2 | cda
3 | edg
4 | hij
5 | klm
And I have another table named tblRequest:
req_id | type_id | user_id | duration
-------------------------------------------
1 | 4 | 1002 | 20
2 | 1 | 1002 | 60
3 | 5 | 1008 | 60
....
So what am trying to do is, fetch the SUM() of duration for each type, for a particular user.
This is what I tried:
SELECT
SUM(r.`duration`) AS `duration`,
t.`type_id`,
t.`type_name`
FROM `tblRequest` AS r
LEFT JOIN `tblType` AS t ON r.`type_id` = t.`type_id`
WHERE r.`user_id` = '1002'
GROUP BY r.`type_id`
It might return something like this:
type_id | type_name | duration
-------------------------------
1 | abb | 60
4 | hij | 20
It works. But the issue is, I want to get 0 as value for other types that doesn't have a row in tblRequest. I mean I want the output to be like this:
type_id | type_name | duration
-------------------------------
1 | abb | 60
2 | cda | 0
3 | edg | 0
4 | hij | 20
5 | klm | 0
I mean it should get the rows of all types, but 0 as value for those type that doesn't have a row in tblRequest
You could perform the aggregation on tblRequest and only then join it, using a left join to handle missing rows and coalesce to convert the nulls to 0s:
SELECT t.type_id, type_name, COALESCE(sum_duration, 0) AS duration
FROM tblType t
LEFT JOIN (SELECT type_id, SUM(duration) AS sum_duration
FROM tblRequest
WHERE user_id = '1002'
GROUP BY type_id) r ON t.type_id = r.type_id
Select a.type_id, isnull(sum(b.duration), 0)
From tblType a Left Outer Join tblRequest b
ON a.type_id = b.type_id and b.user_id = 1002
Group by a.type_id

Mind numbing SQL madness

This query runs on an invoices table to help me decide who I need to pay
Here's the base table:
The users table
+---------+--------+
| user_id | name |
+---------+--------+
| 1 | Peter |
| 2 | Lois |
| 3 | Stewie |
+---------+--------+
The invoices table:
+------------+---------+----------+--------+---------------+---------+
| invoice_id | user_id | currency | amount | description | is_paid |
+------------+---------+----------+--------+---------------+---------+
| 1 | 1 | usd | 140 | Cow hoof | 0 |
| 2 | 1 | usd | 45 | Cow tail | 0 |
| 3 | 1 | gbp | 1 | Cow nostril | 0 |
| 4 | 2 | gbp | 1500 | Cow nose hair | 0 |
| 5 | 2 | cad | 1 | eyelash | 1 |
+------------+---------+----------+--------+---------------+---------+
I want a resulting table that looks like this:
+---------+-------+----------+-------------+
| user_id | name | currency | SUM(amount) |
+---------+-------+----------+-------------+
| 1 | Peter | usd | 185 |
| 2 | Lois | gbp | 1500 |
+---------+-------+----------+-------------+
The conditions are:
Only consider invoices that have not been paid, so where is_paid = 0
Group them by user_id, by currency
If the SUM(amount) < $100 for the user_id, currency pair then don't bother showing the result, since we don't pay invoices that are less than $100 (or equivalent, based on a fixed exchange rate).
Here's what I've got so far (not working -- which I guess is because I'm filtering by a GROUP'ed parameter):
SELECT
users.user_id, users.name,
invoices.currency, SUM(invoices.amount)
FROM
mydb.users,
mydb.invoices
WHERE
users.user_id = invoices.user_id AND
invoices.is_paid != true AND
SUM(invoices.amount) >=
CASE
WHEN invoices.currency = 'usd' THEN 100
WHEN invoices.currency = 'gbp' THEN 155
WHEN invoices.currency = 'cad' THEN 117
END
GROUP BY
invoices.currency, users.user_id
ORDER BY
users.name, invoices.currency;
Help?
You can't use SUM in a WHERE. Use HAVING instead.
Use HAVING clause instead of SUM in WHERE condition
Try this:
SELECT u.user_id, u.name, i.currency, SUM(i.amount) invoiceAmount
FROM mydb.users u
INNER JOIN mydb.invoices i ON u.user_id = i.user_id
WHERE i.is_paid = 0
GROUP BY u.user_id, i.currency
HAVING SUM(i.amount) >= (CASE i.currency WHEN 'usd' THEN 100 WHEN 'gbp' THEN 155 WHEN 'cad' THEN 117 END)
ORDER BY u.name, i.currency;
Try something like this:
SELECT
user_id, name, currency, sum(amount) due
FROM
invoice i
JOIN users u ON i.user_id=u.user_id
WHERE
is_paid = 0 AND
GROUP BY user_id, currency
having due >= 100
do you store exchange rates? Multiply rates with amount to get actual amount with respect to base currency.
sum(amount*ex_rate) due

query the database to find what Nth is the record

I don't know how to writ the Title for this question, but what I need is a query that return what is the N record with a specific value.
The table that I have is over 5.2M records
The records are similar to:
session (string, primary indexed)
customer_id (int, indexed)
clicks (int, indexed)
order_number (int, indexed)
date_entry (datetime, indexed)
many other fields
what I need to know is how many times the same customer logged into the site (different sessions) before placing an order (order_number is 0 unless an order is placed during that session)
a sample data can be (simplify data)
session | c_id | clicks | ord_num | entry |
abc | 123 | 2 | 0 | 2012-08-01 00:00:00 |
cde | 456 | 2 | 0 | 2012-08-01 00:00:01 |
efg | 457 | 2 | 0 | 2012-08-01 00:00:02 |
hij | 123 | 5 | 0 | 2012-08-01 00:00:03 |
kod | 986 | 10 | 0 | 2012-08-01 00:00:04 |
wdg | 123 | 2 | 9876 | 2012-08-01 00:00:05 |
qwe | 123 | 2 | 0 | 2012-08-01 00:00:06 |
wvr | 986 | 12 | 8656 | 2012-08-01 00:00:07 |
What I want is a query that shows something similar to:
entry - date entry
tot_sess - total number of session
tot_cust - total number of customers
1sess - customer1 with only one session
2sess - customers with 2 sessions
3sess - customers with 3 sessions
4sess - customers with 4 sessions
more4sess - customers with more than 4 sessions
order1sess - customers that ordered on the first session
order2sess - customers that ordered on the second session
order3sess - customers that ordered on the third session
order4sess - customers that ordered on the fourth session
orderMore4Sess - customers that ordered after the fourth session
entry |tot_sess|tot_cust| 1sess | 2sess | 3sess | 4sess | more4sess | order1sess | order2sess | order3sess | order4sess | orderMore4Sess |
2012-08-01 | 8 | 4 | 2 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
I am already able to get the information about the session with the following query:
SELECT
t.`date_entry`,
COUNT(sess) `cust`,
SUM(sess) `session`,
COUNT(IF(sess>1,sess,NULL)) `more than once`,
COUNT(IF(sess=1,sess,NULL)) `one`,
COUNT(IF(sess=2,sess,NULL)) `two`,
COUNT(IF(sess=3,sess,NULL)) `three`,
COUNT(IF(sess=4,sess,NULL)) `four`,
COUNT(IF(sess>4,sess,NULL)) `more`,
ROUND(COUNT(IF(sess>1,sess,NULL))/COUNT(sess),2) `perc > 1`,
ROUND(COUNT(IF(sess>2,sess,NULL))/COUNT(sess),2) `perc > 2`,
ROUND(COUNT(IF(sess>3,sess,NULL))/COUNT(sess),2) `perc > 3`,
ROUND(COUNT(IF(sess>4,sess,NULL))/COUNT(sess),2) `perc > 4`
FROM
(
SELECT
`customer_id`,
COUNT(`session`) `sess`,
DATE(`date_entry`) `date_entry`
FROM `customer_activity_log`
WHERE
`clicks` > 1
AND `customer_id` > 0
AND `date_entry` > '2012-08-01'
AND subsite_id <=1
GROUP BY `date_entry`, `customer_id`
) t
GROUP BY date_entry
Once I had that I will also need to look at the data in a different way, for example, if customer 123 showed on the first time on 2012-01-01 and then came back 15 times and placed the order on 2012-08-01 and then came back 5 more times and placed another order on 2012-10-12 I will need a query that will not restrain by date but only by customer, in other words the restrain date_entry will be removed
I hope it makes sense
SELECT e AS entry,
SUM(sessions) AS tot_sess,
COUNT(*) AS tot_cust,
SUM(sessions=1) AS 1sess,
SUM(sessions=2) AS 2sess,
SUM(sessions=3) AS 3sess,
SUM(sessions=4) AS 4sess,
SUM(sessions>4) AS more4sess,
SUM(orders =1) AS order1sess,
SUM(orders =2) AS order2sess,
SUM(orders =3) AS order3sess,
SUM(orders =4) AS order4sess,
SUM(orders >4) AS orderMore4Sess
FROM (
SELECT b.e, b.c_id, b.sessions, COUNT(a.entry) AS orders
FROM customer_activity_log a RIGHT JOIN (
SELECT DATE(entry) AS e, c_id, COUNT(*) AS sessions,
MIN(IF(ord_num=0,NULL,entry)) AS o
FROM customer_activity_log
GROUP BY e, c_id
) b ON a.c_id = b.c_id AND DATE(a.entry) = b.e AND a.entry <= b.o
GROUP BY b.e, b.c_id
) t
See it on sqlfiddle.