apply recursive left join - recursion

I'm using SQL Server 2012 and I have the following problem:
I have the this table (Category):
IDCategory| CategoryDesc | Father
1 | R1 | 0
2 | R1 - ST | 1
3 | R1 - CT | 1
4 | R1 - ST - SA | 2
5 | R1 - ST - CA 10 | 2
6 | R1 - ST - CA 20 | 2
7 | R1 - CT - CA | 3
8 | R1 - CT - SA | 3
9 | R2 | 0
10 | R2 ST | 9
.
.
until R9
And this one (CategoryDefinition):
IDCategory| First| Last
1 | 0 | 300
9 | 301 | 600
.
.
.
And I'm using the following query because I know there're only 3 levels:
SELECT
cat3.IDCategory,
cat.CategoryDesc AS title1,
cat2.CategoryDesc AS title2,
cat3.CategoryDesc AS title3,
catdef.First,
catdef.Last
FROM Category as cat
LEFT JOIN Category AS cat2 ON cat2.Father=cat.IDCategory
LEFT JOIN Category AS cat3 ON cat3.Father=cat2.IDCategory
INNER JOIN CategoryDefinition as catdef on cat.IDCategory = catdef.IDCategory
WHERE cat3.IDCategory = 7
Query result:
IDCategory| title1 | title2 |title3 |First|Last
7 | R1 | R1 - CT | R1 - CT - CA | 0 | 300
But how can I make this recursive? In the case that in the future could appear new levels (So I don't have to add a new left join for each new level that appears).
Thanks!

I can at least help with the recusive cte of an adjacency list and building a materialized path:
For this table:
create table category (IDCategory int primary key,CategoryDesc varchar(32),Father int)
insert into category values
(1,'R1',0)
,(2,'ST',1)
,(3,'CT',1)
,(4,'SA',3)
,(5,'SA 10',2)
,(6,'SA 20',2)
,(7,'CA',2)
,(8,'SA',2)
,(9,'R2',0)
,(10,'ST',9)
using a recursive cte:
;with cte as (
-- anchor elements: where Father = 0
select
IDCategory
, categoryDesc
, Father
, parentName = convert(varchar(32),null)
, path = convert(varchar(128),categoryDesc)
from category
where Father = 0
-- recursion begins here
union all
select
c.IDCategory
, c.categoryDesc
, c.Father
, parentName = p.categoryDesc
, path = convert(varchar(128),p.path+' - '+c.categoryDesc)
from category c
inner join cte as p on c.Father= p.IDCategory
)
-- we select all the results
select cte.*
from cte
order by idCategory
returns:
+------------+--------------+--------+------------+-----------------+
| IDCategory | categoryDesc | Father | parentName | path |
+------------+--------------+--------+------------+-----------------+
| 1 | R1 | 0 | NULL | R1 |
| 2 | ST | 1 | R1 | R1 - ST |
| 3 | CT | 1 | R1 | R1 - CT |
| 4 | SA | 3 | CT | R1 - CT - SA |
| 5 | SA 10 | 2 | ST | R1 - ST - SA 10 |
| 6 | SA 20 | 2 | ST | R1 - ST - SA 20 |
| 7 | CA | 2 | ST | R1 - ST - CA |
| 8 | SA | 2 | ST | R1 - ST - SA |
| 9 | R2 | 0 | NULL | R2 |
| 10 | ST | 9 | R2 | R2 - ST |
+------------+--------------+--------+------------+-----------------+
Adding the join to the anchor of the recursive cte:
;with cte as (
-- anchor elements: where Father = 0
select
c.IDCategory
, c.categoryDesc
, c.Father
, parentName = convert(varchar(32),null)
, path = convert(varchar(128),c.categoryDesc)
, cd.First
, cd.Last
from category c
inner join CategoryDefinition cd
on c.IdCategory=cd.IdCategory
where Father = 0
-- recursion begins here
union all
select
c.IDCategory
, c.categoryDesc
, c.Father
, parentName = p.categoryDesc
, path = convert(varchar(128),p.path+' - '+c.categoryDesc)
, p.First
, p.Last
from category c
inner join cte as p on c.Father= p.IDCategory
)
select cte.*
from cte
--where IdCategory = 7
order by idCategory
rextester demo: http://rextester.com/POSVP81190
returns:
+------------+--------------+--------+------------+-----------------+-------+------+
| IDCategory | categoryDesc | Father | parentName | path | First | Last |
+------------+--------------+--------+------------+-----------------+-------+------+
| 1 | R1 | 0 | NULL | R1 | 0 | 300 |
| 2 | ST | 1 | R1 | R1 - ST | 0 | 300 |
| 3 | CT | 1 | R1 | R1 - CT | 0 | 300 |
| 4 | SA | 3 | CT | R1 - CT - SA | 0 | 300 |
| 5 | SA 10 | 2 | ST | R1 - ST - SA 10 | 0 | 300 |
| 6 | SA 20 | 2 | ST | R1 - ST - SA 20 | 0 | 300 |
| 7 | CA | 2 | ST | R1 - ST - CA | 0 | 300 |
| 8 | SA | 2 | ST | R1 - ST - SA | 0 | 300 |
| 9 | R2 | 0 | NULL | R2 | 301 | 600 |
| 10 | ST | 9 | R2 | R2 - ST | 301 | 600 |
+------------+--------------+--------+------------+-----------------+-------+------+

Related

How can I execute multiple queries in one query?

I am building a chat application where I am using Firebase to send and receive messages. Once I send or receive a message, I am storing it to SQLite as follows. Now it the recent chats screen, I need the last message from all the unique chats, number of unread messages in those unique chats in one single query as I am observing the SQLite database.
Mid(STRING) | SentBy | SentTo | message | readTime | sentTime| Type
----------------+--------+--------+---------+----------+---------+------
A | AA | JD | M1 | 1 | 0 | S
B | JD | AA | M2 | 2 | 1 | s
C | AA | JD | M3 | 3 | 2 | s
D | AB | JD | m5 | null | 3 | s
E | AA | JC | M1 | 5 | 4 | s
F | JD | AB | M2 | 6 | 5 | s
G | AA | JD | M3 | 7 | 6 | s
H | AA | JC | m5 | 8 | 7 | s
I | AA | JD | M1 | null | 8 | s
J | JD | AA | M2 | 10 | 9 | s
K | AA | JD | M3 | 11 | 10 | s
L | AB | JC | m5 | 12 | 11 | s
M | AA | JD | M1 | 13 | 12 | s
N | JC | AA | M2 | 14 | 13 | s
O | AB | JD | M3 | 15 | 14 | s
P | JC | JD | m5 | 16 | 15 | s
I tried
SELECT *,COUNT() FROM messagesTable GROUP BY min ( sentBy, sentTo ), max( sentBy , sentTo ) ORDER BY sentTime desc
This query gives me the last messages from every combination of sentTo and sentBy. But I also need to know how many messages are unread for that combination. I want to run a query for every row like
SELECT COUNT() FROM messagesTable WHERE sentBy = message.sentBy, sentTo = message.sentTo, readTime = null
How can I run both queries in a single query?
You must group by the combination of (sentby, sentto) and with a straight count(*) get the total number of messages and with conditional aggregation you can get the number of unread mesages.
Then join to the result to the table to get also the last message:
select
g.user1, g.user2, g.lasttime, m.message lastmessage,
g.totalcounter, g.unreadcounter
from messagestable m inner join (
select
min(sentby, sentto) user1, max(sentby, sentto) user2,
max(senttime) lasttime, count(*) totalcounter,
sum(case when readtime is null then 1 else 0 end) unreadcounter
from messagestable
group by user1, user2
) g
on g.user1 = min(m.sentby, m.sentto) and g.user2 = max(m.sentby, m.sentto)
and g.lasttime = m.senttime
order by g.lasttime desc
See the demo.
Results:
| user1 | user2 | lasttime | lastmessage | totalcounter | unreadcounter |
| ----- | ----- | -------- | ----------- | ------------ | ------------- |
| JC | JD | 15 | m5 | 1 | 0 |
| AB | JD | 14 | M3 | 3 | 1 |
| AA | JC | 13 | M2 | 3 | 0 |
| AA | JD | 12 | M1 | 8 | 1 |
| AB | JC | 11 | m5 | 1 | 0 |

Master-Detail show data SQL

I'm working with SQL Server and I have this 3 tables
STUDENTS
| id | student |
-------------
| 1 | Ronald |
| 2 | Jenny |
SCORES
| id | score | period | student |
| 1 | 8 | 1 | 1 |
| 2 | 9 | 2 | 1 |
PERIODS
| id | period |
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
And I want a query that returns this result:
| student | score1 | score2 | score3 | score4 |
| Ronald | 8 | 9 | null | null |
| Jenny | null | null | null | null |
As you can see, the number of scores depends of the periods because sometimes it can be 4 o 3 periods.
I don't know if I have the wrong idea or should I make this in the application, but I want some help.
You need to PIVOT your data e.g.
select Y.Student, [1], [2], [3], [4]
from (
select T.Student, P.[Period], S.Score
from Students T
cross join [Periods] P
left join Scores S on S.[Period] = P.id and S.Student = T.id
) X
pivot
(
sum(Score)
for [Period] in ([1],[2],[3],[4])
) Y
Reference: https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot?view=sql-server-20

Join, group and count in the same query

I have these tables in my database:
TOTAL TABLE:
total_table_id | person_id | car_id | year
----------------|-----------|-----------|------
0 | 1 | 4 | 2015
1 | 1 | 2 | 2017
2 | 2 | 0 | 2017
3 | 3 | 3 | 2017
4 | 3 | 4 | 2015
PERSON TABLE:
person_id | name | age
------------|-------|-----
0 | John | 26
1 | Anna | 41
2 | Sam | 33
3 | Tim | 33
CAR TABLE:
car_id | model | color
--------|-------|-------
0 | A | red
1 | B | blue
2 | B | white
3 | D | red
4 | C | black
And what I want after select the year in a dropdown is to get something like this:
2017:
color | age | cars_count
--------|-------|------------
red | 33 | 2
white | 41 | 1
This is the query that I have for the moment:
from a in total
join b in person
on a.person_id equals b.person_id
join c in car
on a.car_id equals c.car_id
select new
{
color = c.color,
age = b.age,
cars_count = ? // <--------This is what I don't know how to get it
}).ToList();
Any tips?
You should use group by statement
var answer = (from total in totalTable
where total.year == 2017
join car in carTable on total.car_id equals car.car_id
join person in personTable on total.person_id equals person.person_id
group person by car.color into sub
select new
{
color = sub.Key,
age = sub.Max(x => x.age),
//or age = sub.Min(x => x.age),
//or age = sub.First().age,
count = sub.Count()
}).ToList();

SQLite : How to combine subquery results

I have a table like this
id | inventory_id | serial_type | serial
------------------------------------------------------
38 | 5892 | 1 | 9502
39 | 5895 | 1 | i95054-1
40 | 5895 | 2 | m95054-1
41 | 5895 | 1 | i95054-2
42 | 5895 | 2 | m95054-2
43 | 5895 | 1 | i95054-3
44 | 5895 | 2 | m95054-3
45 | 5895 | 1 | i95054-4
46 | 5895 | 2 | m95054-4
I have 2 subqueries from same table like
SELECT inventory_id, serial as type_one_serial
FROM serials
WHERE serial_type = 1
AND inventory_id = 5895
and another
SELECT inventory_id, serial as type_two_serial
FROM serials
WHERE serial_type = 2
AND inventory_id = 5895
How I can combine these to get a result like
inventory_id | type_one_serial | type_two_serial
------------------------------------------------
5895 | i95054-1 | m95054-1
5895 | i95054-2 | m95054-2
5895 | i95054-3 | m95054-3
5895 | i95054-4 | m95054-4
EDIT :
I have modified the source table to iclude another column sl
sl | id | inventory_id | serial_type | serial
----------------------------------------------------------------
0 | 38 | 5892 | 1 | 9502
0 | 39 | 5895 | 1 | i95054-1
0 | 40 | 5895 | 2 | m95054-1
1 | 41 | 5895 | 1 | i95054-2
1 | 42 | 5895 | 2 | m95054-2
2 | 43 | 5895 | 1 | i95054-3
2 | 44 | 5895 | 2 | m95054-3
3 | 45 | 5895 | 1 | i95054-4
3 | 46 | 5895 | 2 | m95054-4
You are looking for a pivot query:
SELECT inventory_id,
MAX(CASE WHEN serial_type = 1 THEN 'i' || SUBSTR(serial, 2) END) AS type_one_serial,
MAX(CASE WHEN serial_type = 2 THEN 'm' || SUBSTR(serial, 2) END) AS type_two_serial
FROM serials
WHERE serial_type IN (1, 2) AND -- the WHERE clause may be optional
inventory_id = 5895 -- depending on what you want
GROUP BY inventory_id,
SUBSTR(serial, 2)
Update:
Taking the new sl column into account, we can use this to discriminate between the two types of serials for a given inventory_id. In this case, we could write the following query:
SELECT inventory_id,
MAX(CASE WHEN serial_type = 1 THEN serial END) AS type_one_serial,
MAX(CASE WHEN serial_type = 2 THEN serial END) AS type_two_serial
FROM serials
GROUP BY inventory_id,
sl
#Tim Biegeleisen 's edited answer worked perfectly for me. I have done few modifications based on that. Not sure it will make any improvement, just posting here for reference :
SELECT inventory_id,
MAX(CASE WHEN serial_type = 1 THEN serial END) AS type_one_serial,
MAX(CASE WHEN serial_type = 2 THEN serial END) AS type_two_serial
FROM serials
WHERE inventory_id = 5895
GROUP BY sl

Is there an sqlite function that can check if a field matches a certain value and return 0 or 1?

Consider the following sqlite3 table:
+------+------+
| col1 | col2 |
+------+------+
| 1 | 200 |
| 1 | 200 |
| 1 | 100 |
| 1 | 200 |
| 2 | 400 |
| 2 | 200 |
| 2 | 100 |
| 3 | 200 |
| 3 | 200 |
| 3 | 100 |
+------+------+
I'm trying to write a query that will select the entire table and return 1 if the value in col2 is 200, and 0 otherwise. For example:
+------+--------------------+
| col1 | SOMEFUNCTION(col2) |
+------+--------------------+
| 1 | 1 |
| 1 | 1 |
| 1 | 0 |
| 1 | 1 |
| 2 | 0 |
| 2 | 1 |
| 2 | 0 |
| 3 | 1 |
| 3 | 1 |
| 3 | 0 |
+------+--------------------+
What is SOMEFUNCTION()?
Thanks in advance...
In SQLite, boolean values are just integer values 0 and 1, so you can use the comparison directly:
SELECT col1, col2 = 200 AS SomeFunction FROM MyTable
Like described in Does sqlite support any kind of IF(condition) statement in a select you can use the case keyword.
SELECT col1,CASE WHEN col2=200 THEN 1 ELSE 0 END AS col2 FROM table1

Resources