How to count on join a table with 2 conditions? - sql

I have an items table
id
name
1
Nganu
2
Kae
3
Lho
Also I have an item_usages table:
id
item_id
user_id
usage_time
1
1
99
2021-10-07 00:00:00
2
2
99
2021-10-07 00:00:00
3
1
99
2021-10-08 00:00:00
4
1
22
2021-10-08 00:00:00
5
3
22
2021-10-08 00:00:00
6
1
99
2021-10-08 00:00:00
I want to find an item's total usage and user usage in a query. an example I would like to find user_id 99 usage, expected result:
id
name
total_usage
user_usage
2
Kae
1
1
1
Nganu
4
3
3
Lho
1
0
I tried:
select
"items".*,
count(total_usage.id) as total_usage,
count(user_usage.id) as user_usage
from
"items"
left join
"item_usages" as "total_usage" on "items"."id" = "total_usage"."item_id"
left join
"item_usages" as "user_usage" on "user_usage"."item_id" = "items"."id"
and "user_usage"."user_id" = 99
group by
"items"."id";
but it returns:
id
name
total_usage
user_usage
2
Kae
1
1
1
Nganu
12
12
3
Lho
1
0
item_usages only have 6 rows, why Nganu have 12 on both usage? How to fix my query?
I tried on PostgreSQL 12.8 and 13.4, I also tested on SQLFiddle(PostgreSQL 9.6), Here is the link:
http://sqlfiddle.com/#!17/f1aac/5
I got the query that returned the correct result:
select
"items".*,
min(total_usage.total_count) as total_usage,
count(user_usage.id) as user_usage
from "items"
left join
(select item_id,count(item_id) as total_count from item_usages group by item_id) as total_usage
on "items"."id" = "total_usage"."item_id"
left join "item_usages" as "user_usage"
on "user_usage"."item_id" = "items"."id" and "user_usage"."user_id" = 99
group by "items"."id";
But I don't know about the performance, so I still find faster query if possible and still wondering:
Why does my first query give wrong result?

The reason your query returns high numbers is that you join 2 times.
(From the side of Nganu) The first join will result in 4 rows, the second will map those 4 rows with 3 rows of the same table, resulting in 12 rows.
You can solve this problem with only 1 join:
select "items".id,
count(total_usage.id) as total_usage,
sum(case when total_usage.user_id = 99 then 1 else 0 end) as user_usage
from "items"
left join "item_usages" as "total_usage" on "items"."id" = "total_usage"."item_id"
group by "items".id
And it should work faster (though, on a small dataset is not visible)

Related

MS Access merge two tables

I need to create a new table base on two tables. I have a database in ms access and need to migrate.
tableT tableS
ID CustID DATE Exp1 Exp2 ID CustID DATE Tem1 Tem2
-------------------------------- ---------------------------------
1 1 1/1/00 5 5 1 1 1/1/00 3 4
2 2 1/1/00 1 3 2 2 1/1/00 5 0
3 1 3/1/00 3 2 3 1 5/1/00 0 3
4 3 4/1/00 4 1 4 3 6/1/00 0 0
Desired output table tableNew:
ID CustID DATE Exp1 Exp2 Tem1 Tem2
---------------------------------------------
1 1 1/1/00 5 5 3 4
2 2 1/1/00 1 3 5 0
3 1 3/1/00 3 2
4 3 4/1/00 4 1
5 1 5/1/00 0 3
6 3 6/1/00 0 0
If I use outer join, I will not get the output I need.
Any idea or help.
You want a full join. You can emulate this in MS Access using:
select t.CustID, t.DATE, t.Exp1, t.Exp2, s.tem1, s.tem2
from TableT as t left outer join
tableS as s
on t.CustId = s.CustId and t.date = s.date
union all
select s.CustID, s.DATE, null, null, s.tem1, s.tem2
from tableS as s left outer join
tableT as t
on t.CustId = s.CustId and t.date = s.date
where t.CustId is null;

Issue with SQL Group By and COALESCE on sqlite

I have a table as below in sqlite database. I want to create a line chart showing usage by product groups.
Table: ProductUsageData
UserID ProductName ProductGroup Qty RecordID
1 A1 A 12 1
2 A1 A 12 1
1 A2 A 15 1
3 A1 A 12 2
2 B1 B 12 2
5 B2 B 5 2
1 A1 A 12 3
1 A2 A 15 3
4 A1 A 12 3
3 C1 C 12 3
2 C2 C 15 3
Since I want separate line for each ProductGroup I am using below Query
SELECT
SUM(Qty) as UsedQty,
ProductGroup,
RecordID
FROM ProductUsageData
GROUP BY ProductGroup, RecordID
ORDER BY RecordID ASC;
While I get three records for A (for each RecordID) I get only 1 record each for B & C as they are not used during each RecordID.
Problem is when I am putting one line for each ProductGroup in the chart, the points for B & C are shown as per Qty in the first
My output is like this
A 39 1
A 12 2
B 17 2
A 39 3
C 27 3
So the graph looks like this
instead of
To fix this I changed the query using COALESCE to get 0 Qty if the ProductGroup is not used during each recording.
SELECT
COALESCE(SUM(Qty), 0) as UsedQty,
ProductGroup,
RecordID
FROM ProductUsageData
GROUP BY ProductGroup, RecordID
ORDER BY RecordID ASC;
I was expecting output as below
A 39 1
B 0 1
C 0 1
A 12 2
B 17 2
C 0 2
A 39 3
B 0 3
C 27 3
But I am getting same output as first
Please let me know how can I correct the query to get desired output
A typical solution is to first cross join two queries that select the distinct product groups and record ids from the table; this gives you all possible combinations of productGroup and recordID.
Then, you can bring in the original table with a left join, and aggregate:
select
g.productGroup,
coalesce(sum(p.qty), 0) qty,
r.recordID
from (select distinct productGroup from productUsageData) g
cross join (select distinct recordID from productUsageData) r
left join productUsageData p
on p.productGroup = g.productGroup
and p.recordID = r.recordID
group by r.recordID, g.productGroup
order by r.recordID, g.productGroup
In the real world, you might have separate referential tables for product groups and records ids, which would make the query simpler and more efficient (since it would avoid the need to select distinct in subqueries).
Demo on DB Fiddle:
productGroup | qty | recordID
:----------- | :-- | :-------
A | 39 | 1
B | 0 | 1
C | 0 | 1
A | 12 | 2
B | 17 | 2
C | 0 | 2
A | 39 | 3
B | 0 | 3
C | 27 | 3

SQL Query doesn't return nulls in group by/aggregate?

I have this SQL query that returns a sum of a product sku per day, per store. It skips stores/names that don't return any values.
Currently the output is:
day location sku totalSold
1/1/18 1 1 2
1/2/18 2 2 1
1/4/18 1 4 3
I want the output to be more like:
day location sku totalSold
1/1/18 1 1 2
1/1/18 1 2 0
1/1/18 1 3 0
1/1/18 1 4 3
1/1/18 2 1 2
1/1/18 2 2 0
1/1/18 2 3 0
1/1/18 2 4 0
1/2/18 1 1 2
1/2/18 1 2 0
1/2/18 1 3 0
This is my current sql query:
SELECT tb1.timestamp, tb1.store, tbl2.sku, SUM(CAST(tbl2.quantity as integer)) as 'totalSold'
FROM tb1
LEFT JOIN tbl2 on tbl1.id = tbl2.id
WHERE tbl2.sku IN (select sku from tbl3) --tbl3 is a view w/ list of skunames
GROUP BY tbl1.Timestamp, YEAR(timestamp), MONTH(timestamp), DAY(timestamp), tbl1.store, tbl2.sku
It was answered somewhat in the comments, but for future readers, here is the script you would use and why it is important to do that.
SELECT tb1.timestamp, tb1.store, tbl2.sku, SUM(CAST(tbl2.quantity as integer)) as 'totalSold'
FROM tb1
LEFT JOIN tbl2 on tbl1.id = tbl2.id AND tbl2.sku IN (select sku from tbl3) --tbl3 is a view w/ list of skunames
GROUP BY tbl1.Timestamp, YEAR(timestamp), MONTH(timestamp), DAY(timestamp), tbl1.store, tbl2.sku
The original query forced the LEFT JOIN to be treated like an INNER JOIN because only records returned by the join could be evaluated for the WHERE clause conditions. By moving the section starting with tbl2.sku into the LEFT JOIN, the conditions previously in the WHERE clause get evaluated on the table before the join is evaluated (this may not be technically correct, but it is how it appears to us).

filling running total over all month although its null

I have 2 tables. One only with all periods. Second with Account, Amount and period.
I want to build a View that lists Amount kumulated, period and account. Also if I don't have an fact for a period in my table should be appear the period in my view with the last amount.
select distinct
account, b.periode,
SUM(amount) OVER (PARTITION BY account ORDER BY b.periode RANGE UNBOUNDED PRECEDING)
from
fakten a
full join
perioden b on a.periode = b.periode
order by b.periode
it like this:
1 1 6
2 1 4
1 2 13
2 2 3
NULL 3 NULL
1 4 46
2 5 48
NULL 6 NULL
NULL 7 NULL
1 8 147
NULL 9 NULL
NULL 10 NULL
NULL 11 NULL
NULL 12 NULL
I need it like:
1 1 6
2 1 4
1 2 13
2 2 3
1 3 13
2 3 3
1 4 46
2 4 3
1 5 46
2 5 48
1 6 46
2 6 46
and so one...
Any ideas?
full join is not the right approach. Instead, generate the rows you want using a cross join. Then use left join and group by to do the calculation.
select a.account, p.periode,
SUM(f.amount) OVER (PARTITION BY a.account ORDER BY p.periode)
from (select distinct account from fakten) a cross join -- you probably have an account table, use it
perioden p
on a.periode = p.periode left join
fakten f
on f.account = a.account and f.periode = p.periode
group by a.account, p.periode
order by a.account, p.periode;

Query for max to_date for one user id?

I am getting some unexpected results from a SQL query.
Table data:
users:
id username
1 admin
2 x1
3 y1
4 z1
my_connections:
id user_id friend_user_id status
1 1 2 201
2 2 1 201
3 2 4 201
4 1 3 200
5 2 3 201
6 3 2 201
7 4 2 201
8 4 1 200
jobs:
id user_id company_name designation from_date to_date
1 1 A 1 2011-06-01 2011-07-30
2 1 B 11 2011-08-02 2014-01-20
3 2 c 12 2012-05-02 2014-01-20
4 3 D 13 2010-05-02 2014-01-20
5 4 E 11 2009-05-25 2014-01-01
Here is my query:
SELECT users.id,users.username,my_connections.user_id,my_connections.friend_user_id,my_connections.status,jobs.user_id,jobs.company_name,
jobs.designation,jobs.from_date,MAX(jobs.to_date)
FROM users
LEFT JOIN jobs ON jobs.user_id = users.id
LEFT JOIN my_connections ON my_connections.friend_user_id = users.id
WHERE my_connections.status = 201 AND users.id IN (1,3,4)
GROUP BY jobs.company_name
ORDER BY jobs.to_date DESC
And the results:
id username user_id friend_user_id status user_id company_name designs from_date to_date
3 .. 2 3 201 3 D .. 2010-05-02 2014-01-20
4 .. 2 4 201 4 E .. 2009-05-25 2014-01-01
1 .. 2 1 201 1 A .. 2011-08-02 2014-01-20
1 .. 2 1 201 1 B .. 2011-06-01 2011-07-30
In the result, I wanted one row per friend_user_id, with the maximum value of to_date. Instead I am getting multiple rows (if there are multiple rows in the jobs table).
How can I fix this query?
if you want unique results on the friend_user_id field you must group by friend_user_id. This will guarantee unique results on the friend_user_id column. But im pretty sure you don't want that because it may show incorrect data. I am still unsure how the query is working because the group by only contains one field. You must group by all the raw fields in the select query and perform aggregate functions on fields that are not in the group by clause for example:
SELECT users.id,users.username,my_connections.user_id,my_connections.friend_user_id,my_connections.status,jobs.user_id,jobs.company_name,
jobs.designation,jobs.from_date,MAX(jobs.to_date)
FROM users
LEFT JOIN jobs ON jobs.user_id = users.id
LEFT JOIN my_connections ON my_connections.friend_user_id = users.id
WHERE my_connections.status = 201 AND users.id IN (1,3,4)
GROUP BY users.id,users.username,my_connections.user_id,my_connections.friend_user_id,my_connections.status,jobs.user_id,jobs.company_name,
jobs.designation,jobs.from_date
ORDER BY jobs.to_date DESC
In this query all of the fields in the group by clause are in the select clause. Now all the fields not included in the group by clause can use functions like: MAX(), AVG(), SUM() etc.