SQL SELECT ID WHERE rows with the same ID have different Values - sql

I need some help creating a SQL statement across rows.
SELECT SZ.Stammindex AS ID, S.sEbene1, S.sEbene2, S.sEbene3
FROM SuchbaumZuordnung SZ
LEFT JOIN Suchbaum S
ON SZ.gSuchbaumID = S.gID
WHERE (S.sEbene1 IN ('Test1')
AND (S.sEbene2 IN ('Test2') OR S.sEbene2 IS NULL)
AND S.sEbene3 IS NULL)
As you can see in the screenshot, I selected ID=10004 and ID=10005. But actually I only want ID=10005 to show up. I am trying to filter across Rows as already mentioned.
My goal is to get all the IDs, where all the conditions are connected with "AND", something like this:
WHERE (sEbene1 IN ('Test1')
AND (sEbene2 IN ('Test2') *AND* sEbene2 IS NULL)
AND sEbene3 IS NULL)
But this will return nothing.
Edit
I hope you guys can help me.

I suspect that you want:
SELECT SZ.Stammindex AS ID
FROM SuchbaumZuordnung SZ
WHERE EXISTS (SELECT 1
FROM Suchbaum S
WHERE SZ.gSuchbaumID = S.gID AND
S.sEbene1 IN ('Test1') AND
sEbene2 IN ('Test2')
) AND
EXISTS (SELECT 1
FROM Suchbaum S
WHERE SZ.gSuchbaumID = S.gID AND
S.sEbene2 IS NULL AND
S.sEbene3 IS NULL
);
This is looking for two different rows in Suchbaum, each one matching one of the conditions.

Considering you only have 3 columns you want to check different rows, it seems like this would be easily serviced with a CTE and a Windowed COUNT:
WITH CTE AS(
SELECT SZ.Stammindex AS ID,
S.sEbene1, --Guessed the table alias
S.sEbene2, --Guessed the table alias
S.sEbene3, --Guessed the table alias
COUNT(DISTINCT CONCAT(ISNULL(S.S.sEbene1,'-'),ISNULL(S.sEbene2,'-'),ISNULL(S.sEbene3,'-'))) OVER (PARTITION BY SZ.Stammindex) AS DistinctRows
FROM SuchbaumZuordnung SZ
LEFT JOIN Suchbaum S ON SZ.gSuchbaumID = S.gID) --This was missing the ON in your sample
SELECT C.Stammindex,
C.sEbene1,
C.sEbene2,
C.sEbene3
FROM CTE C
WHERE C.DistinctRows > 1;
If it's purely where an ID has more than 1 rows (which could be identical) then you can just use COUNT:
WITH CTE AS(
SELECT SZ.Stammindex AS ID,
S.sEbene1, --Guessed the table alias
S.sEbene2, --Guessed the table alias
S.sEbene3, --Guessed the table alias
COUNT(*) OVER (PARTITION BY SZ.Stammindex) AS [Rows]
FROM SuchbaumZuordnung SZ
LEFT JOIN Suchbaum S ON SZ.gSuchbaumID = S.gID)
SELECT C.Stammindex,
C.sEbene1,
C.sEbene2,
C.sEbene3
FROM CTE C
WHERE C.[Rows] > 1;

Related

Find matching sets in a database table

I have a junction table in a (SQL Server 2014) database with columns FirstID and SecondID. Given a specific FirstID, I'd like to find all other FirstIDs from the table that have an equivalent set of SecondIDs (even if that set is empty).
Sample Data:
FirstId SecondId
1 1
1 2
2 3
3 1
3 2
... ...
In the case of the sample data, if I specified FirstID = 1, then I'd expect 3 to appear in the result set.
I've tried the following so far, which works pretty well except for empty sets:
SELECT FirstSecondEqualSet.FirstId
FROM FirstSecond FirstSecondOriginal
INNER JOIN FirstSecond FirstSecondEqualSet ON FirstSecondOriginal.SecondId = FirstSecondEqualSet.SecondId
WHERE FirstSecondOriginal.FirstId = #FirstId 
AND FirstSecondEqualSet.FirstId != #FirstId
GROUP BY FirstSecondEqualSet.FirstId
HAVING COUNT(1) = (SELECT COUNT(1) FROM FirstSecond WHERE FirstSecond.FirstId = #FirstId)
I think it's somehow related to Relational Division with no Remainder (RDNR). See this great article by Dwain Camps for reference.
DECLARE #firstId INT = 1
SELECT
f2.FirstId
FROM FirstSecond f1
INNER JOIN FirstSecond f2
ON f2.SecondId = f1.SecondId
AND f1.FirstId <> f2.FirstId
WHERE
f1.FirstId = #firstId
GROUP BY f2.FirstId
HAVING COUNT(*) = (SELECT COUNT(*) FROM FirstSecond WHERE FirstId = #firstId)
Here is one approach. It counts the number of values for each firstid and then joins on the secondid.
select fs2.firstid
from (select fs1.*, count(*) over (partition by firstid) as numseconds
from firstsecond fs1
where fs1.firstid = #firstid
) fs1 join
(select fs2.*, count(*) over (partition by firstid) as numseconds
from firstsecond fs2
) fs2
on fs1.secondid = fs2.secondid and fs1.numseconds = fs2.numseconds
group by fs2.firstid
having count(*) = max(fs1.numseconds);

SQL CASE / WHEN evaluating to two possible values

I have a list of items, Each item will have one of two options A or B. If the item has both options then A will always have a recipe of S (Standard) and B will always have a recipe of O (optional), However if there is ONLY the option B then B will have a recipe of S. I am a novice and can not begin to think on how to write this.
Item__Option__Recipe
1____A______S
1____B______O
2____B______S
3____A______S
3____B______O
4____A______S
5____A______S
6____B______S
So A will always be S and B will be S unless there is an A and then it will be O? If that's the logic then this should work:
SELECT
A.Item,
A.Option,
CASE
WHEN Option = 'A' THEN 'S' // A will always be S
WHEN Option = `B`
CASE
WHEN EXISTS(SELECT null FROM table WHERE Item = A.Item AND Option = 'A')
THEN 'O'
ELSE 'S'
END
END AS Recipe
FROM table
This assumes SQL Server syntax (for the table variable) but otherwise uses fairly standard (for these days) Common Table Expressions and a ROW_NUMBER():
declare #t table (Item int,[Option] char(1))
insert into #t(Item,[Option]) values
(1,'A'),(1,'B'),
(2,'B'),
(3,'A'),(3,'B'),
(4,'A'),
(5,'A'),
(6,'B')
;With Recipes as (
select 1 as rn,'S' as Recipe union all
select 2,'O'
), Ordered as (
select Item,[Option],
ROW_NUMBER() OVER (
PARTITION BY Item
ORDER BY [Option]
) as rn
from #t
)
select
Item,[Option],Recipe
from
Ordered o
inner join
Recipes r
on
o.rn = r.rn
order by Item,[Option]
And produces the result set you've shown.

Reuse subquery result in WHERE-Clause for INSERT

i am using Microsoft SQL Server 2008
i would like to save the result of a subquery to reuse it in a following subquery.
Is this possible?
What is best practice to do this? (I am very new to SQL)
My query looks like:
INSERT INTO [dbo].[TestTable]
(
[a]
,[b]
)
SELECT
(
SELECT TOP 1 MAT_WS_ID
FROM #TempTableX AS X_ALIAS
WHERE OUTERBASETABLE.LT_ALL_MATERIAL = X_ALIAS.MAT_RM_NAME
)
,(
SELECT TOP 1 MAT_WS_NAME
FROM #TempTableY AS Y_ALIAS
WHERE Y_ALIAS.MAT_WS_ID = MAT_WS_ID
--(
--SELECT TOP 1 MAT_WS_ID
--FROM #TempTableX AS X_ALIAS
--WHERE OUTERBASETABLE.LT_ALL_MATERIAL = X_ALIAS.MAT_RM_NAME
--)
)
FROM [dbo].[LASERTECHNO] AS OUTERBASETABLE
My question is:
Is this correct what i did.
I replaced the second SELECT Statement in the WHERE-Clause for [b] (which is commented out and exactly the same as for [a]), with the result of the first SELECT Statement of [a] (=MAT_WS_ID).
It seems to give the right results.
But i dont understand why!
I mean MAT_WS_ID is part of both temporary tables X_ALIAS and Y_ALIAS.
So in the SELECT statement for [b], in the scope of the [b]-select-query, MAT_WS_ID could only be known from the Y_ALIAS table. (Or am i wrong, i am more a C++, maybe the scope things in SQL and C++ are totally different)
I just wannt to know what is the best way in SQL Server to reuse an scalar select result.
Or should i just dont care and copy the select for every column and the sql server optimizes it by its own?
One approach would be outer apply:
SELECT mat.MAT_WS_ID
, (
SELECT TOP 1 MAT_WS_NAME
FROM #TempTableY AS Y_ALIAS
WHERE Y_ALIAS.MAT_WS_ID = mat.MAT_WS_ID
)
FROM [dbo].[LASERTECHNO] AS OUTERBASETABLE
OUTER APPLY
(
SELECT TOP 1 MAT_WS_ID
FROM #TempTableX AS X_ALIAS
WHERE OUTERBASETABLE.LT_ALL_MATERIAL = X_ALIAS.MAT_RM_NAME
) as mat
You could rank rows in #TempTableX and #TempTableY partitioning them by MAT_RM_NAME in the former and by MAT_WS_ID in the latter, then use normal joins with filtering by rownum = 1 in both tables (rownum being the column containing the ranking numbers in each of the two tables):
WITH x_ranked AS (
SELECT
*,
rownum = ROW_NUMBER() OVER (PARTITION BY MAT_RM_NAME ORDER BY (SELECT 1))
FROM #TempTableX
),
y_ranked AS (
SELECT
*,
rownum = ROW_NUMBER() OVER (PARTITION BY MAT_WS_ID ORDER BY (SELECT 1))
FROM #TempTableY
)
INSERT INTO dbo.TestTable (a, b)
SELECT
x.MAT_WS_ID,
y.MAT_WS_NAME
FROM dbo.LASERTECHNO t
LEFT JOIN x_ranked x ON t.LT_ALL_MATERIAL = x.MAT_RM_NAME AND x.rownum = 1
LEFT JOIN y_ranked y ON x.MAT_WS_ID = y.MAT_WS_ID AND y.rownum = 1
;
The ORDER BY (SELECT 1) bit is a trick to specify an indeterminate ordering, which, accordingly, would result in indeterminate rownum = 1 rows picked by the query. That is to more or less duplicate your TOP 1 without an explicit order, but I would recommend you to specify a more sensible ORDER BY clause to make the results more predictable.

SQL Server : convert sub select query to join

I have 2 two tables questionpool and question where question is a many to one of question pool. I have created a query using a sub select query which returns the correct random results but I need to return more than one column from the question table.
The intent of the query is to return a random test from the 'question' table for each 'QuizID' from the 'Question Pool' table.
SELECT QuestionPool.QuestionPoolID,
(
SELECT TOP (1) Question.QuestionPoolID
FROM Question
WHERE Question.GroupID = QuestionPool.QuestionPoolID
ORDER BY NEWID()
)
FROM QuestionPool
WHERE QuestionPool.QuizID = '5'
OUTER APPLY is suited to this:
Select *
FROM QuestionPool
OUTER APPLY
(
SELECT TOP 1 *
FROM Question
WHERE Question.GroupID = QuestionPool.QuestionPoolID
ORDER BY NEWID()
) x
WHERE QuestionPool.QuizID = '5'
Another example of OUTER APPLY use http://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html
Live test: http://www.sqlfiddle.com/#!3/d8afc/1
create table m(i int, o varchar(10));
insert into m values
(1,'alpha'),(2,'beta'),(3,'delta');
create table x(i int, j varchar, k varchar(10));
insert into x values
(1,'a','hello'),
(1,'b','howdy'),
(2,'x','great'),
(2,'y','super'),
(3,'i','uber'),
(3,'j','neat'),
(3,'a','nice');
select m.*, '' as sep, r.*
from m
outer apply
(
select top 1 *
from x
where i = m.i
order by newid()
) r
Not familiar with SQL server, but I hope this would do:
Select QuestionPool.QuestionPoolID, v.QuestionPoolID, v.xxx -- etc
FROM QuestionPool
JOIN
(
SELECT TOP (1) *
FROM Question
WHERE Question.GroupID = QuestionPool.QuestionPoolID
ORDER BY NEWID()
) AS v ON v.QuestionPoolID = QuestionPool.QuestionPoolID
WHERE QuestionPool.QuizID = '5'
Your query appears to be bringing back an arbitrary Question.QuestionPoolId for each QuestionPool.QuestionPoolId subject to the QuizId filter.
I think the following query does this:
select qp.QuestionPoolId, max(q.QuestionPoolId) as any_QuestionPoolId
from Question q join
qp.QuestionPoolId qp
on q.GroupId = qp.QuestionPoolId
WHERE QuestionPool.QuizID = '5'
group by qp.QuestionPoolId
This returns a particular question.
The following query would allow you to get more fields:
select qp.QuestionPoolId, q.*
from (select q.*, row_number() over (partition by GroupId order by (select NULL)) as randrownum
from Question q
) join
(select qp.QuestionPoolId, max(QuetionPool qp
on q.GroupId = qp.QuestionPoolId
WHERE QuestionPool.QuizID = '5' and
randrownum = 1
This uses the row_number() to arbitrarily enumerate the rows. The "Select NULL" provides the random ordering (alternatively, you could use "order by GroupId".
Common Table Expressions (CTEs) are rather handy for this type of thing...
http://msdn.microsoft.com/en-us/library/ms175972(v=sql.90).aspx

Select Statement Return 0 if Null

I have the following query
SELECT ProgramDate, [CountVal]= COUNT(ProgramDate)
FROM ProgramsTbl
WHERE (Type = 'Type1' AND ProgramDate = '10/18/11' )
GROUP BY ProgramDate
What happens is that if there is no record that matches the Type and ProgramDate, I do not get any records returned.
What I like to have outputted in the above is something like the following if there is no values returned. Notice how for the CountVal we have 0 even if there are no records returned that fit the match condition:
ProgramDate CountVal
10/18/11 0
This is a little more complicated than you would like however, it is very possible. You will first have to create a temporary table of dates. For example, the query below creates a range of dates from 2011-10-11 to 2011-10-20
CREATE TEMPORARY TABLE date_stamps AS
SELECT (date '2011-10-10' + new_number) AS date_stamp
FROM generate_series(1, 10) AS new_number;
Using this temporary table, you can select from it and left join your table ProgramsTbl. For example
SELECT date_stamp,COUNT(ProgramDate)
FROM date_stamps
LEFT JOIN ProgramsTbl ON ProgramsTbl.ProgramDate = date_stamps.date_stamp
WHERE Type = 'Type1'
GROUP BY ProgramDate;
Select ProgramDate, [CountVal]= SUM(occur)
from
(
SELECT ProgramDate, 1 occur
FROM ProgramsTbl
WHERE (Type = 'Type1' AND ProgramDate = '10/18/11' )
UNION
SELECT '10/18/11', 0
)
GROUP BY ProgramDate
Because each SELECT statement is really building a table of records you can use a SELECT query to build a table with both the program count and a default count of zero. This would require two SELECT queries (one to get the actual count, one to get the default count) and using a UNION to combine the two SELECT results into a single table.
From there you can SELECT from the UNIONed table to sum the CountVals (if the programDate occurs in the ProgramTable the CountVal will be
CountVal of the first query if it exists(>0) + CountVal of the second query (=0)).
This way even if there are no records for the desired programDate in ProgramTable you will get a record back indicating a count of 0.
This would look like:
SELECT ProgramDate, SUM(CountVal)
FROM
(SELECT ProgramDate, COUNT(*) AS CountVal
FROM ProgramsTbl
WHERE (Type = 'Type1' AND ProgramDate = '10/18/11' )
UNION
SELECT '10/18/11' AS ProgramDate, 0 AS CountVal) T1
Here's a solution that works on SQL Server; not sure about other db platforms:
DECLARE #Type VARCHAR(5) = 'Type1'
, #ProgramDate DATE = '10/18/2011'
SELECT pt.ProgramDate
, COUNT(pt2.ProgramDate)
FROM ( SELECT #ProgramDate AS ProgramDate
, #Type AS Type
) pt
LEFT JOIN ProgramsTbl pt2 ON pt.Type = pt2.Type
AND pt.ProgramDate = pt2.ProgramDate
GROUP BY pt.ProgramDate
Grunge but simple and efficient
SELECT '10/18/11' as 'Program Date', count(*) as 'count'
FROM ProgramsTbl
WHERE Type = 'Type1' AND ProgramDate = '10/18/11'
Try something along these lines. This will establish a row with a date of 10/18/11 that will definitely return. Then you left join to your actual data to get your desired count (which can now return 0 if there are no corresponding rows).
To do this for more than 1 date, you'd want to build a Date table that holds a list of all dates you want to query (so substitute the "select '10/18/11'" with "select Date from DateTbl").
SELECT ProgDt.ProgDate, [CountVal]= COUNT(ProgramsTbl.ProgramDate)
FROM (SELECT '10/18/11' as 'ProgDate') ProgDt
LEFT JOIN ProgramsTbl
ON ProgDt.ProgDate = ProgramsTbl.ProgramDate
WHERE (Type = 'Type1')
GROUP BY ProgDt.ProgDate
To create a date table that you can use for querying, do this (assumes SQL Server 2005+):
create table Dates (MyDate datetime)
go
insert into Dates
select top 100000 row_number() over (order by s1.name)
from master..spt_values s1, master..spt_values s2
go