How to count records using an l-tree - sql

I have two tables, tickets and categories. The categories table has 3 columns of interest: id, name and path. The data looks like this:
id | Name | Path
------------------
1 | ABC | 1
2 | DEF | 1.2
3 | GHI | 1.2.3
4 | JKL | 4
5 | MNO | 4.5
6 | PQR | 4.5.6
9 | STU | 4.5.9
Note that the path column is an l-tree. What this is meant to represent is that the category with id=2 is a subcategory of id=1 and that id=3 is a subcategory of id=2.
In my tickets table, there's a column called category_id which refers to the id column in my categories table. Each ticket can have up to one category assigned to it (category_id may be null).
I'm trying to count all the tickets for each category.
Suppose my tickets table looks like this:
ticket_id | ticket_title | category_id
1 | A | 1
2 | B | 2
3 | C | 3
4 | D | 5
5 | F | 5
6 | G | 6
7 | H | 9
I would like to output:
category_id | count
1 | 3
2 | 2
3 | 1
4 | 4
5 | 4
6 | 1
9 | 1
I've found that I can get all of the tickets which belong to a given category with the following query: select * from tickets where category_id in (select id from categories where path ~ '*.1.*'); (although now that I'm writing this question I'm not convinced this is correct).
I've also attempted to perform the ticket-count-by-category problem and I came up with:
SELECT
categories.id as cid,
COUNT(*) as tickets_count
FROM tickets
LEFT JOIN categories ON tickets.category_id = categories.id
GROUP BY cid;
which outputs the following:
c_id | count
1 | 1
2 | 1
3 | 1
5 | 2
6 | 1
9 | 1
I'm not very good at SQL. Is it possible to achieve what I want?

Try this:
WITH tickets_per_path AS (
SELECT
c.path AS path,
count(*) AS count
FROM tickets t INNER JOIN categories c ON (t.category_id = c.id)
GROUP BY c.path)
SELECT
c.id,
sum(tickets_per_path.count) AS count
FROM categories c LEFT JOIN tickets_per_path ON (c.path #> tickets_per_path.path)
GROUP BY c.id
ORDER BY c.id;
Which yields the following result:
id| count
1 | 3
2 | 2
3 | 1
4 | 4
5 | 4
6 | 1
9 | 1
It roughly works like this:
the WITH clause computes the number of tickets per path (without
including the count of tickets of descendent paths).
the second select clause joins the categories table with the precomputed tickets_per_path view, but instead of an equi-join on path, it
joins by testing whether a record in the left table (categories) is
an ancestor of the right side table (using #> operator). Then it
groups by category id and sums up the ticket counts by category
including the descendant counts.

You are close, but you need a more general join:
SELECT c.id as cid, COUNT(*) as tickets_count
FROM categories c LEFT JOIN
tickets t
ON t.category_id || '.' LIKE c.id || '.%'
GROUP BY c.id;
The '.' in the comparison is just so 1.100 doesn't match 1.1.

Related

SQL - IN clause with no match

I'm trying to build a query where I can select from a table all products with a certain ID but I would also like to find out what products were not found within the IN clause.
Product Table
ID | Name
---|---------
1 | ProductA
2 | ProductB
4 | ProductD
5 | ProductE
6 | ProductF
7 | ProductG
select *
from products
where id in (2,3,7);
As you can see, product id 3 does not exist in the table.
My query will only return rows 2 and 7.
I would like a blank/null row returned if a value in the IN clause did not return anything.
Desired Results:
ID | Name
---|---------
2 | ProductB
3 | null
7 | ProductG
You can use a left join:
select i.id, p.name
from (select 2 as id union all select 3 union all select 7
) i left join
products p
on p.id = i.id
IN is not useful in this case.
Use a CTE with the ids that you want to search for and left join to the table:
with cte(id) as (select * from (values (2),(3),(7)))
select c.id, p.name
from cte c left join products p
on p.id = c.id
See the demo.
Results:
| id | Name |
| --- | -------- |
| 2 | ProductB |
| 3 | |
| 7 | ProductG |

Find duplicate combinations

I need a query to find duplicate combinations in these tables:
AttributeValue:
id | name
------------------
1 | green
2 | blue
3 | red
4 | 100x200
5 | 150x200
Product:
id | name
----------------
1 | Produkt A
ProductAttribute:
id | id_product | price
--------------------------
1 | 1 | 100
2 | 1 | 200
3 | 1 | 100
4 | 1 | 200
5 | 1 | 100
6 | 1 | 200
7 | 1 | 100 -- duplicate combination
8 | 1 | 100 -- duplicate combination
ProductAttributeCombinations:
id_product_attribute | id_attribute
-------------------------------------
1 | 1
1 | 4
2 | 1
2 | 5
3 | 2
3 | 4
4 | 2
4 | 5
5 | 3
5 | 4
6 | 3
6 | 5
7 | 1
7 | 4
8 | 1
8 | 5
I need SQL that creates result like:
id_product | duplicate_attributes
----------------------------------
1 | {7,8}
If I understand correct, 7 is a duplicate of 1 and 8 is a duplicate of 2. As phrased, your question is a bit confusing, because 7 and 8 are not related to each other and the only table of interest is ProductAttributeCombinations.
If this is the case, then one method is to use string aggregation
with combos as (
select id_product_attribute,
string_agg(id_attribute::text, ',' order by id_attribute) as combo
from ProductAttributeCombinations pac
group by id_product_attribute
)
select *
from combos c
where exists (select 1
from combos c2
where c2.id_product_attribute > c.id_product_attribute and
c2.combo = c.combo
);
Your question leaves some room for interpretation. Here is my educated guess:
For each product, return an array of all instances with the same set of attributes as any other instance of the same product with smaller ID.
WITH combo AS (
SELECT id_product, id, array_agg(id_attribute) AS attributes
FROM (
SELECT pa.id_product, pa.id, pac.id_attribute
FROM ProductAttribute pa
JOIN PoductAttributeCombinations pac ON pac.id_product_attribute = pa.id
ORDER BY pa.id_product, pa.id, pac.id_attribute
) sub
GROUP BY 1, 2
)
SELECT id_product, array_agg(id) AS duplicate_attributes
FROM combo c
WHERE EXISTS (
SELECT 1
FROM combo
WHERE id_product = c.id_product
AND attributes = c.attributes
AND id < c.id
)
GROUP BY 1;
Sorting can be inlined into the aggregate function so we don't need a subquery for the sort (like #Gordon already provided). This is shorter, but also typically slower:
WITH combo AS (
SELECT pa.id_product, pa.id
, array_agg(pac.id_attribute ORDER BY pac.id_attribute) AS attributes
FROM ProductAttribute pa
JOIN PoductAttributeCombinations pac ON pac.id_product_attribute = pa.id
GROUP BY 1, 2
)
SELECT ...
This only returns products with duplicate instances.
SQL Fiddle.
Your table names are rather misleading / contradict the rest of your question. Your sample data is not very clear either, only featuring a single product. I assume there are many in your table.
It's also unclear whether you are using double-quoted table names preserving CaMeL-case spelling. I assume: no.

Inner Join all children top(n) parent

I need to do a inner join, where i Get a concrete number of "parent elements" but all it's relations.
If i have the table Agent:
IdAgent | Name
1 | Agent1
2 | Agent2
And the table Econ
IdEcon IdAgent Number
1 | 1 | number1
2 | 2 | number21
3 | 2 | number22
4 | 2 | number23
So if I do a:
SELECT * FROM Agent LEFT JOIN Econ
I Will Get 4 rows
IdAgent | Name | IdEcon IdAgent Number
1 | Name 1 | 1 | 1 | number1
2 | Name 2 | 2 | 2 | number21
2 | Name 2 | 3 | 2 | number22
2 | Name 2 | 4 | 2 | number23
But if i do a:
SELECT top(2) * FROM Agent LEFT JOIN Econ
I Will Get 2 rows and i want 4 rows (I want the top 2 only to affect the table Agent)
How can I accomplish that?
Untested:
SELECT * FROM (Select top(2) * FROM Agent) LEFT JOIN Econ
You could select the top two agents separately and use the result as a derived table to join to Econ, as in jarlh's answer, or you could use TOP (2) WITH TIES together with ORDER BY Agent.IdAgent:
SELECT TOP (2) WITH TIES
...
FROM
dbo.Agent
LEFT JOIN dbo.Econ ON ...
ORDER BY
Agent.IdAgent ASC
;

How to get number of students per course in sql?

So I have these 3 tables:
t_student which looks like this:
STUDENT_ID| FIRST_NAME |LAST_NAME
-----------------------------------
1 | Ivan | Petrov
2 | Ivan | Ivanov
3 | Georgi | Georgiev
t_course which looks like this:
course_id | NAME |LECTURER_NAME
-----------------------------------
1 | Basics | Vasilev
2 | Photography| Loyns
t_enrolment which looks like this:
enrolment_id| student_fk |course_fk | Avarage_grade
-------------------------------------------------------
1 | 1 | 1 |
2 | 3 | 1 |
3 | 4 | 1 |
4 | 2 | 1 |
5 | 1 | 2 | 5.50
6 | 2 | 2 | 5.40
7 | 5 | 2 | 6.00
I need to make 'select' statement and present the number of students per course. The result should be:
Count_students | Course_name
-----------------------------
4 | Basics
3 | Photography
Select all courses from your course Table, join the enrolment table and group by your course id. With count() you can select the number of Students
SELECT MAX(t_course.NAME) AS Course_name, COUNT(t_enrolment.student_fk) AS Count_students
FROM t_course
LEFT JOIN t_enrolment ON t_enrolment.course_fk = t_course.course_id
GROUP BY t_course.course_id;
If you want to select the same student in one course only once (if more then one enrolment can happen) you can use COUNT(DISTINCT t_enrolment.student_fk)
UPDATE
To make it working not only in mySQL I added an aggregate function to the name column.
Depending on the SQL database you are using you will have to add quotes or backticks.
Is this your homework?
select count(*) Count_students, c.name as course_name from t_enrolment e, t_course c group where e.course_fk = c.course_id by c.name
You need a select statement with a join to the couse table (for the Course_name). Group by 't_course'.name to use the COUNT(*) function
this will work:
SELECT COUNT(*) AS Count_students, c.NAME AS Course_name
FROM t_enrolment e
JOIN course c
ON e.course_fk = c.course_id
GROUP BY c.NAME
More information
Count function
Group by
Join

SQL: Get count of rows w.r.t rows of another table

I want to get the count of rows from a table belonging to a category (which are defined in another table). Kind of like the following.
-----------------------------------------
id | name | category
| |
1 | Name 1 | toddler
2 | Name 2 | toddler
3 | Name 3 | newborn
4 | Name 4 | toddler
5 | Name 5 | adult
And I have another table where all the categories are defined
-----------------------------------------
id | category
|
1 | toddler
2 | newborn
3 | adult
4 | elderly
Now I need an SQL Query on the first table which can give me a return result something like this
-----------------------------------------
category | count
|
toddler | 3
newborn | 1
adult | 1
elderly | 0
I need to count each name from Table 1 with a particular category from Table 2 and return the result.
This seems to have a fairly simple solution but I can't get my mind to work on it. Please help!
This is a simple query with LEFT JOIN and COUNT.
select c.category, COUNT(n.category) as count
from Table2 c
left join Table1 n on c.category = n.category
group by c.category
SQL Fiddle demo
Simple. Use a left join on your Table2 with Table1, then use the count function on category and do group by on category.
select b.category, count(a.category)
from Table1 as a
left join Table2 as b
on a.id = b.id
group by b.category