Set multiple variables and case statement in 1 query - sql

I have
table a with columns lot and tid
table b with columns lot and id
table t with columns id and flag
One of my query set variable #ID to the below query whose flag = 'Y'
The 2nd query set variable #ID_Not_Flag to the below query whose flag <> 'Y'
Can I do the below queries (set #ID and #ID_Not_Flag) in 1 query?
SELECT #ID = a.tid
FROM a, b, t
WHERE b.lot = a.lot and b.id =123 and t.id = a.tid and flag = 'Y'
SELECT #ID_Not_Flag = a.tid
FROM a, b, t
WHERE b.lot = a.lot and b.id =123 and t.id = a.tid and flag <> 'Y'
Table a
lot tid
100 1
100 2
Table b
lot id
100 123
Table t
id flag
1 Y
2 N
Desired result from 1 query:
#ID = 1, #ID_Not_Flag = 2
The below query result in #ID = 1, #ID_Not_Flag = NULL
SELECT
#ID = (CASE WHEN flag = 'Y' THEN a.tid END),
#ID_Not_Flag = (CASE WHEN flag <> 'Y' THEN a.tid END)
FROM a, b, t
WHERE b.lot = a.lot and b.id =123 and t.id = a.tid
SELECT #ID,#ID_Not_Flag
Result (not correct):
ID ID_Not_Flag
1 NULL

You can try a pivot table. The first part of the query is just creating temp tables to hold the data.
/*CREATE TEMP TABLES*/
DECLARE #a TABLE ( lot int, tid int)
DECLARE #b TABLE ( lot int, id int)
DECLARE #t TABLE ( id int, flag VARCHAR(1))
INSERT INTO #a (lot, tid) VALUES (100, 1) , (100, 2)
INSERT INTO #b (lot, id) VALUES (100, 123)
INSERT INTO #t (id, flag) VALUES (1, 'Y') , (2, 'N')
/*QUERY*/
SELECT [Y] [#id] ,
[N] [#id_not_flag]
FROM (
SELECT a.lot, a.tid, b.id , t.flag
--,ID = (CASE WHEN flag = 'Y' THEN a.tid END)
--,ID_Not_Flag = (CASE WHEN flag <> 'Y' THEN a.tid END)
FROM #a a
LEFT JOIN #b b
ON a.lot = b.lot
LEFT JOIN #t t
ON t.id = a.tid) AS Src
PIVOT (max(Src.tid) FOR flag in ([Y] , [N])) Pvt

You can just use another join and COALESCE
Example returns 1 2
DECLARE #a Table(lot INT,tid INT);
DECLARE #b Table(lot int ,id INT);
DECLARE #t Table(id INT,flag CHAR);
INSERT INTO #a(lot, tid)
values
(100, 1),
(100, 2);
INSERT INTO #b(lot, id)
values
(100, 123)
INSERT INTO #t(id, flag)
values
(1, 'Y'),
(2, 'N');
--SELECT * FROM #a;
--SELECT * FROM #b;
--SELECT * FROM #t;
DECLARE
#ID INT,
#ID_Not_Flag INT;
SELECT
#ID = COALESCE(ty.id,#ID),
#ID_Not_Flag = COALESCE(tn.id, #ID_Not_Flag )
FROM #b AS b
INNER JOIN #a AS a ON b.lot = a.lot
LEFT OUTER JOIN #t AS ty ON ty.id = a.tid AND ty.flag = 'Y'
LEFT OUTER JOIN #t AS tn ON tn.id = a.tid AND tn.flag = 'N'
WHERE b.id = 123;
SELECT #ID , #ID_Not_Flag ;

Related

Nested while loop in SQL Server is not showing the expected result

I am trying to connect records from two different tables so I can display the data in a tabular format in an SSRS tablix.
The code below does not return the expected results.
As is, for each item in Temp_A the loop updates everything with the last item in Temp_C. Here is the code:
CREATE TABLE #Temp_A
(
[ID] INT,
[Name] VARCHAR(255)
)
INSERT INTO #Temp_A ([ID], [Name])
VALUES (1, 'A'), (2, 'B')
CREATE TABLE #Temp_C
(
[ID] INT,
[Name] VARCHAR(255)
)
INSERT INTO #Temp_C ([ID], [Name])
VALUES (1, 'C'), (2, 'D')
CREATE TABLE #Temp_Main
(
[Temp_A_ID] INT,
[Temp_A_Name] VARCHAR(255),
[Temp_C_ID] INT,
[Temp_C_Name] VARCHAR(255),
)
DECLARE #MIN_AID int = (SELECT MIN(ID) FROM #Temp_A)
DECLARE #MAX_AID int = (SELECT MAX(ID) FROM #Temp_A)
DECLARE #MIN_DID int = (SELECT MIN(ID) FROM #Temp_C)
DECLARE #MAX_DID int = (SELECT MAX(ID) FROM #Temp_C)
WHILE #MIN_AID <= #MAX_AID
BEGIN
WHILE #MIN_DID <= #MAX_DID
BEGIN
INSERT INTO #Temp_Main([Temp_A_ID], [Temp_A_Name])
SELECT ID, [Name]
FROM #Temp_A
WHERE ID = #MIN_AID
UPDATE #Temp_Main
SET [Temp_C_ID] = ID, [Temp_C_Name] = [Name]
FROM #Temp_C
WHERE ID = #MIN_DID
SET #MIN_DID = #MIN_DID + 1
END
SET #MIN_AID = #MIN_AID + 1
SET #MIN_DID = 1
END
SELECT * FROM #Temp_Main
DROP TABLE #Temp_A
DROP TABLE #Temp_C
DROP TABLE #Temp_Main
Incorrect result:
Temp_A_ID | Temp_A_Name | Temp_C_ID | Temp_C_Name
----------+-------------+-----------+---------------
1 A 2 D
1 A 2 D
2 B 2 D
2 B 2 D
Expected results:
Temp_A_ID | Temp_A_Name | Temp_C_ID | Temp_C_Name
----------+-------------+-----------+---------------
1 A 1 C
1 A 2 D
2 B 1 C
2 B 2 D
What am I missing?
You seem to want a cross join:
select a.*, c.*
from #Temp_A a cross join
#Temp_C c
order by a.id, c.id;
Here is a db<>fiddle.
There is no need to write a WHILE loop to do this.
You can use insert to insert this into #TempMain, but I don't se a need to have a temporary table for storing the results of this query.

Insert IF record EXISTS in one table and not EXISTS in other

Problem with IF EXISTS
I have two tables
create table #Table1
(
DateID date, Shop int, MAC int, Stock int, Transit int
)
INSERT INTO #Table1 values ('01.01.2014', 1, 2, 2,3)
INSERT INTO #Table1 values ('01.04.2014', 1, 2, 2,3)`
create table #Table2
(
DateID date, Shop int, MAC int, OnHand int
)
INSERT INTO #Table2 values ('01.01.2014', 1, 2, 2)
INSERT INTO #Table2 values ('01.01.2014', 2, 3, 1)
INSERT INTO #Table2 values ('01.01.2014', 3, 1, 4)
INSERT INTO #Table2 values ('01.02.2014', 1, 2, 5)
Then I need to do insert into Table 1 if it does not exist in Table 1 DateID, Shop , MAC, but exists in Table 2
I have this statement
DECLARE #Date date = '01.02.2014'
IF EXISTS (SELECT a.DateID, a.Shop, a.MAC
FROM #Table1 a
INNER JOIN #Table2 b ON a.Shop = b.Shop
AND a.MAC = b.MAC
AND a.DateID = b.DateID
WHERE a.DateID = #Date)
INSERT INTO #Table1 (DateID, Shop, MAC, Stock)
select
#Date, a.Shop, a.MAC , sum (OnHand)
from
#Table2 a
where
DateID <= #Date
group by
a.Shop, a.MAC
order by
1
other IF
else
It's working but because it has
IF EXISTS
it's always true, if I am using
IF NOT EXISTS
it's always false and this part never execute.
Please any help!
You can do it in an sql statement instead of an if statement:
insert into #Table1
(DateID, Shop, MAC, Stock)
select #Date, a.Shop, a.MAC , sum(OnHand)
from #Table2 a
left join #Table1 b
ON a.Shop = b.Shop
AND a.MAC = b.MAC
AND a.DateID = b.DateID
where b.Shop is null
group by a.Shop, a.MAC
order by 1
If you need an IF stmt - do something like this:
set #RowsToInsert = 0
select #RowsToInsert = count(*)
from #Table2 a
left join #Table1 b
ON a.Shop = b.Shop AND a.MAC = b.MAC AND a.DateID = b.DateID
where b.Shop is null
IF #RowsToInsert > 0 ... you will need to put insert logic here
IF #RowsToInsert = 0 ... no rows are missing, put update or other logic here
If you'd like to use EXISTS then this might work:
INSERT INTO #Table1 (DateID, Shop, MAC, Stock)
SELECT #Date,
A.Shop,
A.MAC ,
SUM (OnHand)
FROM #Table2 A
WHERE NOT EXISTS
(
SELECT 1
FROM #Table1 B
WHERE A.Shop = B.Shop AND
A.MAC = B.MAC AND
A.DateID = B.DateID
)
AND
A.DateID <= #Date
GROUP BY
A.Shop,
A.MAC
I fixed it by creating NONCLUSTERED INDEX and set IGNORE_DUP_KEY = ON
create table #Table1 (
DateID date, Shop int, MAC int, Stock int, Transit int )
CREATE UNIQUE NONCLUSTERED INDEX IX_RecvID ON #F_STOCK (DateID,Shop ,MAC) WITH (IGNORE_DUP_KEY = ON)

Query to update records from two different tables

I have a query that updates table records.
UPDATE #TEMP
SET [fld_LastName] = CustomerProfile.fld_LastName
,fld_FirstName = CustomerProfile.fld_FirstName
,fld_BirthDate = CustomerProfile.fld_BirthDate
FROM [DB_1].[dbo].[tbl_Customer] AS CustomerProfile --c
WHERE CustomerProfile.fld_CustomerNo = #TEMP.fld_CustomerNo
I want to update the records when the customer is not present in:
[DB_1].[dbo].[tbl_Customer]
I would want to look up to:
[DB_2].[dbo].[tbl_Customer]
How can i do that in an SQL Query?
Thanks a lot.
This should work for you:
UPDATE B
SET [fld_LastName] = CustomerProfile.fld_LastName
,fld_FirstName = CustomerProfile.fld_FirstName
,fld_BirthDate = CustomerProfile.fld_BirthDate
FROM [DB_1].[dbo].[tbl_Customer] AS CustomerProfile INNER JOIN #TEMP B
ON CustomerProfile.fld_CustomerNo = B.fld_CustomerNo
WHERE B.[fld_LastName] IS NULL OR B.fld_FirstName IS NULL OR B.fld_BirthDate IS NULL
You can do it with one update. Something like this:
DECLARE #a TABLE (id INT, val INT)
DECLARE #b TABLE (id INT, val INT)
DECLARE #c TABLE (id INT, val INT)
INSERT #a SELECT 1,NULL UNION SELECT 2,NULL UNION SELECT 3,30
INSERT #b SELECT 1,10
INSERT #c SELECT 1,20 UNION SELECT 2,20
SELECT * FROM #a
UPDATE #a
SET val = COALESCE(b.val,c.val,a.val)
FROM #a a
LEFT JOIN
#b b ON a.id=b.id
LEFT JOIN
#c c ON a.id=c.id
SELECT * FROM #a
This assumes that you prefer NOT NULL values from DB_2 to NULL values in DB_1--if those fields are even nullable.
UPDATE #TEMP
SET fld_LastName = COALESCE(b.fld_LastName,c.fld_LastName,a.fld_LastName)
,fld_FirstName = COALESCE(b.fld_FirstName,c.fld_FirstName,a.fld_FirstName)
,fld_BirthDate = COALESCE(b.fld_BirthDate,c.fld_BirthDate,a.fld_BirthDate)
FROM #Temp a
LEFT JOIN
DB_1.dbo.tbl_Customer b ON a.fld_CustomerNo = b.fld_CustomerNo
LEFT JOIN
DB_2.dbo.tbl_Customer c ON a.fld_CustomerNo = c.fld_CustomerNo

How can I improve this SQL query?

I ran into an interesting SQL problem today and while I came up with a solution that works I doubt it's the best or most efficient answer. I defer to the experts here - help me learn something and improve my query! RDBMS is SQL Server 2008 R2, query is part of an SSRS report that will run against about 100,000 rows.
Essentially I have a list of IDs that could have multiple values associated with them, the values being Yes, No, or some other string. For ID x, if any of the values are a Yes, x should be Yes, if they are all No, it should be No, if they contain any other values but yes and no, display that value. I only want to return 1 row per ID, no duplicates.
The simplified version and test case:
DECLARE #tempTable table ( ID int, Val varchar(1) )
INSERT INTO #tempTable ( ID, Val ) VALUES ( 10, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 11, 'N')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 11, 'N')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 12, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 12, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 12, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 13, 'N')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 14, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 14, 'N')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 15, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 16, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 17, 'F')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 18, 'P')
SELECT DISTINCT t.ID, COALESCE(t2.Val, t3.Val, t4.Val)
FROM #tempTable t
LEFT JOIN
(
SELECT ID, Val
FROM #tempTable
WHERE Val = 'Y'
) t2 ON t.ID = t2.ID
LEFT JOIN
(
SELECT
ID, Val FROM #tempTable
WHERE Val = 'N'
) t3 ON t.ID = t3.ID
LEFT JOIN
(
SELECT ID, Val
FROM #tempTable
WHERE Val <> 'Y' AND Val <> 'N'
) t4 ON t.ID = t4.ID
Thanks in advance.
Let's answer an easier problem: for each id, get the Val which is last in the alphabet. This will work if Y and N are the only values. And the query is much simpler:
SELECT t.ID, MAX(t.Val) FROM t GROUP BY t.ID;
So, reduce your case to the simple case. Use an enum (if your DB supports it) or break the value codes into another table with a collation column (in this case, you could have 1 for Y, 2 for N, and 999 for all other possible values, and you want the smallest). Then
SELECT ID, c.Val FROM
(SELECT t.ID, MIN(codes.collation) AS mx
FROM t join codes on t.Val = codes.Val GROUP BY t.ID) AS q
JOIN codes c ON mx=c.collation;
Here codes has two columns, Val and Collation.
You can also do this with a CTE type query, as long as you have the Values ordered as you want them. This approach has one join to a small lookup table and should be much, much faster than 3 self-joins.
WITH q AS (SELECT t.id, t.Val, ROW_NUMBER() AS r FROM t JOIN codes ON t.Val=codes.Val
PARTITION BY t.id ORDER BY codes.collation)
SELECT q.id, q.Val WHERE r=1;
I'd change it to this just to make it easier to read:
SELECT DISTINCT t.ID, COALESCE(t2.Val, t3.Val, t4.Val)
FROM #tempTable t
LEFT JOIN #tempTable t2 ON t.ID = t2.ID and t2.Val = 'Y'
LEFT JOIN #tempTable t3 ON t.ID = t3.ID and t3.Val = 'N'
LEFT JOIN #tempTable t4 ON t.ID = t4.ID and t4.Val <> 'Y' AND t4.Val <> 'N'
Gives the same results as your example.
I also looked at the execution plans for both and they looked exactly the same, I doubt you'd see any performance difference.
Try this:
;WITH a AS (
SELECT
ID,
SUM(CASE Val WHEN 'Y' THEN 1 ELSE 0 END) AS y,
SUM(CASE Val WHEN 'N' THEN 0 ELSE 1 END) AS n,
MIN(CASE WHEN Val IN ('Y','N') THEN NULL ELSE Val END) AS first_other
FROM #tempTable
GROUP BY ID
)
SELECT
ID,
CASE WHEN y > 0 THEN 'Y' WHEN n = 0 THEN 'N' ELSE first_other END AS Val
FROM a
If there are any 'Y' values then the sum of y will be greater than 0
If all values are 'N' then the sum of n will be zero
Get the first non 'Y' or 'N' character available if needed
In this case the result can be determined with only one pass through
the table
I'm reading your spec like this:
if any ID is Y then Y
if all IDs are N then N
else display value (other than Y or N)
eliminate rows per (1)
delete from #tempTable
where not Val='Y' and ID in (
select distinct ID
from #tempTable
where Val='Y'
)
select distinct to eliminate multiple N's per (2).
select distinct * from #tempTable
group up various "other" values to get a single row per ID.
SELECT A.Id, AllVals =
SubString(
(SELECT ', ' + B.Val
FROM C as B
WHERE A.Id = B.Id
FOR XML PATH ( '' ) ), 3, 1000)
FROM C as A
GROUP BY Id
Entire runnable query:
declare #tempTable table (ID int, Val char(1))
INSERT INTO #tempTable ( ID, Val ) VALUES ( 10, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 11, 'N')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 11, 'N')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 12, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 12, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 12, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 13, 'N')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 14, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 14, 'N')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 15, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 16, 'Y')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 17, 'F')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 18, 'P')
INSERT INTO #tempTable ( ID, Val ) VALUES ( 18, 'F')
delete from #tempTable
where not Val='Y' and ID in (
select distinct ID
from #tempTable
where Val='Y'
);
WITH C as (select distinct * from #tempTable)
SELECT A.Id, AllVals =
SubString(
(SELECT ', ' + B.Val
FROM C as B
WHERE A.Id = B.Id
FOR XML PATH ( '' ) ), 3, 1000)
FROM C as A
GROUP BY Id
OUTPUT:
Id AllVals
10 Y
11 N
12 Y
13 N
14 Y
15 Y
16 Y
17 F
18 F, P

SQL join different tables depending on row information

Suppose I have table A with a field that can be either 1 or 2...
How do I select such that for each row in table A, if the field is 1, join the select with table B and if the field is 2, join the select with table C?
(
SELECT MyField1, MyField2 FROM A
INNER JOIN B ON A.Id = B.Id
AND A.MyField = 1
)
UNION
(
SELECT MyField1, MyField2 FROM A
INNER JOIN C ON A.Id = C.Id
AND A.MyField = 2
)
Somethine like this can work
DECLARE #TableA TABLE(
ID INT
)
DECLARE #TableB TABLE(
ID INT,
Val VARCHAR(50)
)
DECLARE #TableC TABLE(
ID INT,
Val VARCHAR(50)
)
INSERT INTO #TableA SELECT 1
INSERT INTO #TableA SELECT 2
INSERT INTO #TableB SELECT 1, 'B'
INSERT INTO #TableC SELECT 2, 'C'
SELECT *
FROM #TableA a INNER JOIN
#TableB b ON a.ID = b.ID
AND a.ID = 1
UNION
SELECT *
FROM #TableA a INNER JOIN
#TableC c ON a.ID = c.ID
AND a.ID = 2