Using EXISTS as a column in TSQL - tsql

Is it possible to use the value of EXISTS as part of a query?
(Please note: unfortunately due to client constraints, I need SQLServer 2005 compatible answers!)
So when returning a set of results, one of the columns is a boolean value which states whether the subquery would return any rows.
For example, I want to return a list of usernames and whether a different table contains any rows for each user. The following is not syntactically correct, but hopefully gives you an idea of what I mean...
SELECT T1.[UserName],
(EXISTS (SELECT *
FROM [AnotherTable] T2
WHERE T1.[UserName] = T2.[UserName])
) AS [RowsExist]
FROM [UserTable] T1
Where the resultant set contains a column called [UserName] and boolean column called [RowsExist].
The obvious solution is to use a CASE, such as below, but I wondered if there was a better way of doing it...
SELECT T1.[UserName],
(CASE (SELECT COUNT(*)
FROM [AnotherTable] T2
WHERE T1.[UserName] = T2.[UserName]
)
WHEN 0 THEN CAST(0 AS BIT)
ELSE CAST(1 AS BIT) END
) AS [RowsExist]
FROM [UserTable] T1

Your second query isn't valid syntax.
SELECT T1.[UserName],
CASE
WHEN EXISTS (SELECT *
FROM [AnotherTable] T2
WHERE T1.[UserName] = T2.[UserName]) THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT)
END AS [RowsExist]
FROM [UserTable] T1
Is generally fine and will be implemented as a semi join.
The article Subqueries in CASE Expressions discusses this further.
In some cases a COUNT query can actually perform better though as discussed here

I like the other guys sql better but i just wrote this:
with bla as (
select t2.username, isPresent=CAST(1 AS BIT)
from t2
group by t2.username
)
select t1.*, isPresent = isnull(bla.isPresent, CAST(0 AS BIT))
from t1
left join blah on t1.username=blah.username

From what you wrote here I would alter your first query into something like this
SELECT
T1.[UserName], ISNULL(
(
SELECT
TOP 1 1
FROM [AnotherTable]
WHERE EXISTS
(
SELECT
1
FROM [AnotherTable] AS T2
WHERE T1.[UserName] = T2.[UserName]
)
), 0)
FROM [UserTable] T1
But actually if you use TOP 1 1 you would not need EXISTS, you could also write
SELECT
T1.[UserName], ISNULL(
(
SELECT
TOP 1 1
FROM [AnotherTable] AS T2
WHERE T1.[UserName] = T2.[UserName]
), 0)
FROM [UserTable] T1

Related

TSQL - in a string, replace a character with a fixed one every 2 characters

I can't replace every 2 characters of a string with a '.'
select STUFF('abcdefghi', 3, 1, '.') c3,STUFF('abcdefghi', 5, 1,
'.') c5,STUFF('abcdefghi', 7, 1, '.') c7,STUFF('abcdefghi', 9, 1, '.')
c9
if I use STUFF I should subsequently overlap the strings c3, c5, c7 and c9. but I can't find a method
can you help me?
initial string:
abcdefghi
the result I would like is
ab.de.gh.
the string can be up to 50 characters
Create a numbers / tally / digits table, if you don't have one already, then you can use this to target each character position:
with digits as ( /* This would be a real table, here it's just to test */
select n from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10))x(n)
), t as (
select 'abcdefghi' as s
)
select String_Agg( case when d.n%3 = 0 then '.' else Substring(t.s, d.n, 1) end, '')
from t
cross apply digits d
where d.n <Len(t.s)
Using for xml with existing table
with digits as (
select n from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10))x(n)
),
r as (
select t.id, case when d.n%3=0 then '.' else Substring(t.s, d.n, 1) end ch
from t
cross apply digits d
where d.n <Len(t.s)
)
select result=(select '' + ch
from r r2
where r2.id=r.id
for xml path('')
)
from r
group by r.id
You can try it like this:
Easiest might be a quirky update ike here:
DECLARE #string VARCHAR(100)='abcdefghijklmnopqrstuvwxyz';
SELECT #string = STUFF(#string,3*A.pos,1,'.')
FROM (SELECT TOP(LEN(#string)/3) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) A(pos);
SELECT #string;
Better/Cleaner/Prettier was a recursive CTE:
We use a declared table to have some tabular sample data
DECLARE #tbl TABLE(ID INT IDENTITY, SomeString VARCHAR(200));
INSERT INTO #tbl VALUES('')
,('a')
,('ab')
,('abc')
,('abcd')
,('abcde')
,('abcdefghijklmnopqrstuvwxyz');
--the query
WITH recCTE AS
(
SELECT ID
,SomeString
,(LEN(SomeString)+1)/3 AS CountDots
,1 AS OccuranceOfDot
,SUBSTRING(SomeString,4,LEN(SomeString)) AS RestString
,CAST(LEFT(SomeString,2) AS VARCHAR(MAX)) AS Growing
FROM #tbl
UNION ALL
SELECT t.ID
,r.SomeString
,r.CountDots
,r.OccuranceOfDot+2
,SUBSTRING(RestString,4,LEN(RestString))
,CONCAT(Growing,'.',LEFT(r.RestString,2))
FROM #tbl t
INNER JOIN recCTE r ON t.ID=r.ID
WHERE r.OccuranceOfDot/2<r.CountDots-1
)
SELECT TOP 1 WITH TIES ID,Growing
FROM recCTE
ORDER BY ROW_NUMBER() OVER(PARTITION BY ID ORDER BY OccuranceOfDot DESC);
--the result
1
2 a
3 ab
4 ab
5 ab
6 ab.de
7 ab.de.gh.jk.mn.pq.st.vw.yz
The idea in short
We use a recursive CTE to walk along the string
we add the needed portion together with a dot
We stop, when the remaining length is to short to continue
a little magic is the ORDER BY ROW_NUMBER() OVER() together with TOP 1 WITH TIES. This will allow all first rows (frist per ID) to appear.

"Subquery returns more than 1 value" when trying to INSERT

I'm trying to INSERT into a table a column that is part of another column in another table using TSQL, but I get the error stating that there is more than one value returned when I used that subquery as an expression. I understand what causes the error, but I can't seem to think of a way to make it produce what I want.
I'm trying to do something similar to:
A.Base B.Reference C.Wanted
--- ---- ----
abcdaa aa abcdaa
bcdeab bb cdefbb
cdefbb cc efghcc
defgbc ddd fghddd
efghcc
fghddd
So I'm using the code:
INSERT INTO C ( [Some other column], Wanted )
SELECT
A.[Some other column],
, CASE
WHEN LEN( B.Reference ) = 2 THEN
( SELECT A.Base FROM A WHERE RIGHT( A.Base, 2 ) =
( SELECT B.Reference FROM B WHERE LEN( B.Reference ) = 2 )
)
WHEN LEN( B.Reference ) = 3 THEN
( SELECT A.Base FROM A WHERE RIGHT( A.Base, 3 ) =
( SELECT B.Reference FROM B WHERE LEN( B.Reference ) = 3 )
)
END
FROM
A
, B
Which will return me the "more than 1 value" error. Honestly, I'm probably making this way more convoluted than it needs to be, but I've been staring at these tables for a while now.
I hope I'm getting the idea across as to what I'm trying to do.
If you know the records aren't duplicate, and you are sure your JOIN between A and B works (as Martin mentioned) can't you just select distinct to return just the unique records?
I'd try it like this:
--Create a mockup with declared table variables and test data
DECLARE #tblA TABLE(someColumnInA VARCHAR(100));
DECLARE #tblB TABLE(someColumnInB VARCHAR(100));
DECLARE #tblC TABLE(someColumnInC VARCHAR(100));
INSERT INTO #tblA VALUES
('abcdaa')
,('bcdeab')
,('cdefbb')
,('defgbc')
,('efghcc')
,('fghddd')
INSERT INTO #tblB VALUES
('aa')
,('bb')
,('cc')
,('ddd');
--The query
INSERT INTO #tblC(someColumnInC)
SELECT SomeColumnInA
FROM #tblA a
WHERE EXISTS(SELECT 1 FROM #tblB b WHERE a.someColumnInA LIKE '%' + b.SomeColumnInB + '%');
SELECT * FROM #tblC;
The idea in short:
After creating a mockup (please do this next time in advance) we use a query to insert all values from #tblA into #tblC as long as there exists any value in #tblB, which is part of the current value in #tblA.
How about doing something like this?
select *
from A
where RIGHT(A.Base,2) IN (select B.Reference FROM B WHERE LEN(B.Reference) = 2)
UNION ALL
select *
from A
where RIGHT(A.Base,3) IN (select B.Reference FROM B WHERE LEN(B.Reference) = 3)

How to translate SQL to DAX, Need to add FILTER

I want to create calculated table that will summarize In_Force Premium from existing table fact_Premium.
How can I filter the result by saying:
TODAY() has to be between `fact_Premium[EffectiveDate]` and (SELECT TOP 1 fact_Premium[ExpirationDate] ORDE BY QuoteID DESC)
In SQL I'd do that like this:
`WHERE CONVERT(date, getdate()) between CONVERT(date, tblQuotes.EffectiveDate)
and (
select top 1 q2.ExpirationDate
from Table2 Q2
where q2.ControlNo = Table1.controlno
order by quoteid` desc
)
Here is my DAX statement so far:
In_Force Premium =
FILTER(
ADDCOLUMNS(
SUMMARIZE(
//Grouping necessary columns
fact_Premium,
fact_Premium[QuoteID],
fact_Premium[Division],
fact_Premium[Office],
dim_Company[CompanyGUID],
fact_Premium[LineGUID],
fact_Premium[ProducerGUID],
fact_Premium[StateID],
fact_Premium[ExpirationDate]
),
"Premium", CALCULATE(
SUM(fact_Premium[Premium])
),
"ControlNo", CALCULATE(
DISTINCTCOUNT(fact_Premium[ControlNo])
)
), // Here I need to make sure TODAY() falls between fact_Premium[EffectiveDate] and (SELECT TOP 1 fact_Premium[ExpirationDate] ORDE BY QuoteID DESC)
)
Also, what would be more efficient way, to create calculated table from fact_Premium or create same table using sql statement (--> Get Data--> SQL Server) ?
There are 2 potential ways in T-SQL to get the next effective date. One is to use LEAD() and another is to use an APPLY operator. As there are few facts to work with here are samples:
select *
from (
select *
, lead(EffectiveDate) over(partition by CompanyGUID order by quoteid desc) as NextEffectiveDate
from Table1
join Table2 on ...
) d
or
select table1.*, oa.NextEffectiveDate
from Table1
outer apply (
select top(1) q2.ExpirationDate AS NextEffectiveDate
from Table2 Q2
where q2.ControlNo = Table1.controlno
order by quoteid desc
) oa
nb. an outer apply is a little similar to a left join in that it will allow rows with a NULL to be returned by the query, if that is not needed than use cross apply instead.
In both these approaches you may refer to NextEffectiveDate in a final where clause, but I would prefer to avoid using the convert function if that is feasible (this depends on the data).

How to use parameters in a SQL query with NOT EXISTS?

How can I change following query, so that I'm able to parameterize the SparePartNames?
It returns all ID's of repairs where not all mandatory spareparts were changed, in other words where at least one part is missing.
Note that the number of spareparts might change in future not only the names. Is it possible without using a stored procedure with dynamic SQL? If not, how could this SP look like?
Edit: Note that i do not need to know how to pass a list/array as parameter, this is asked myriads of time on SO. I've also already a Split table-valued-function. I'm just wondering how i could rewrite the query to be able to join(or whatever) with a list of mandatory parts, so that i'll find all records where at least one part is missing. So is it possible to use a varchar-parameter like '1264-3212,1254-2975' instead of a list of NOT EXISTS? Sorry for the confusion if it was not clear in the first place.
SELECT d.idData
FROM tabData d
INNER JOIN modModel AS m ON d.fiModel = m.idModel
WHERE (m.ModelName = 'MT27I')
AND (d.fiMaxServiceLevel >= 2)
AND (d.Manufacture_Date < '20120511')
AND (NOT EXISTS
(SELECT NULL
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON sp.idSparePart = td.fiSparePart
WHERE (td.fiData = d.idData)
AND (sp.SparePartName = '1264-3212'))
OR (NOT EXISTS
(SELECT NULL
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON sp.idSparePart = td.fiSparePart
WHERE (td.fiData = d.idData)
AND (sp.SparePartName = '1254-2975'))
)
)
Unfortunately I don't see how I could use sp.SparePartName IN/NOT IN(#sparePartNames) here.
One way to do it is to create a function to split delimited strings:
CREATE FUNCTION [dbo].[Split]
(
#Delimiter char(1),
#StringToSplit varchar(512)
)
RETURNS table
AS
RETURN
(
WITH Pieces(pieceNumber, startIndex, delimiterIndex)
AS
(
SELECT 1, 1, CHARINDEX(#Delimiter, #StringToSplit)
UNION ALL
SELECT pieceNumber + 1, delimiterIndex + 1, CHARINDEX(#Delimiter, #StringToSplit, delimiterIndex + 1)
FROM Pieces
WHERE delimiterIndex > 0
)
SELECT
SUBSTRING(#StringToSplit, startIndex, CASE WHEN delimiterIndex > 0 THEN delimiterIndex - startIndex ELSE 512 END) AS Value
FROM Pieces
)
populate a table variable with the spare part names:
DECLARE #SpareParts TABLE
(
SparePartName varchar(50) PRIMARY KEY CLUSTERED
);
INSERT INTO #SpareParts
SELECT Value FROM dbo.Split(',', '1264-3212,1254-2975');
and then join to the table variable:
SELECT d.idData
FROM tabData d
INNER JOIN modModel AS m ON d.fiModel = m.idModel
WHERE (m.ModelName = 'MT27I')
AND (d.fiMaxServiceLevel >= 2)
AND (d.Manufacture_Date < '20120511')
AND EXISTS (
SELECT 1
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON sp.idSparePart = td.fiSparePart
LEFT JOIN #SpareParts AS s ON s.SparePartName = sp.SparePartName
WHERE td.fiData = d.idData
AND s.SparePartName IS NULL
)
Assuming there is (or will be) a table or view of mandatory spare parts, a list of exists can be replaced with a left join to tabDataDetail / tabSparePart pair on SparePartName; non-matches are reported back using td.fiSparePart is null.
; with mandatorySpareParts (SparePartName) as (
select '1264-3212'
union all
select '1254-2975'
)
SELECT d.idData
FROM tabData d
INNER JOIN modModel AS m ON d.fiModel = m.idModel
WHERE (m.ModelName = 'MT27I')
AND (d.fiMaxServiceLevel >= 2)
AND (d.Manufacture_Date < '20120511')
AND exists
(
SELECT null
from mandatorySpareParts msp
left join ( tabDataDetail AS td
INNER JOIN tabSparePart AS sp
ON sp.idSparePart = td.fiSparePart
AND td.fiData = d.idData
)
ON msp.SparePartName = sp.SparePartName
WHERE td.fiSparePart is null
)
Part names should be replaced by their id's, which would simplify left join and speed the query up.
EDIT: i've errorneously left filtering of td in where clause, which invalidated left join. It is now in ON clause where it belongs.
Use a table-variable and join on that.

Query to get row from one table, else random row from another

tblUserProfile - I have a table which holds all the Profile Info (too many fields)
tblMonthlyProfiles - Another table which has just the ProfileID in it (the idea is that this table holds 2 profileids which sometimes become monthly profiles (on selection))
Now when I need to show monthly profiles, I simply do a select from this tblMonthlyProfiles and Join with tblUserProfile to get all valid info.
If there are no rows in tblMonthlyProfile, then monthly profile section is not displayed.
Now the requirement is to ALWAYS show Monthly Profiles. If there are no rows in monthlyProfiles, it should pick up 2 random profiles from tblUserProfile. If there is only one row in monthlyProfiles, it should pick up only one random row from tblUserProfile.
What is the best way to do all this in one single query ?
I thought something like this
select top 2 * from tblUserProfile P
LEFT OUTER JOIN tblMonthlyProfiles M
on M.profileid = P.profileid
ORder by NEWID()
But this always gives me 2 random rows from tblProfile. How can I solve this ?
Try something like this:
SELECT TOP 2 Field1, Field2, Field3, FinalOrder FROM
(
select top 2 Field1, Field2, Field3, FinalOrder, '1' As FinalOrder from tblUserProfile P JOIN tblMonthlyProfiles M on M.profileid = P.profileid
UNION
select top 2 Field1, Field2, Field3, FinalOrder, '2' AS FinalOrder from tblUserProfile P LEFT OUTER JOIN tblMonthlyProfiles M on M.profileid = P.profileid ORDER BY NEWID()
)
ORDER BY FinalOrder
The idea being to pick two monthly profiles (if that many exist) and then 2 random profiles (as you correctly did) and then UNION them. You'll have between 2 and 4 records at that point. Grab the top two. FinalOrder column is an easy way to make sure that you try and get the monthly's first.
If you have control of the table structure, you might save yourself some trouble by simply adding a boolean field IsMonthlyProfile to the UserProfile table. Then it's a single table query, order by IsBoolean, NewID()
In SQL 2000+ compliant syntax you could do something like:
Select ...
From (
Select TOP 2 ...
From tblUserProfile As UP
Where Not Exists( Select 1 From tblMonthlyProfile As MP1 )
Order By NewId()
) As RandomProfile
Union All
Select MP....
From tblUserProfile As UP
Join tblMonthlyProfile As MP
On MP.ProfileId = UP.ProfileId
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) >= 1
Union All
Select ...
From (
Select TOP 1 ...
From tblUserProfile As UP
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) = 1
Order By NewId()
) As RandomProfile
Using SQL 2005+ CTE you can do:
With
TwoRandomProfiles As
(
Select TOP 2 ..., ROW_NUMBER() OVER ( ORDER BY UP.ProfileID ) As Num
From tblUserProfile As UP
Order By NewId()
)
Select MP.Col1, ...
From tblUserProfile As UP
Join tblMonthlyProfile As MP
On MP.ProfileId = UP.ProfileId
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) >= 1
Union All
Select ...
From TwoRandomProfiles
Where Not Exists( Select 1 From tblMonthlyProfile As MP1 )
Union All
Select ...
From TwoRandomProfiles
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) = 1
And Num = 1
The CTE has the advantage of only querying for the random profiles once and the use of the ROW_NUMBER() column.
Obviously, in all the UNION statements the number and type of the columns must match.