How to make MS SQL select on this - sql

I have MS SQL database table like this
TableA
+----+-----------+--------+
|ID | Table2_FK | Value |
+----+-----------+--------+
|1 | 7 | X |
|2 | 7 | Y |
|3 | 8 | X |
|4 | 8 | Z |
|5 | 9 | W |
|6 | 9 | M |
|5 | 10 | X |
|6 | 10 | Z |
+----+-----------+--------+
I want to make query to get list of Table2_FKs if I pass X and Z in query for Values. In this example 8 and 10 is the result
It can be more than 2 values

You can do this with group by and having:
select table2_fk
from t
where value in ('X', 'Z')
group by table2_fk
having count(*) = 2;
If the values can be duplicated for a key value, then use count(distinct value) = 2. The "2" is the number of values in the IN list.

Try this:
select distinct Table2_FK
from TableA
where value in ('X','Z');

You can use query as below:
Select distinct table2_fk from (
Select *, Ct = count(id) over (partition by table2_fk) from yourtable
) a
Where a.[Value] in ('X','Z') and a.Ct >= 2

you can use a query like below
select
distinct Table2_FK
from TableA a
where exists (
select 1 1 from TableA b where b.value ='X' and a.Table2_FK =b.Table2_FK
)
and exists (
select 1 1 from TableA c where c.value ='Z' and a.Table2_FK =c.Table2_FK
)

Related

Grouping over the subquery in SQL on unique id

I've a query which gets results from temp table. It has aggregate columns which are derived from the temp table:
SELECT
DISTINCT
SUM(a),
SUM(b),
c,
d,
id1
FROM
#tmpTable
.
.
.
join with many other tables
I want to now get the SUM of columns c & d returned from the query along with all other columns. It will be group by id1. It should look something like:
+--------------------------------------------
||Sum(A) |Sum(B)|C |D |id1 |
|-------------------------------------------+
| 12 |34 |1 | 3 | 1 |
|-------------------------------------------+
| 22 |37 | 2 | 4 | 2 |
|-------------------------------------------+
| 33 | 55 | 3 | 5 | 1 |
|-------------------------------------------+
| 44 | 25 | 5 | 6 | 2 |
+---------+------+------+---------+---------+
Final result should be this:
+--------------------------------------------
||Sum(A) |Sum(B)|Sum(C)|Sum(d) |id1 |
|-------------------------------------------+
| 12 |34 |4 | 8 | 1 |
|-------------------------------------------+
| 22 |37 | 7 | 10 | 2 |
|-------------------------------------------+
| 33 | 55 | 4 | 8 | 1 |
|-------------------------------------------+
| 44 | 25 | 7 | 10 | 2 |
+---------+------+------+---------+---------+
select
x.sum_a,
x.sum_b,
x.sum_c,
x.sum_d,
t.id1
from
tmpTable t
join
(
select
id1,
sum(A) as sum_a,
sum(B) as sum_b,
sum(C) as sum_c,
sum(D) as sum_d
from
tmpTable
group by
id1
) x on t.id1 = x.id1
Seeing as you have different grouping criteria for A and B, you can group them separately to C and D. The below (using common table expression) might start you on the right track:
; with SummaryValues AS
(
select id1, sum(C) as SumC, SUM(D) as SumD
from #SourceTable
group by id1
)
select SUM(st.A), SUM(st.b), sv.SumC, sv.SumD, st.id1
from #SourceTable st
inner join SummaryValues sv
on st.id1 = sv.id1
group by <whatever grouping you are using>
If your current real query is summing up a and b the way you want and generating that first sample output, maybe something like:
SELECT DISTINCT
SUM(a),
SUM(b),
SUM(c) OVER (PARTITION BY id1),
SUM(d) OVER (PARTITION BY id1),
id1
FROM
#tmpTable
.
.
.
join with many other tables
to get the second one.

Getting the last updated name

I am having a table having records like this:
+------+------+
| ID | name |
+------+------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | A |
| 5 | B |
| 6 | A |
| 7 | A |
| 8 | A |
+------+------+
I need to get value of A after it was last updated from a different value, for example here it would be the row at ID 6.
Try this query (MySQL syntax):
select min(ID)
from records
where name = 'A'
and ID >=
(
select max(ID)
from records
where name <> 'A'
);
Illustration:
select * from records;
+------+------+
| ID | name |
+------+------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | A |
| 5 | B |
| 6 | A |
| 7 | A |
| 8 | A |
+------+------+
-- run query:
+---------+
| min(ID) |
+---------+
| 6 |
+---------+
Using the Lag function...
SELECT Max([ID])
FROM (SELECT [name], [ID],
Lag([name]) OVER (ORDER BY [ID]) AS PrvVal
FROM tablename) tbl
WHERE [name] = 'A'
AND prvval <> 'A'
Online Demo: http://www.sqlfiddle.com/#!18/a55eb/2/0
If you want to get the whole row, you can do this...
SELECT Top 1 *
FROM (SELECT [name], [ID],
Lag([name]) OVER (ORDER BY [ID]) AS PrvVal
FROM tablename) tbl
WHERE [name] = 'A' AND prvval <> 'A'
ORDER BY [ID] DESC
Online Demo: http://www.sqlfiddle.com/#!18/a55eb/22/0
The ANSI SQL below uses a self-join on the previous id.
And the where-clause gets those with a name that's different from the previous.
select max(t1.ID) as ID
from YourTable as t1
left join YourTable as t2 on t1.ID = t2.ID+1
where (t1.name <> t2.name or t2.name is null)
and t1.name = 'A';
It should work on most RDBMS, including MS Sql Server.
Note that with the ID+1 that there's an assumption that are no gaps between the ID's.

SQL query to select rows that based on some value of other rows in the same table

Assume I have the following activity table:
id type value flag
------|------|-------|------|
1 |A | 13 | 1 |
2 |B | 29 | |
3 |C | 11 | |
4 |A | 78 | |
5 |X | 91 | |
6 |C | 2 | |
7 |B | 14 | 1 |
I want to select rows that any row with the same type has the flag 1 or the type is X. In this case, I would like to get:
id type value flag
------|------|-------|------|
1 |A | 13 | 1 |
4 |A | 78 | |
2 |B | 29 | |
7 |B | 14 | 1 |
5 |X | 91 | |
I can do an INNER JOIN to get the result like:
SELECT
"activities".*
FROM "activities"
INNER JOIN activities AS a2
ON activities.type = a2.type
AND a2.flag = 1
OR activities.type = 'X'
AND activities.id = a2.id
However, this is slow when the amount of records becomes large, especially when I need to do a COUNT(*) on top of the result. I wonder how I can rewrite the query and make it more performant.
I am using Postgres. Thanks!
Here are 3 ways to do it. Choose one which will be work faster on your dataset. To speed up these queries you have to create indexes on type and flag fields.
select a.* from activities a
JOIN (select distinct type FROM activities where type='X' or flag=1) t
ON a.type=t.type;
select a.* from activities a
where type='X'
or EXISTS(SELECT * FROM activities WHERE type=a.type AND flag=1);
select a.* from activities a
where type='X'
or type IN (SELECT type FROM activities WHERE flag=1)
SQLFiddle demo
Try this solution
SELECT id,
type,
value,
flag
FROM (SELECT *,
SUM(CASE WHEN flag = 1 THEN 1 ELSE 0 END) OVER (PARTITION BY type) AS flag_occurence_for_type
FROM activities) t
WHERE type = 'X' OR flag_occurence_for_type > 0

Many to one relation, select only rows which ancestor met all criteria

First I will show You architecture of tables.
Table "public.questionare"
Column | Type |
--------------------+-----------------------+
id | integer |
Table "public.questionareacceptance"
Column | Type |
-------------------------+-----------------------+
id | integer |
questionare_id | integer |
accept_id | integer |
impact_id | integer |
Table questionareacceptance contains:
id | questionare_id | accept_id| impact_id |
----+----------------+----------+------------------+
1 |1 |1 | |
2 |1 |1 | 1 |
3 |1 |1 | 1 |
4 |2 | | 1 |
5 |3 |1 | 1 |
6 |4 |1 | 1 |
7 |4 |1 | 1 |
What I am trying to get is a list of questionare ID where in each questionareacceptance fields accept_id and impact_id are not NULL
My query looks like:
SELECT q.id AS quest,
qa.id AS accepted
FROM questionare q,
questionareacceptance qa
WHERE q.id = qa.questionare_id
AND qa.accept_id IS NOT NULL
AND qa.impact_id IS NOT NULL;
But the result is as fallows:
quest | accepted |
--------------------+-----------------------+
1 |1 |
1 |2 |
1 |3 |
2 |4 |
3 |5 |
4 |6 |
4 |7 |
But the result that should be returned are only 3 and 4 others have impact_id or accept_id null.
Can anyone point me where I am doing the mistake?
your query could be written with not exists:
select
q.id as quest, qa.id as accepted
from questionare as q
inner join questionareacceptance as qa on qa.questionare_id = q.id
where
not exists (
select *
from questionareacceptance as tqa
where
tqa.questionare_id = q.id and
(tqa.accept_id is null or tqa.impact_id is null)
)
but I think faster one would using window functions:
with cte as (
select
q.id as quest, qa.id as accepted,
sum(case when qa.accept_id is not null and qa.impact_id is not null then 1 else 0 end) over(partition by q.id) as cnt1,
count(*) over(partition by q.id) as cnt2
from questionare as q
inner join questionareacceptance as qa on qa.questionare_id = q.id
)
select quest, accepted
from cte
where cnt1 = cnt2
actually looks like you don't need join at all:
with cte as (
select
qa.questionare_id as quest, qa.id as accepted,
sum(case when qa.accept_id is not null and qa.impact_id is not null then 1 else 0 end) over(partition by qa.questionare_id) as cnt1,
count(*) over(partition by qa.questionare_id) as cnt2
from questionareacceptance as qa
)
select quest, accepted
from cte
where cnt1 = cnt2;
sql fiddle demo

How can I count how much each different values occur in each column independently?

Given table mytable with two columns letter and num
letter|num
------+------
a |1
a |1
b |1
b |2
I tried doing
SELECT letter, count(letter), num, count(num) from mytable group BY letter, num;
but it returns
letter|count|num |count
------+-----+------+-----
b | 1 | 1 | 1
a | 2 | 1 | 2
b | 1 | 2 | 1
whereas I wanted
letter|count|num |count
------+-----+------+-----
a | 2 | 1 | 3
b | 2 | 2 | 1
Is this possible to do, and can I do it in one query?
You could change it to 2 separate aggregates like this.
SELECT 'letter' as type, letter AS item, count(letter)
from mytable group BY letter
UNION ALL --CAST to be same type as letter
SELECT 'num', CAST(num AS varchar(100)), count(num)
from mytable group BY num;