Related
I'd like to receive a table from my database but I am unable to form the query.
This is what I like to achieve: Think of a group of users who shall be invited to an event.
To find a date A list of dates are provided by the host.
So far we have these tables:
Users:
Id
Name
7
Sally
2
Bob
3
John
4
Lisa
5
Joe
6
Jane
Events
Id
Name
1
Birthdayparty
2
Barbecue
3
Dinner
Event Users:
Id
UserId
EventId
1
7
1
(Sally is invited to bp)
2
2
1
(Bob, too)
3
4
1
(and Lisa)
4
1
2
(Sally is invited. to Bbe)
5
5
2
(also Joe)
6
4
2
(and Lisa)
So far for the structure of main parts of the db.
Now lets put some possible dates for the birthday party
EventProposal:
Id
Event
Date
1
1
5. March
Birthday dates
2
1
6. March
3
1
8. March
4
1
10. March
5
3
4. April
Dinner
6
3
5. April
Now the last table holds which user selected which dates for an event.
EventProposalSelection:
Id
EventId
UserId
DateId
1
1
1
1
Sally selected 5. March for birthday
2
1
1
2
Sally selected 6. March for birthday
3
1
1
3
Sally selected 8. March for birthday
4
1
2
2
Bob selected 6. March for birthday
5
1
2
3
Bob selected 8. March for birthday
6
1
4
1
Lisa selected 5. March for birthday
7
1
4
2
Lisa selected 6. March for birthday
8
1
4
4
Lisa selected 10. March for birthday
What I like to know is if a user has picked a date for an event.
I want to see all dates of a specific event for a specific user
(where clause contains userId and eventId)
If I ask for Sally in combination of Birthday
(where userId = 1 and eventId = 1)
I'd like to receive
DateId
Checked
1
true
2
true
3
false
4
true
The tables are properly constrained and related to each other
How can I achieve this in MySQL?
EDIT:
select
e.Name EventName,
e.id,
dp.DateProposal DateOfEvent,
coalesce( u.Name, '' ) GuestName,
-- due to left-join, see if the date is chosen or NULL (not chosen)
case when dps.dateid IS NULL then false else true end DateWasPicked
from
-- start with events
Event e
-- Now, what dates were birthday parties POSSIBLE to attend
JOIN EventProposal dp
on e.id = dp.EventId
-- NOW, what dates by given users were selected
-- BUT, since not all dates may be selected, do a LEFT JOIN
LEFT JOIN EventProposalSelection dps
on dp.id = dps.dateid
-- and finally who selected the given date
-- and again LEFT-JOIN since a user may not pick all dates
LEFT JOIN User u
on dps.userid = u.id
-- prevent getting ALL users for an event
AND u.Id = 7 --Sally
where
-- but only birthday parties
e.Id = 1
-- Tricky here because you want ALL POSSIBLE event dates,
-- but ONLY those for Sally
AND ( dps.dateid IS NULL OR u.Id = 7 )
order by
-- and suggest you actually use datetime based column
-- as you can use functions to get the format of the date.
dp.DateProposal
Lead to
which seems to be fine, but when running for Bob (UserId = 2)
there is a date missing
And running for John (UserId = 3)
Ok, so lets take this one step at a time. You are interested in a SPECIFIC event, and all POSSIBLE dates FOR said event. Then, based on a specific user, if they had (or not) picked any of the possible dates. And by the user ID, get the name too.
Sample data. Your version of data had the sample selections with Sally's ID of 1, not 7. So this is a sample set I ran with using 7 as the basis for Sally
create table users ( id integer, name varchar(10))
insert into users ( id, name ) values
( 7, 'Sally' ),
( 2, 'Bob' ),
( 3, 'John' ),
( 4, 'Lisa' ),
( 5, 'Joe' ),
( 6, 'Jane' )
create table Events ( id int, name varchar(15))
insert into Events (id, name ) values
( 1, 'Birthdayparty' ),
( 2, 'BBQ' ),
( 3, 'Dinner' )
create table EventUsers ( id int, userid int, eventid int )
insert into EventUsers ( id, userid, eventid ) values
( 1, 7, 1 ),
( 2, 2, 1 ),
( 3, 4, 1 ),
( 4, 1, 2 ),
( 5, 5, 2 ),
( 6, 4, 2 )
create table EventProposal (id int, event int, date datetime )
insert into EventProposal ( id, event, date ) values
( 1, 1, '2022-03-05' ),
( 2, 1, '2022-03-06' ),
( 3, 1, '2022-03-08' ),
( 4, 1, '2022-03-10' ),
( 5, 3, '2022-04-04' ),
( 6, 3, '2022-04-05' )
create table EventProposalSelection ( id int, eventid int, userid int, DateID int )
insert into EventProposalSelection ( id, eventid, userid, dateid ) values
( 1, 1, 7, 1 ),
( 2, 1, 7, 2 ),
( 3, 1, 7, 3 ),
( 4, 1, 2, 2 ),
( 5, 1, 2, 3 ),
( 6, 1, 4, 1 ),
( 7, 1, 4, 2 ),
( 8, 1, 4, 4 )
select
AllEventDates.id,
AllEventDates.EventName,
AllEventDates.DateOfEvent,
u.id UserID,
coalesce( u.Name, '' ) GuestName,
-- due to left-join, see if the date is chosen or NULL (not chosen)
case when eps.dateid IS NULL
then 'false' else 'true' end DateWasPicked
from
Users u
-- this query get all events and all possible dates regardless
-- of who may have supplied a selection to attend
JOIN
( select
e.id,
e.Name EventName,
ep.id EventProposalID,
ep.date DateOfEvent
from
Events e
JOIN EventProposal ep
on e.id = ep.Event
where
-- but only birthday parties
e.Name = 'Birthdayparty' ) AllEventDates
on 1=1
-- NOW, left join for a given one person
LEFT JOIN EventProposalSelection eps
on eps.userid = u.id
AND AllEventDates.EventProposalID = eps.dateid
-- and finally who selected the given date
-- and again LEFT-JOIN since a user may not pick all dates
where
u.id = 7
order by
-- and suggest you actually use datetime based column
-- as you can use functions to get the format of the date.
AllEventDates.DateOfEvent
I have the following table
customer_id
id
product_type
serial_number
parent_prod_id
123
200
Camera
3222333
200
123
201
InstaCam
3322322
200
123
202
InstaCam
4332233
200
125
200
Camera
3222333
200
126
200
Camera
3222333
200
My query should return the customer count for each product type but if the same customer purchased a product such as InstaCam which is tied to the parent prod id Camera, then the customer count for the product InstaCam must be 0. In the above table, Camera was purchased by three different customers with customer ids 123, 125 and 126. Since InstaCam was also purchased by one of the customers who purchased the Camera and because the parent_prod_id of InstaCam is the same as the id of Camera, the same customer should not be counted again for the Instacam product so the customer count would be 0.
Expected output:
serial_number
product_type
customer_count
3222333
Camera
3
3322322
InstaCam
0
4332233
InstaCam
0
I have tried many solutions for hours with no luck. Any help would be greatly appreciated. Thank you.
This must work. Basically what this query does is sum the cases valid for your requirements.
These cases are:
The product is a parent
The product is a child but there is not a buy for the parent
Else => 0 (not sum)
Then, with this clasification, you can add the occurrences.
select d.serial_number, d.product_type, sum(counter) as customer_count
from (
select *,
case
when y.id = y.parent_prod_id then 1
when not exists (
select 1
from your_data yy
where y.customer_id=yy.customer_id
and yy.id = y.parent_prod_id
) then 1
else 0
end counter
from your_data y
) d
group by d.serial_number, d.product_type
You can test on this <>db_fiddle
You can do it with simple join and conditional aggregation.
Schema and insert statements:
create table yourtable(customer_id int, id int, product_type varchar(50), serial_number int, parent_prod_id int);
insert into yourtable values(123,200, 'Camera', 3222333,200);
insert into yourtable values(123,201, 'InstaCam', 3322322,200);
insert into yourtable values(123,202, 'InstaCam', 4332233,200);
insert into yourtable values(125,200, 'Camera', 3222333,200);
insert into yourtable values(126,200, 'Camera', 3222333,200);
Query:
select a.serial_number, a.product_type,sum(case when a.id=b.id then 1 else 0 end)customer_count
from yourtable a
left join yourtable b on a.parent_prod_id=b.id and a.customer_id=b.customer_id
group by a.serial_number, a.product_type
Output:
serial_number
product_type
customer_count
3222333
Camera
3
3322322
InstaCam
0
4332233
InstaCam
0
db<>fiddle here
To solve this, you will need to join the table to itself and compare sales.
First let's make the table and populate it with the supplied data:
DROP TABLE IF EXISTS `Sales`;
CREATE TABLE IF NOT EXISTS `Sales` (
`customer_id` int(11) UNSIGNED NOT NULL ,
`id` int(11) UNSIGNED NOT NULL ,
`product_type` varchar(80) NOT NULL DEFAULT '',
`serial_number` varchar(40) NOT NULL DEFAULT '',
`parent_prod_id` int(11) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `Sales` (`customer_id`, `id`, `product_type`, `serial_number`, `parent_prod_id`)
VALUES (123, 200, 'Camera', '3222333', 200),
(123, 201, 'InstaCam', '3322322', 200),
(123, 202, 'InstaCam', '4332233', 200),
(125, 200, 'Camera', '3222333', 200),
(126, 200, 'Camera', '3222333', 200);
To get the results you seek, we can use a query like this:
SELECT s.`serial_number`, s.`product_type`,
COUNT(DISTINCT CASE WHEN pp.`id` IS NOT NULL THEN NULL ELSE s.`customer_id` END) as `customer_count`
FROM `Sales` s LEFT OUTER JOIN `Sales` pp ON s.`customer_id` = pp.`customer_id`
AND s.`parent_prod_id` = pp.`id`
AND s.`id` <> pp.`id`
GROUP BY s.`serial_number`, s.`product_type`;
This will give you a result like this:
serial_number
product_type
customer_count
3222333
Camera
3
3322322
InstaCam
0
4332233
InstaCam
0
Now to test this, let's add a record for a customer who bought only an InstaCam:
INSERT INTO `Sales` (`customer_id`, `id`, `product_type`, `serial_number`, `parent_prod_id`)
VALUES (131, 201, 'InstaCam', '3322322', 200);
Run the same query as before, and you'll get this:
serial_number
product_type
customer_count
3222333
Camera
3
3322322
InstaCam
1
4332233
InstaCam
0
Next time I answer a question, I'll make sure I have a cup of coffee first 🤪
You can use distinct on customer_id such as SELECT count(distinct customer_id) so to count a customer only once.
https://www.mysqltutorial.org/mysql-distinct.aspx
I am new to Sql, and need some guidance to create a Trial Balance via Sql query in MySql.
Consider the following scenario:
Two Tables:
Accounts
Transactions
Accounts Table fields details:
AccNo (PK)(varchar) (5)
AccName (varchar)(50)
AccOpBal (double)
Transactions Table fields details:
TransID (int) (Auto Increment) (PK)
AccNo (varchar) (5)
TransDt (DateTime)
TransDebit (Double)
TransCredit (Double)
Now I need a SQL query based on transDt date range(for e.g 01st Jan-14 to 31sth Jan-2014) which will return:
AccNo
AccOpBal
TransDebit (Sum of monthly transaction i.e Jan-2014)
TransCredit (Sum of monthly transaction i.e Jan-2014)
TransDebit (Sum of Yearly transaction i.e from 01st July-2013 to 31st Jan 2014 or YTD)
TransCredit (Sum of Yearly transaction i.e from 01st July-2013 to 31st Jan 2014 or YTD)
It is not necessary that every AccNo has opening balance (AccOpBal), likewise, it is also not necessary that every AccNo has transactions (TransDebit or TransCredit). But if an AccNo has any, it should be in query.
UPDATE Picture of sample trial added
You could achieve that result with a select over a union of two queries, one for the month to date and one for the year to date figures.
select accno, accopbal, sum(mtd_d), sum(mtd_c), sum(ytd_d),sum(ytd_c)
from
( select ao.accno
, ao.accOpBal
, 0 as mtd_d
, 0 as mtd_c
, 0 as ytd_d
, 0 as ytd_c
from accounts ao
left outer join transactions tn on tn.accno = ao.accno
where tn.accno is null
union
select tm.accno
, a.accOpBal
, sum(tm.transdebit) as mtd_d
, sum(tm.transcredit) as mtd_c
, 0 as ytd_d
, 0 as ytd_c
from accounts a
right outer join transactions tm on tm.accno = a.accno
where tm.transdt between '2014-01-01' and '2014-01-31'
group by a.accno, a.accopbal
union
select ty.accno
, a.accOpBal
, 0
, 0
, sum(ty.transdebit)
, sum(ty.transcredit)
from accounts a
right outer join transactions ty on ty.accno = a.accno
and ty.transdt between '2013-07-01' and '2014-01-31'
group by a.accno, a.accopbal
) alltxn
group by accno, accopbal
Here is a sqlfiddle with a small test set
and here is the testset:
-- january
insert into transactions values (1, 'alfki', '2014-01-01', 1,3);
insert into transactions values (1, 'alfki', '2014-01-02', 1,3);
insert into transactions values (1, 'alfki', '2014-01-03', 1,3);
-- last year
insert into transactions values (1, 'alfki', '2013-09-01', 5,2);
-- txn without acc
insert into transactions values (1, 'noexi', '2014-01-03', 4,2);
-- acc with txn
INSERT INTO Accounts values ( 'alfki', 'alfred', 4);
-- acc without txn
INSERT INTO Accounts values ( 'lefto', 'lefto', 6);
with the following query result:
ACCNO | ACCOPBAL |SUM(MTD_D)|SUM(MTD_C)|SUM(YTD_D)|SUM(YTD_C)
------+----------+----------+----------+----------+-----------
alfki | 4 | 3 | 9 | 8 | 11
lefto | 6 | 0 | 0 | 0 | 0
noexi | (null) | 4 | 2 | 4 | 2
I have a list of product IDs and I want to find out which orders contain all those products. Orders table is structured like this:
order_id | product_id
----------------------
1 | 222
1 | 555
2 | 333
Obviously I can do it with some looping in PHP but I was wondering if there is an elegant way to do it purely in mysql.
My ideal fantasy query would be something like:
SELECT order_id
FROM orders
WHERE (222,555) IN GROUP_CONCAT(product_id)
GROUP BY order_id
Is there any hope or should I go read Tolkien? :) Also, out of curiosity, if not possible in mysql, is there any other database that has this functionality?
You were close
SELECT order_id
FROM orders
WHERE product_id in (222,555)
GROUP BY order_id
HAVING COUNT(DISTINCT product_id) = 2
Regarding your "out of curiosity" question in relational algebra this is achieved simply with division. AFAIK no RDBMS has implemented any extension that makes this as simple in SQL.
I have a preference for doing set comparisons only in the having clause:
select order_id
from orders
group by order_id
having sum(case when product_id = 222 then 1 else 0 end) > 0 and
sum(case when product_id = 555 then 1 else 0 end) > 0
What this is saying is: get me all orders where the order has at least one product 222 and at least one product 555.
I prefer this for two reasons. The first is generalizability. You can arrange more complicated conditions, such as 222 or 555 (just by changing the "and" to and "or"). Or, 333 and 555 or 222 without 555.
Second, when you create the query, you only have to put the condition in one place, in the having clause.
Assuming your database is properly normalized, i.e. there's no duplicate Product on a given Order
Mysqlism:
select order_id
from orders
group by order_id
having sum(product_id in (222,555)) = 2
Standard SQL:
select order_id
from orders
group by order_id
having sum(case when product_id in (222,555) then 1 end) = 2
If it has duplicates:
CREATE TABLE tbl
(`order_id` int, `product_id` int)
;
INSERT INTO tbl
(`order_id`, `product_id`)
VALUES
(1, 222),
(1, 555),
(2, 333),
(1, 555)
;
Do this then:
select order_id
from tbl
group by order_id
having count(distinct case when product_id in (222,555) then product_id end) = 2
Live test: http://www.sqlfiddle.com/#!2/fa1ad/5
CREATE TABLE orders
( order_id INTEGER NOT NULL
, product_id INTEGER NOT NULL
);
INSERT INTO orders(order_id,product_id) VALUES
(1, 222 ) , (1, 555 ) , (2, 333 )
, (3, 222 ) , (3, 555 ) , (3, 333 ); -- order#3 has all the products
CREATE TABLE products AS (SELECT DISTINCT product_id FROM orders);
SELECT *
FROM orders o1
--
-- There should not exist a product
-- that is not part of our order.
--
WHERE NOT EXISTS (
SELECT *
FROM products pr
WHERE 1=1
-- extra clause: only want producs from a literal list
AND pr.product_id IN (222,555,333)
-- ... that is not part of our order...
AND NOT EXISTS ( SELECT *
FROM orders o2
WHERE o2.product_id = pr.product_id
AND o2.order_id = o1.order_id
)
);
Result:
order_id | product_id
----------+------------
3 | 222
3 | 555
3 | 333
(3 rows)
im having difficulty with the following fairly simple setup:
CREATE TABLE IF NOT EXISTS invoices (
id int(11) NOT NULL auto_increment,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS invoices_items (
id int(11) NOT NULL auto_increment,
invoice_id int(11) NOT NULL,
description text NOT NULL,
amount decimal(10,2) NOT NULL default '0.00',
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS invoices_payments (
id int(11) NOT NULL auto_increment,
invoice_id int(11) NOT NULL,
amount decimal(10,2) NOT NULL default '0.00',
PRIMARY KEY (id)
);
some data:
INSERT INTO invoices (id) VALUES (1);
INSERT INTO invoices_items (id, invoice_id, description, amount) VALUES
(1, 1, 'Item 1', '750.00'),
(2, 1, 'Item 2', '750.00'),
(3, 1, 'Item 3', '50.00'),
(4, 1, 'Item 4', '150.00');
INSERT INTO invoices_payments (id, invoice_id, amount) VALUES
(1, 1, '50.00'),
(2, 1, '1650.00');
and the sql yielding unusual results:
select invoices.id,
ifnull(sum(invoices_payments.amount),0) as payments_total,
ifnull(count(invoices_items.id),0) as item_count
from invoices
left join invoices_items on invoices_items.invoice_id=invoices.id
left join invoices_payments on invoices_payments.invoice_id=invoices.id
group by invoices.id
results in the (erroneous) output
id payments_total item_count
1 6800.00 8
now, as evidenced by there being infact only four 'invoice_item' rows, i dont understand why mysql is not grouping properly.
EDIT
i know i can do something like this:
select x.*, ifnull(sum(invoices_payments.amount),0) as payments_total from (
select invoices.id,
ifnull(count(invoices_items.id),0) as item_count
from invoices
left join invoices_items on invoices_items.invoice_id=invoices.id
group by invoices.id
) as x left join invoices_payments on invoices_payments.invoice_id=x.id
group by x.id
but i want to know if im doing something wrong in the first query - i cant immediately see why the first query is giving incorrect results! :(
Your join logic is incorrect. In your join, you specify invoices_items.invoice_id = invoices.id. You also specify invoices_payments.invoice_id = invoices.id. Because of transitivity, you end up with:
invoices_items.invoice_id = invoices.id
invoices_payments.invoice_id = invoices.id
invoice_items.invoice_id = invoices_payments.invoice_id
The sum of the 2 invoice payments is $1700. For every invoice payment, there are 4 invoice_items that satisfy the above relations. $1700 * 4 = $6800.
For every invoice item, there will be two invoice payments that satisfy the above relations. 4 invoice items * 2 = 8 count.
There are two tables with a many:one relationship with invoices. Your count is the cartesian product.
The payments should be applied to the invoice, not the invoice items. Get the invoice total first, then join the payments to it.
This may be similar to what you are looking for:
SELECT
invoice_total.invoice_id,
invoice_total.amount as invoice_amount,
payments_total.amount as total_paid
FROM
(
SELECT
invoice_id,
SUM(amount) as amount
FROM
invoices_items
GROUP BY
invoice_id
) invoice_total
INNER JOIN
(
SELECT
invoice_id,
SUM(amount) as amount
FROM
invoices_payments
GROUP BY
invoice_id
) payments_total
ON invoice_total.invoice_id = payments_total.invoice_id;
edit:
ah, sorry - see your point now. The reason you're getting unexpected results is that this query:
SELECT *
FROM invoices
LEFT JOIN invoices_items ON invoices_items.invoice_id = invoices.id
LEFT JOIN invoices_payments ON invoices_payments.invoice_id = invoices.id;
results in this:
id id invoice_id description amount id invoice_id amount
1 1 1 Item 1 750.00 1 1 50.00
1 1 1 Item 1 750.00 2 1 1650.00
1 2 1 Item 2 750.00 1 1 50.00
1 2 1 Item 2 750.00 2 1 1650.00
1 3 1 Item 3 50.00 1 1 50.00
1 3 1 Item 3 50.00 2 1 1650.00
1 4 1 Item 4 150.00 1 1 50.00
1 4 1 Item 4 150.00 2 1 1650.00
As you can see you get every invoices_items record once each for every invoices_payments record. You're going to have to grab (i.e. group) them separately.
Note that the GROUP BY clause in your initial query is redundant.
Here's what you need:
SELECT
invoices.id,
payments_total.payments_total,
IFNULL(COUNT(invoices_items.id),0) AS item_count
FROM invoices
LEFT JOIN invoices_items ON invoices.id = invoices_items.invoice_id
LEFT JOIN (
SELECT invoice_id,
IFNULL(SUM(invoices_payments.amount),0) AS payments_total
FROM invoices_payments
GROUP BY invoice_id
) AS payments_total ON invoices.id = payments_total.invoice_id
;