SQL Query with group and having - sql

I'm trying to select all client ID's that has TypeId equal 1 but not TypeId equal 3.
Table example:
---------------------
| ClientID | TypeId |
---------------------
| 1 | 1 |
| 1 | 3 |
| 2 | 3 |
| 3 | 1 |
---------------------
My query:
SELECT ClientId, TypeId
FROM Table
GROUP BY ClientId, TypeId
HAVING TypeId != 3
What I have:
---------------------
| ClientID | TypeId |
---------------------
| 1 | 1 |
| 3 | 1 |
---------------------
What I expect:
---------------------
| ClientID | TypeId |
---------------------
| 3 | 1 |
---------------------
The critical thing is that the table have more than 3 * 10^8 registers.
Thanks in advance!

I would suggest aggregation and having:
SELECT ClientId
FROM Table
GROUP BY ClientId
HAVING SUM(CASE WHEN TypeId = 1 THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN TypeId = 3 THEN 1 ELSE 0 END) = 0;
Each condition in the HAVING clause counts the number of rows having a particular TypeId value. The > 0 means there is at least one. The = 0 means there are none.
If you actually want to get the original rows that match -- so all TypeIds associated with a client. You can use a JOIN or window functions:
SELECT ClientId, TypeId
FROM (SELECT ClientId, TypeId,
SUM(CASE WHEN TypeId = 1 THEN 1 ELSE 0 END) OVER (PARTITION BY ClientId) as TypeId_1,
SUM(CASE WHEN TypeId = 3 THEN 1 ELSE 0 END) OVER (PARTITION BY ClientId) as TypeId_3
FROM Table
) t
WHERE TypeId_1 > 0 AND TypeId_3 = 0;

Try this:
SELECT t1.*
FROM Table AS t1
WHERE TypeId = 1 AND
NOT EXISTS (SELECT 1
FROM Table AS t2
WHERE t1.ClientId = t2.ClientId AND t2.TypeId = 3)

Try this one
SELECT ClientId, TypeId
FROM Table
WHERE ClientId not in (select ClientId from Table where TypeId = 3)
GROUP BY ClientId, TypeId

I think you could also achieve this with a common table expression:
WITH cte AS (
SELECT ClientId
FROM [Table]
WHERE TypeId = 1
)
SELECT DISTINCT ClientId
FROM [Table]
WHERE ClientId IN
(
SELECT ClientId
FROM cte
)
AND TypeId != 3
Alternatively, try this:
WITH cte AS (
SELECT ClientId
FROM [Table]
WHERE TypeId = 1
)
SELECT ClientId
FROM cte
EXCEPT
SELECT CLientId
FROM [Table]
WHERE TypeId = 3

Related

SQLite: Use subquery result in another subquery

I have following table with data
id | COL1
=========
1 | b
2 | z
3 | b
4 | c
5 | b
6 | a
7 | b
8 | c
9 | a
So i know ID of 'z' (ID = 2) in the table and i will call it Z_ID.
I need to retrieve rows between 'a' and 'c' (including 'a' and 'c').
It must be first 'a' that comes after Z_ID.
'c' must come after Z_ID and after 'a' that i found previously.
Result that i am seeking is:
id | COL1
=========
6 | a
7 | b
8 | c
My SELECT looks like this
SELECT *
FROM table
WHERE id >= (
SELECT MIN(ID)
FROM table
WHERE COL1 = 'a' AND ID > 2
)
AND id <= (
SELECT MIN(ID)
FROM table
WHERE COL1 = 'c'AND ID > 2 and ID > (
SELECT MIN(ID)
FROM table
WHERE COL1 = 'a' AND ID > 2
)
)
I am getting the result that i want. But i am concerned about performance because i am using same subquery two times. Is there a way to reuse a result from first subquery?
Maybe there is cleaner way to get the result that i need?
Use a CTE which will return only once the result of the subquery that you use twice:
WITH cte AS (
SELECT MIN(ID) minid
FROM tablename
WHERE COL1 = 'a' AND ID > 2
)
SELECT t.*
FROM tablename t CROSS JOIN cte c
WHERE t.id >= c.minid
AND t.id <= (
SELECT MIN(ID)
FROM tablename
WHERE COL1 = 'c' and ID > c.minid
)
In your 2nd query's WHERE clause:
WHERE COL1 = 'c'AND ID > 2 and ID > (...
the condition AND ID > 2 is not needed because the next condition and ID > (... makes sure that ID will be greater than 2 so I don't use it either in my code.
See the demo.
Results:
| id | COL1 |
| --- | ---- |
| 6 | a |
| 7 | b |
| 8 | c |
You can use window functions for this:
select t.*
from (select t.*,
min(case when id > min_a_id and col1 = 'c' then id end) over () as min_c_id
from (select t.*,
min(case when col1 = 'a' then id end) over () as min_a_id
from (select t.*,
min(case when col1 = 'z' then id end) over () as z_id
from t
) t
where id > z_id
) t
) t
where id >= min_a_id and id < min_c_id;

sql where have all multiple conditions in entry table

I've been trying to wrap my brain around this using joins, subquery joins, not exists clauses and I keep failing to come up with a query that produces the correct results.
I have 2 table's Trans and TransEntry
Trans Table (PRIMARY TransID)
TransID | Date
-----------
1 1/1/18
2 1/2/18
3 1/3/18
TransEntry Table (PRIMARY TransEntryID)
TransEntryID | TransID |Item
-----------
1 1 A
2 1 B
3 1 C
4 2 A
5 2 D
6 2 F
7 3 A
8 3 B
9 3 G
10 3 C
I need to have all TransID from TransEntry where the have item A and item C in the Entry, in our sample it will be only TransID (1,3)
or
SELECT TransID FROM TransEntry WHERE Item = 'A'
INTERSECT
SELECT TransID FROM TransEntry WHERE Item = 'C'
I think something like this should work:
SELECT TransID
FROM TransEntry
WHERE Item in ('A','C')
GROUP BY TransID
HAVING Count(DISTINCT Item) = 2;
Here's a sqlfiddle showing this
CREATE TABLE TransEntry(
TransEntryID INTEGER NOT NULL PRIMARY KEY
,TransID INTEGER NOT NULL
,Item VARCHAR(1) NOT NULL
);
INSERT INTO TransEntry(TransEntryID,TransID,Item) VALUES
(1,1,'A')
,(2,1,'B')
,(3,1,'C')
,(4,2,'A')
,(5,2,'D')
,(6,2,'F')
,(7,3,'A')
,(8,3,'B')
,(9,3,'G')
,(10,3,'C');
SELECT TransID
FROM TransEntry
WHERE Item in ('A','C')
GROUP BY TransID
HAVING Count(DISTINCT Item) = 2
+---------+
| TransID |
+---------+
| 1 |
| 3 |
+---------+
My answer wasn't as succinct as Aaron's, but in case it's helpful:
SELECT
TransID
FROM
Trans
WHERE
TransID IN ( SELECT TransID FROM TransEntry WHERE Item = 'A' ) AND
TransID IN ( SELECT TransID FROM TransEntry WHERE Item = 'C' )
you can go for something like this:
select transId
from
(
select
TransID ,
case when item = 'A' then 1 else 0 end as HasA,
case when item = 'C' then 1 else 0 end as HasC
from TransEntry
) a
group by transId
having sum(hasA) > 0 and sum(hasC) > 0
You can use GROUP BY :
SELECT TransID
FROM TransEntry
GROUP BY TransID
HAVING SUM(CASE WHEN item = 'A' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN item = 'C' THEN 1 ELSE 0 END) > 0;
SELECT TransEntry.TransID, TransEntry.Item, GROUP_CONCAT(TransEntry.Item) AS items
FROM TransEntry
group by TransEntry.TransID Having Find_In_Set('A',items)>0 and Find_In_Set('C',items)>0
see the jsfiddle

SQL separate the count of one column

I have a SQL table that contains three columns:
userId
userName
item
and I created this SQL query which will count all the items types of one user:
select
count(ItemID) as 'count of all items types',
userId,
userName
from
userTable
where
ItemID in (2, 3, 4)
and userId = 1
group by
userId, userName
The result will be like this:
+--------+----------+--------------------------+
| userId | userName | count of all items types |
+--------+----------+--------------------------+
| 1 | kim | 25 |
and I am looking for a way to separate the counting of itemes types, so the result should be like this:
+--------+----------+----------------+----------------+-----------------+
| userId | userName | count of item1 | count of item2 | count of item3 |
+--------+----------+----------------+----------------+-----------------+
| 1 | kim | 10 | 10 | 5 |
SELECT
userID,
userName,
SUM(CASE WHEN ItemID = 2 THEN 1 ELSE 0 END) AS count_of_item1,
SUM(CASE WHEN ItemID = 3 THEN 1 ELSE 0 END) AS count_of_item2,
SUM(CASE WHEN ItemID = 4 THEN 1 ELSE 0 END) AS count_of_item3
FROM
My_Table
GROUP BY
userID,
userName
This is called conditional aggregation. Use CASE for this.
With COUNT:
select
count(case when ItemID = 1 then 1 end) as count_item1,
count(case when ItemID = 2 then 1 end) as count_item2,
count(case when ItemID = 3 then 1 end) as count_item3
...
(then 1 could also be anything else except null, e.g. then 'count me'. This works because COUNT counts non-null values and when omitting the ELSE in CASE WHEN you get null. You could also explicitly add else null.)
Or with SUM:
select
sum(case when ItemID = 1 then 1 else 0 end) as count_item1,
sum(case when ItemID = 2 then 1 else 0 end) as count_item2,
sum(case when ItemID = 3 then 1 else 0 end) as count_item3
...
This is how you would do it :
select userId,
username,
SUM(CASE WHEN ItemID = '2' THEN 1 ELSE 0 END) AS Item2-Cnt,
SUM(CASE WHEN ItemID = '3' THEN 1 ELSE 0 END) AS Item3-Cnt,
SUM(CASE WHEN ItemID = '4' THEN 1 ELSE 0 END) AS Item4-Cnt
FROM userTable
GROUP BY userID, userName

Aggregating a unique pair of column values from the same table based on a common column value

I have the following table:
my_table
------------------------
| common_id | uniq_val |
------------------------
| 1 | foo |
------------------------
| 1 | bar |
------------------------
And I want to aggregate values from it such that the resulting query looks like:
DESIRED RESULT
---------------------------------------
| common_id | uniq_val_1 | uniq_val_2 |
---------------------------------------
| 1 | foo | bar |
---------------------------------------
OR
---------------------------------------
| common_id | uniq_val_1 | uniq_val_2 |
---------------------------------------
| 1 | bar | foo |
---------------------------------------
So I've written the query:
SELECT t1.common_id, t1.uniq_val, t2.uniq_val
FROM my_table t1 JOIN my_table AS t2
ON t1.common_id=t2.common_id
WHERE t1.uniq_val!=t2.uniq_val;
Which results in
RESULTING SELECT
---------------------------------------
| common_id | uniq_val_1 | uniq_val_2 |
---------------------------------------
| 1 | foo | foo |
---------------------------------------
| 1 | bar | bar |
---------------------------------------
But I only need one of those columns, so I should be able to do a GROUP BY t1.common_id, like:
SELECT t1.common_id, t1.uniq_val, t2.uniq_val
FROM my_table t1 JOIN my_table AS t2
ON t1.common_id=t2.common_id
WHERE t1.uniq_val!=t2.uniq_val
GROUP BY t1.common_id;
Unfortunately this returns the error:
ERROR: column "t1.uniq_val" must appear in the GROUP BY clause or be used in an aggregate function
Can anyone point out the error in my logic?
How about simple aggregation?
select common_id, min(uniq_val) as uniq_val_1, max(uniq_val) as uniq_val_2
from my_table
group by common_id;
you can try distinct on
SELECT distinct on (t1.common_id) t1.common_id, t1.uniq_val, t2.uniq_val
FROM my_table t1 JOIN my_table AS t2
ON t1.common_id=t2.common_id
WHERE t1.uniq_val!=t2.uniq_val;
I think it will produce what you need!
This will handle up to 10 uniq_val values per common_id. You can remove or add to account for fewer or more uniq_val values if needed.
See a demonstration here with common_id values that have varying counts of uniq_val values:
http://sqlfiddle.com/#!15/e2c87/1/0
select common_id,
max(case when rn = 1 then uniq_val else null end) as uniq_val_1,
max(case when rn = 2 then uniq_val else null end) as uniq_val_2,
max(case when rn = 3 then uniq_val else null end) as uniq_val_3,
max(case when rn = 4 then uniq_val else null end) as uniq_val_4,
max(case when rn = 5 then uniq_val else null end) as uniq_val_5,
max(case when rn = 6 then uniq_val else null end) as uniq_val_6,
max(case when rn = 7 then uniq_val else null end) as uniq_val_7,
max(case when rn = 8 then uniq_val else null end) as uniq_val_8,
max(case when rn = 9 then uniq_val else null end) as uniq_val_9,
max(case when rn = 10 then uniq_val else null end) as uniq_val_10
from(
select row_number() over (partition by common_id order by common_id, uniq_val) as rn,
common_id,
uniq_val
from my_table
order by common_id, uniq_val) x
group by common_id
order by common_id

SQL: Get multiple line entries linked to one item?

I have a table:
ID | ITEMID | STATUS | TYPE
1 | 123 | 5 | 1
2 | 123 | 4 | 2
3 | 123 | 5 | 3
4 | 125 | 3 | 1
5 | 125 | 5 | 3
Any item can have 0 to many entries in this table. I need a query that will tell me if an ITEM has all it's entries in either a state of 5 or 4. For example, in the above example, I would like to end up with the result:
ITEMID | REQUIREMENTS_MET
123 | TRUE --> true because all statuses are either 5 or 4
125 | FALSE --> false because it has a status of 3 and a status of 5.
If the 3 was a 4 or 5, then this would be true
What would be even better is something like this:
ITEMID | MET_REQUIREMENTS | NOT_MET_REQUIREMENTS
123 | 3 | 0
125 | 1 | 1
Any idea how to write a query for that?
Fast, short, simple:
SELECT itemid
,count(status = 4 OR status = 5 OR NULL) AS met_requirements
,count(status < 4 OR status > 5 OR NULL) AS not_met_requirements
FROM tbl
GROUP BY itemid
ORDER BY itemid;
Assuming all columns to be integer NOT NULL.
Builds on basic boolean logic:
TRUE OR NULL yields TRUE
FALSE OR NULL yields NULL
And NULL is not counted by count().
->SQLfiddle demo.
SELECT a.ID FROM (SELECT ID, MIN(STATUS) AS MINSTATUS, MAX(STATUS) AS MAXSTATUS FROM TABLE_NAME AS a GROUP BY ID)
WHERE a.MINSTATUS >= 4 AND a.MAXSTATUS <= 5
One way of doing this would be
SELECT t1.itemid, NOT EXISTS(SELECT 1
FROM mytable t2
WHERE itemid=t1.itemid
AND status NOT IN (4, 5)) AS requirements_met
FROM mytable t1
GROUP BY t1.itemid
UPDATE: for your updated requirement, you can have something like:
SELECT itemid,
sum(CASE WHEN status IN (4, 5) THEN 1 ELSE 0 END) as met_requirements,
sum(CASE WHEN status IN (4, 5) THEN 0 ELSE 1 END) as not_met_requirements
FROM mytable
GROUP BY itemid
simple one:
select
"ITEMID",
case
when min("STATUS") in (4, 5) and max("STATUS") in (4, 5) then 'True'
else 'False'
end as requirements_met
from table1
group by "ITEMID"
better one:
select
"ITEMID",
sum(case when "STATUS" in (4, 5) then 1 else 0 end) as MET_REQUIREMENTS,
sum(case when "STATUS" in (4, 5) then 0 else 1 end) as NOT_MET_REQUIREMENTS
from table1
group by "ITEMID";
sql fiddle demo
WITH dom AS (
SELECT DISTINCT item_id FROM items
)
, yes AS ( SELECT item_id, COUNT(*) AS good_count FROM items WHERE status IN (4,5) GROUP BY item_id
)
, no AS ( SELECT item_id, COUNT(*) AS bad_count FROM items WHERE status NOT IN (4,5) GROUP BY item_id
)
SELECT d.item_id
, COALESCE(y.good_count,0) AS good_count
, COALESCE(n.bad_count,0) AS bad_count
FROM dom d
LEFT JOIN yes y ON y.item_id = d.item_id
LEFT JOIN no n ON n.item_id = d.item_id
;
Can be done with an outer join, too:
WITH yes AS ( SELECT item_id, COUNT(*) AS good_count FROM items WHERE status IN (4,5) GROUP BY item_id)
, no AS ( SELECT item_id, COUNT(*) AS bad_count FROM items WHERE status NOT IN (4,5) GROUP BY item_id)
SELECT COALESCE(y.item_id, n.item_id) AS item_id
, COALESCE(y.good_count,0) AS good_count
, COALESCE(n.bad_count,0) AS bad_count
FROM yes y
FULL JOIN no n ON n.item_id = y.item_id
;
Nevermind, it was actually easy to do:
select ITEM_ID ,
sum (case when STATUS >= 3 then 1 else 0 end ) as met_requirements,
sum (case when STATUS < 3 then 1 else 0 end ) as not_met_requirements
from TABLE as d
group by ITEM_ID