select categories where their parent is type 1 - sql

I have this table categories
|catId |catName|catParentID|catType|
-------------------------------------
|1 |cat1 |null |6 |
|2 |cat2 |null |9 |
|3 |cat3 |1 |6 |
|4 |cat4 |2 |9 |
|5 |cat5 |1 |6 |
|6 |cat6 |3 |8 |
the parents are in the same table with the sub categories only they have no parent.
i need to get all the sub categories that their parent's type is 6.
the output of the example above should look like this-
cat3
cat5

Given your data structure, this seems to work:
select c.*
from categories c
where c.catParentID is not null and -- has a parent
c.catType = 6;
However, that might not be a general solution. So you can use a self-join:
select c.*
from categories c join
categories cp
on c.catParentID = cp.catID
where cp.catType = 6;

SELECT *
FROM categories
WHERE cattype = 6
AND catparentid IS NOT NULL

The Simplest way is,
SELECT * FORM categories WHERE catParentId ='1' AND catType ='6'

Try this... (Based on your desired output)
SELECT t1.*
FROM tablename t1
LEFT JOIN tablename t2 ON t1.catparentid = t2.catid
WHERE t2.cattype = 6
AND t2.catparentid IS NULL

Related

Return rows only if matches all list values

Let's say I have a table customers:
-----------------
|id|name|country|
|1 |Joe |Mexico |
|2 |Mary|USA |
|3 |Jim |France |
-----------------
And a table languages:
-------------
|id|language|
|1 |English |
|2 |Spanish |
|3 |French |
-------------
And a table cust_lang:
------------------
|id|custId|langId|
|1 |1 |1 |
|2 |1 |2 |
|3 |2 |1 |
|4 |3 |3 |
------------------
Given a list: ["English", "Spanish", "Portugese"]
Using a WHERE IN for the list, it will still return customers with ids 1,2 because they match "English" and "Spanish".
However, the results should be 0 rows returned since no customer matches ALL three terms.
I only want the customer ids to return if it matches the cust_lang table.
For instance, Given a list: ["English", "Spanish"]
I would want the results to be customer Id 1, since he alone speaks both languages.
EDIT: #GordonLinoff - That works!!
Now to make it more complex, what's wrong with this additional related query:
Let's assume I also have a table degrees:
-----------
|id|degree|
|1 |PHD |
|2 |BA |
|3 |MD |
-----------
A corresponding join table cust_deg:
------------------
|id|custId|degId |
|1 |1 |1 |
|2 |1 |2 |
|3 |2 |1 |
|4 |3 |3 |
------------------
The following query does not work. However, it is two of the same queries combined. The results should be only rows that match both lists, instead of the one list.
SELECT * FROM customers C
WHERE C.id IN (
SELECT CL.langId FROM cust_lang CL
JOIN languages L on CL.langId = L.id
WHERE L.language IN ("English", "Spanish")
GROUP BY CL.langID
HAVING COUNT(*) = 2)
AND C.id IN (
SELECT CD.custId FROM cust_deg CD
JOIN degrees D ON CD.degID = D.id
WHERE D.degree IN ("PHD", "BA")
GROUP BY CD.custId HAVING COUNT(*) = 2));`
EDIT2: I think i fixed it. I accidentally had an extra select statement in there.
You can do this with group by and having:
select cl.custid
from cust_lang cl join
languages l
on cl.langid = l.id
where l.language in ('English', 'Spanish', 'Portuguese')
group by cl.custid
having count(*) = 3;
If, for example, you only wanted to check for two languages, then you need only change you WHERE ... IN and HAVING conditions, e.g.:
where l.language in ('English', 'Spanish')
and
having count(*) = 2
This is pretty much Gordon's answer but it has the benefit of being a little more flexible on the language list and it doesn't require any change to the having clause.
with my_languages as (
select langId from languages
where language in ('English', 'Spanish')
)
select cl.custId
from cust_lang as cl inner join my_languages as l on l.langId = cl.langId
group by cl.custId
having count(*) = (select count(*) from lang)

Update each row with incremental value Postgres

I want to update every row in a table in Postgres and set each row to a different value; this value is gonna be an incremental value with a start value.
For instance, suppose I have table tab_a with the following data:
|attr_a|attr_b|
|1 |null |
|2 |null |
|3 |null |
|4 |null |
The output I might want is:
|attr_a|attr_b|
|1 |5 |
|2 |6 |
|3 |7 |
|4 |8 |
Here is my script:
UPDATE tab_a
SET attr_b = gen.id
FROM generate_series(5,8) AS gen(id);
However is not working as expected...
You could do
UPDATE tab_a upd
SET attr_b = row_number + 4 -- or something like row_number + (select max(attr_a) from tab_a)
FROM (
SELECT attr_a, row_number() over ()
FROM tab_a
ORDER BY 1
) a
WHERE upd.attr_a = a.attr_a;
Do something like this
UPDATE pr_conf_item upd
SET item_order = a.row_number
FROM (
SELECT id, row_number() over ()
FROM pr_conf_item
ORDER BY 1
) a
WHERE upd.id = a.id;

Joining with null

I have two tables and I want to join them with null values in it.
Sample data of my first table(A_TEST):
+--+----+
|ID|NAME|
+--+----+
| |a |
|1 |b |
|1 |c |
+--+----+
Sample data of my second table(B_TEST):
+--+----+
|ID|NAME|
+--+----+
|1 |d |
|2 |e |
|3 |f |
+--+----+
I need to achieve the result by joining a_test.id = b_test.id and if there is null values in it I need to fetch them too. So I tried to write query as below,
select a_test.id,a_test.name,b_test.id,b_test.name
from a_test,b_test
where (a_test.id = b_test.id
or a_test.id is null);
I got output as below,
+--+----+--+----+
|ID|NAME|ID|NAME|
+--+----+--+----+
| |a |1 |d |
| |a |2 |e |
| |a |3 |f |
|1 |b |1 |d |
|1 |c |1 |d |
+--+----+--+----+
But my expected result is, since id 1 is there in my a_test i need the corresponding row from b_test also.See output below
+--+----+--+----+
|ID|NAME|ID|NAME|
+--+----+--+----+
| |a |1 |d |
|1 |b |1 |d |
|1 |c |1 |d |
+--+----+--+----+
I tried with outer joins also but that also does not give me the expected output.
Your own query is almost correct (although you shouldn't use error-prone comma-separated joins that went out of fashion some twenty years ago). You are only missing the condition what must match in case of a_test.id is null (which is: the b_test.id must be in table a_test).
select
a.id as a_id,
a.name as a_name,
b.id as b_id,
b.name as b_name
from a_test a
join b_test b on
(a.id = b.id)
or
(a.id is null and b.id in (select id from a_test));
SQL fiddle: http://www.sqlfiddle.com/#!4/fae22/2.
However strange and meaningless your requirement is, this query gives you your expected result:
select A.*, B.*
from a_test A
join b_test B
on A.id = B.id
union all
select A.*, B.*
from a_test A
cross join b_test B
where A.id is null
and exists (
select 1
from a_test Ax
where Ax.id = B.id
)
order by 2, 4
;
Enjoy!
If a a_test.id NULL is supposed to be treated as 1 when joining, use COALESCE and a sub-query to find replacing value (to be figured out by yourself, just make sure it doesn't return more than one row):
select a_test.id,a_test.name,b_test.id,b_test.name
from a_test,b_test
where COALESCE(a_test.id,(select integervalue from sometable)) = b_test.id
select a_test.id,a_test.name,b_test.id,b_test.name
from a_test,b_test
where a_test.id = b_test.id(+)
But, what are you want see, when a_test.id is null or missing?

Complicated min/max multi-table query

I need to get the min and max score of group ids, but only if they are enabled:
cdu_group_sl: cdu_group_cc: cdu_group_ph:
-------------------- -------------------- --------------------
|id |name |enabled | |id |name |enabled | |id |name |enabled |
-------------------- -------------------- --------------------
|1 |sl_1 |1 | |1 |cc_1 |1 | |1 |ph_1 |0 |
|2 |sl_3 |1 | |2 |cc_2 |0 | |2 |ph_2 |1 |
|3 |sl_4 |1 | |3 |cc_3 |1 | |3 |ph_3 |1 |
-------------------- -------------------- --------------------
Scores are found in a separate table:
cdu_user_progress
----------------------------------
|id |group_type |group_id |score |
----------------------------------
|1 |sl |1 |50 |
|1 |cc |1 |10 |
|1 |ph |1 |20 |
|1 |sl |2 |80 |
|1 |sl |3 |20 |
|1 |cc |3 |30 |
|1 |sl |1 |40 |
|1 |ph |1 |50 |
|1 |cc |1 |40 |
|1 |ph |2 |90 |
----------------------------------
I need to get a max and min score for each type of group for only enabled groups (for each type):
---------------------------------------------
|group_type |group_id |min_score |max_score |
---------------------------------------------
|sl |1 |40 |50 |
|sl |2 |80 |80 |
|sl |3 |20 |20 |
|cc |1 |10 |40 |
|cc |3 |30 |30 |
|ph |1 |20 |50 |
|ph |2 |90 |90 |
---------------------------------------------
Any idea what the query might be??? So far I have:
SELECT * FROM cdu_user_progress
JOIN cdu_group_sl ON (cdu_group_sl.id = cdu_user_progress.group_id AND cdu_user_progress.group_type = 'sl')
JOIN cdu_group_cc ON (cdu_group_cc.id = cdu_user_progress.group_id AND cdu_user_progress.group_type = 'cc')
JOIN cdu_group_ph ON (cdu_group_ph.id = cdu_user_progress.group_id AND cdu_user_progress.group_type = 'ph')
WHERE cdu_user_progress.uid = $student->uid
AND (cdu_user_progress.group_type = 'sl' AND cdu_group_sl.enabled = 1)
AND (cdu_user_progress.group_type = 'cc' AND cdu_group_cc.enabled = 1)
AND (cdu_user_progress.group_type = 'ph' AND cdu_group_ph.enabled = 1)
Probably completely wrong...
what about using a union to pick the groups you are interested in - something like:
select group_type, group_id min(score) min_score, max(score) max_score
from (
select id, 'sl' grp from cdu_group_sl where enabled = 1
union all
select id, 'cc' from cdu_group_cc where enabled = 1
union all
select id, 'ph' from cdu_group_ph where enabled = 1
) grps join cdu_user_progress scr
on grps.id = scr.group_id and grps.grp = scr.group_type
group by scr.group_type, scr.group_id
The following is probably the fastest way to do this query. To optimize this, you should have an index on group_id, enabled on each of the three "sl", "cc", and "ph" tables:
select cup.*
from cdu_user_progress cup
where (cup.group_type = 'sl' and
exists (select 1
from cdu_group_sl sl
where sl.id = cup.group_id and
sl.enabled = 1
)
) or
(cup.group_type = 'cc' and
exists (select 1
from cdu_group_cc cc
where cc.id = cup.group_id and
cc.enabled = 1
)
) or
(cup.group_type = 'ph' and
exists (select 1
from cdu_group_ph ph
where ph.id = cup.group_id and
ph.enabled = 1
)
)
As a note, having three tables with the same structure is usually a sign of a poor database schema. These three tables should probably be combined into a single table, which would make this query much easier to write.
If you are just starting up this project, I would recommend refining your data structure. Based on what you showed, you could benefit from only one cdu_groups table with a reference to a new cdu_group_types table, and removing the group_type column from cdu_user_progress.
If this is an established project, where changing the structure would be too disruptive... then one of the other answers showing a query would be a better/easier fit.
Otherwise, you could simplify things with restructured tables and end up with a query like:
SELECT group_type,
group_id,
MIN(score) as min_score,
MAX(score) as max_score
FROM cdu_user_progress c
INNER JOIN cdu_groups g
ON c.group_id=g.id
INNER JOIN cdu_group_types t
ON g.group_type_id=t.id
WHERE enabled=1
GROUP BY group_type, group_id
This is shown, with expected results, in this SQLFiddle. With this structure you can add new group types as you want (and also cut down on amount of tables and joins). Tables would be (simplified in this code below, no FKs or anything):
CREATE TABLE cdu_user_progress
(id INT, group_id INT, score INT)
CREATE TABLE cdu_group_types
(id INT, group_type VARCHAR(3))
CREATE TABLE cdu_groups
(id INT, group_type_id INT, name VARCHAR(10), enabled BIT NOT NULL DEFAULT 1)
Granted moving data to a new structure may be a pain or not reasonable... but wanted to throw this out there as a possibility or just something to chew on.

SQL Server recursive query with associated table

I have a typical parent/child relationship table to represent folders. My challenge is using it in conjunction with another table.
The folder table is like this:
+--+----+--------+
|id|name|parentid|
+--+----+--------+
|1 |a |null |
+--+----+--------+
|2 |b |1 |
+--+----+--------+
|3 |c1 |2 |
+--+----+--------+
|4 |c2 |2 |
+--+----+--------+
The association table is like this:
+--+--------+
|id|folderid|
+--+--------+
|66|2 |
+--+--------+
|77|3 |
+--+--------+
so that where association.id = 66 has a relationship to folder.id = 2
What I need to do is find the association.id of the first ancestor with a record in the association table.. Using the example data above, given folder.id of 3 I expect to find 77; given folder.id of 2 or 4 I expect to find 66; any other folder.id value would find null.
Finding folder ancestry can be done with a common table expression like this:
WITH [recurse] (id,name,parentid,lvl) AS
(
select a.id,a.name,a.parentid,0 FROM folder AS a
WHERE a.id='4'
UNION ALL
select r.id,r.name,r.parentid,lvl+1 FROM folder as r
INNER JOIN [recurse] ON recurse.parentid = r.id
)
SELECT * from [recurse] ORDER BY lvl DESC
yielding the results:
+--+----+--------+---+
|id|name|parentid|lvl|
+--+----+--------+---+
|1 |a | |2 |
+--+----+--------+---+
|2 |b |1 |1 |
+--+----+--------+---+
|4 |c2 |2 |0 |
+--+----+--------+---+
To include the association.id I've tried using a LEFT JOIN in the recursive portion of the CTE, but this is not allowed by SQL Server.
What workaround do I have for this?
Or better yet, is the a way to query directly for the particular association.id? (e.g., without walking through the results of the CTE query that I have been attempting)
SELECT r.id, r.name, r.parentid, r.lvl, a.folderid, a.id as associationid
FROM [recurse] r
LEFT JOIN [association] a
ON r.id = a.folderid
WHERE a.folderId IS NOT NULL
ORDER BY lvl DESC
This will give you the records that have values in the association table. Then you could limit it to the first record that has a value or just grab the top result