sql server TOP command with order by clause - sql-server

CREATE DATABASE TEST
USE TEST
CREATE TABLE TBL_TEMP
(
ID INT,
NAME VARCHAR(100),
CREATED_ON DATETIME
)
INSERT INTO TBL_TEMP VALUES (1, 'A', NULL)
INSERT INTO TBL_TEMP VALUES (2, 'B', NULL)
INSERT INTO TBL_TEMP VALUES (3, 'C', NULL)
INSERT INTO TBL_TEMP VALUES (4, 'D', NULL)
SELECT TOP 1 *
FROM TBL_TEMP
ORDER BY CREATED_ON
Result:
ID NAME CREATED_ON
------------------
2 B NULL
SELECT TOP 1 * FROM TBL_TEMP
Result:
ID NAME CREATED_ON
--------------------
1 A NULL
Why top 1 gives two different results, is it that when order by clause is used it picks random row and when not used then it gives proper top record ?
is it a kind of bug in sql server 2008 ?

SQL does not guarantee an order unless you specify an ORDER BY clause, so in the second example you get the first-inserted row by good fortune.
If you specify an ORDER BY clause, the order is not defined if the values to sort on are identical. SQL could have selected any one of the four.
This is not a bug, but defined behaviour in SQL.

Related

Compare Previous Row value with Current Row Value Without Using Loops in MSSQL

Source Table
Row_no
Flag
Curr_value
Prev_value
1
C
V1
NULL
2
P
V11
NULL
3
P
V12
NULL
4
C
V2
NULL
5
C
V31
NULL
6
P
V32
NULL
I have a scenario where i have to compare previous row value with current row value and update the Curr_value column based on a case condition and the curr_value and Prev_value should be derived for all the records in the table.
The condition used to derive Curr_value column is case when (Flag='C') then curr_value else Prev_value end and I’m using LAG function in MSSQL to get Previous value column.
OUTPUT
Row_no
Flag
Curr_value
Prev_value
1
C
V1
0
2
P
V1
V1
3
P
V1
V1
4
C
V2
V1
5
C
V3
V2
6
P
V3
V3
I tried implementing the same using While Loop but the execution time is very high. Please let me know if the same output can be achieved without using loops in MSSQL.
I set up the following script as a complete working example:
CREATE TABLE #tmpSampleData(
Row_no int NOT NULL PRIMARY KEY
,Flag varchar(1) NULL
,Curr_value varchar(3) NULL
);
INSERT INTO #tmpSampleData (Row_no,Flag,Curr_value)
VALUES (1, 'C', 'V1')
,(2, 'P', 'V1')
,(3, 'P', 'V1')
,(4, 'C', 'V2')
,(5, 'C', 'V3')
,(6, 'P', 'V3');
SELECT
sample_data.Row_no
,sample_data.Flag
,sample_data.Curr_value
,LAG(sample_data.Curr_value,1,'0') OVER(ORDER BY sample_data.Row_no) AS Prev_value
,CASE
WHEN sample_data.Flag = 'C'
THEN sample_data.Curr_value
ELSE LAG(sample_data.Curr_value,1,'0') OVER(ORDER BY sample_data.Row_no)
END AS CalculatedField
FROM
#tmpSampleData AS sample_data;
IF OBJECT_ID(N'tempdb..#tmpSampleData', N'U') IS NOT NULL
BEGIN
DROP TABLE #tmpSampleData;
END;
But I think the main part you are interested in here is just this:
CASE
WHEN sample_data.Flag = 'C'
THEN sample_data.Curr_value
ELSE LAG(sample_data.Curr_value,1,'0') OVER(ORDER BY sample_data.Row_no)
END AS CalculatedField
Simply add your condition of sample_data.Flag = 'C' and then either return Curr_value or the result of your `LAG
Just use Lag function...
Create table #temp
(
id int,
Fuel int,
dates date
)
Insert into #temp values(1,10,getdate())
Insert into #temp values(2,5,getdate()+1)
Insert into #temp values(3,18,getdate()+2)
Insert into #temp values(4,10,getdate()+3)
Insert into #temp values(5,8,getdate()+4)
Insert into #temp values(6,20,getdate()+5)
Insert into #temp values(7,10,getdate()+6)
Select * from (
SELECT id,
LAG(Fuel, 1) OVER (ORDER BY id ASC) AS previous_Fuel , Fuel, dates
FROM #temp )A where Isnull(previous_Fuel,0) < Fuel

Prevent Grouping rows by NULL value

According to this article:
When grouping with a column in a GROUP BY statement that contains NULLs, they will be put into one group in your result set:
However, what I want is to prevent grouping rows by NULL value.
The following code gives me one row:
IF(OBJECT_ID('tempdb..#TestTable') IS NOT NULL)
DROP TABLE #TestTable
GO
CREATE TABLE #TestTable ( ID INT, Value INT )
INSERT INTO #TestTable(ID, Value) VALUES
(NULL, 70),
(NULL, 70)
SELECT
ID
, Value
FROM #TestTable
GROUP BY ID, Value
The output is:
ID Value
NULL 70
However, I would like to have two rows. My desired result looks like this:
NULL 70
NULL 70
Is it possible to have two rows with GROUP BY?
UPDATE:
What I need is to count those rows:
SELECT
COUNT(1) AS rows
FROM (SELECT 1 AS foo
FROM #TestTable
GROUP BY ID, Value
)q
OUTPUT: 1
But, actually, there are two rows. I need output to have 2.
What you need is a way to make NULL values in Id unique. Using the following code will make the values unique, but continue to group the non-NULL value by virtue of the default value for a case expression being NULL:
group by Id, case when Id is NULL then NewId() end, Value
Assuming you want this behavior because you do want to group by the values of the nullable column (Id in your example), you can add a row_number when the id column is null using a common table expression to create an artificial difference between duplicate groups - like this:
-- Adding some more rows to the table
INSERT INTO #TestTable(ID, Value) VALUES
(NULL, 70),
(NULL, 70),
(1, 70),
(1, 70),
(2, 70);
The query, with the cte:
WITH CTE AS
(
SELECT Id, Value, IIF(Id IS NULL, ROW_NUMBER() OVER(ORDER BY Id), NULL) As Surrogate
FROM #TestTable
)
SELECT
ID
, Value
FROM CTE
GROUP BY ID, Surrogate, Value
Results:
ID Value
NULL 70
NULL 70
1 70
2 70

MS SQL join/insert with non unique matching rows via script?

I need to to prove the existence of the amount of values from table1 in an MS SQL DB.
The table1 for proving has the following values:
MANDT DOKNR LFDNR
1 0020999956
1 0020999958
1 0020999960 2
1 0020999960 3
1 0020999960
1 0020999962
As you can see there are single rows and then there are special cases, where values are doubled with a running number (means it exists three times in the source), so all 2nd/3rd/further entries do get a increasing number in LFDNR.
The target table2 (where I need to proove for the amount/existance) has two columns with matching data:
DataID Facet
42101976 0020999956
42100240 0020999958
65688960 0020999960
65694287 0020999960
65697507 0020999960
42113401 0020999962
I would like to insert the DataID from 2nd table to the first table to have a 'proof', so to see if anything is missing from table2 and keep the table1 as proof.
I tried to uses joins and then I thought about a do while script running all rows down, but my knowledge stops creating scripts for this.
Edit:
Output should be then:
MANDT DOKNR LFDNR DataID
1 0020999956 42101976
1 0020999958 42100240
1 0020999960 2 65688960
1 0020999960 3 65694287
1 0020999960 65697507
1 0020999962 42113401
But it could be, for example, that a row in table 2 is missing, so a DataID would be empty then (and show that one is missing).
Any help appreciated!
You can use ROW_NUMBER to calculated [LFDNR] for each row in the second table, then to update the first table. If the [DataID] is null after the update, we have a mismatch.
DECLARE #table1 TABLE
(
[MANDT] INT
,[DOKNR] VARCHAR(32)
,[LFDNR] INT
,[DataID] INT
);
DECLARE #table2 TABLE
(
[DataID] INT
,[Facet] VARCHAR(32)
);
INSERT INTO #table1 ([MANDT], [DOKNR], [LFDNR])
VALUES (1, '0020999956', NULL)
,(1, '0020999958', NULL)
,(1, '0020999960', 2)
,(1, '0020999960', 3)
,(1, '0020999960', NULL)
,(1, '0020999962',NULL)
INSERT INTO #table2 ([DataID], [Facet])
VALUES (42101976, '0020999956')
,(42100240, '0020999958')
,(65688960, '0020999960')
,(65694287, '0020999960')
,(65697507, '0020999960')
,(42113401, '0020999962');
WITH DataSource ([DataID], [DOKNR], [LFDNR]) AS
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY [Facet] ORDER BY [DataID])
FROM #table2
)
UPDATE #table1
SET [DataID] = DS.[DataID]
FROM #table1 T
INNER JOIN DataSource DS
ON T.[DOKNR] = DS.[DOKNR]
AND ISNULL(T.[LFDNR], 1) = DS.[LFDNR];
SELECT *
FROM #table1;

SQL Update NULL value from select statement query

I'm new to posting on this site, but been using it for a while to get assistance to SQL queries.
I have an issue that I'm trying to resolve. I have 2 columns in a query which are machine and ID, for some machines the ID will be NULL, but for others they will have an ID value as set out below.
Machine ID
test1 3
test12 NULL
test3 4
test4 NULL
As the ID's will be present in the table, I need to update the NULL values, if the machine name is like the one which has a value, for example test 1 and test12 both should have ID 3, but test12 is showing NULL. What I want to be able to do is to replace the NULL for test12 with ID = 3, as the machine names are similar.
I have tried COALESCE, ISNULL and CASE, which all will update the values, but I need know the value, but I wont know it until I have done the select statement.
Any ideas on how to resolve this please?
As noted in the comments you will have to work out your match formula and specify it in the join condition. I believe the query you are looking for is:
Create Table Machines (Name Varchar(8000), ID Int)
Insert Into Machines Values ('test1', 3)
Insert Into Machines Values ('test12', Null)
Insert Into Machines Values ('test3', 4)
Insert Into Machines Values ('test4', Null)
Insert Into Machines Values ('test89', Null)
Insert Into Machines Values ('test8', 5)
Insert Into Machines Values ('test64', Null)
Update M1 Set M1.ID = M2.ID
From Machines M1
Join (Select Left(Name,5) NamePrefix, Max(ID) ID
From Machines
Where ID Is Not Null
Group By Left(Name,5)) M2
On Left(M1.Name, 5) = M2.NamePrefix
Where M1.ID Is Null
Select * From Machines
Note that I used a group by in the joined query in case multiple rows match and we only want one value returned. You can use window functions or other logic instead of the group by if you want to pick specifically which match is chosen.
Based on the requirements in your last comment:
There will be a number of records in the table, they are grouped by the letter before and after the '-' i.e. AB-CDE-L111, AB-CDE-L112, AB-CDE-L113, AB-CDE-L124, AB-CDE-L116 all of these should have in the query an ID of 45. The next set of machines will be AB-CCC-L111, AB-CCC-L112, AB-CCC-L115 all of these should have in the query an ID of 47 and finally there will be the last set of machine, AB-BBB-L113, AB-BBB-L144, AB-BBB-L115, AB-BBB-L120 all of these should have in the query an ID of 50. In the query, a machine returns a NULL ID then I need to update the query results, not the table.
So a SELECT query to get you your results would be:
declare #machine table (Machine varchar(30) not null, ID int null)
insert into #machine
values ('AB-CDE-L111', NULL),
('AB-CDE-L112', NULL),
('AB-CDE-L113', 45),
('AB-CDE-L124', NULL),
('AB-CDE-L116', NULL),
('AB-CCC-L111', NULL),
('AB-CCC-L112', NULL),
('AB-CCC-L113', 47),
('AB-CCC-L124', NULL),
('AB-CCC-L116', NULL),
('AB-BBB-L111', NULL),
('AB-BBB-L112', NULL),
('AB-BBB-L113', 50),
('AB-BBB-L124', NULL),
('AB-BBB-L116', NULL)
select m1.Machine, m2.ID
from #machine m1
inner join #machine m2
on m2.ID is not null
and left(m2.Machine, 6) = left(m1.Machine, 6)
order by m1.Machine
This assumes that:
1) There is always the same amount of characters making up the prefix to the machine code.
2) That there is only one Machine in each group that has been assigned an ID.
If either of these assumptions is wrong then you may need to do extra string manipulation in the case of 1) and use some kind of function (ROW_NUMBER etc.) in the case of 2) to avoid duplicate rows (although you could just use SELECT DISTINCT if the IDs would be the same).

How to get records where csv column contains all the values of a filter

i have a table some departments tagged with user as
User | Department
user1 | IT,HR,House Keeping
user2 | HR,House Keeping
user3 | IT,Finance,HR,Maintainance
user4 | Finance,HR,House Keeping
user5 | IT,HR,Finance
i have created a SP that take parameter varchar(max) as filter (i dynamically merged if in C# code)
in the sp i creat a temp table for the selected filters eg; if user select IT & Finance & HR
i merged the string as IT##Finance##HR (in C#) & call the sp with this parameter
in SP i make a temp table as
FilterValue
IT
Finance
HR
now the issue how can i get the records that contains all the departments taged with them
(users that are associated with all the values in temp table) to get
User | Department
user3 | IT,Finance,HR,Maintainance
user5 | IT,HR,Finance
as optput
please suggest an optimised way to achive this filtering
This design is beyond horrible -- you should really change this to use truly relational design with a dependent table.
That said, if you are not in a position to change the design, you can limp around the problem with XML, and it might give you OK performance.
Try something like this (replace '#test' with your table name as needed...). You won't need to even create your temp table -- this will jimmy your comma-delimited string around into XML, which you can then use XQuery against directly:
DECLARE #test TABLE (usr int, department varchar(1000))
insert into #test (usr, department)
values (1, 'IT,HR,House Keeping')
insert into #test (usr, department)
values (2, 'HR,House Keeping')
insert into #test (usr, department)
values (3, 'IT,Finance,HR,Maintainance')
insert into #test (usr, department)
values (4, 'Finance,HR,House Keeping')
insert into #test (usr, department)
values (5, 'IT,HR,Finance')
;WITH departments (usr, department, depts)
AS
(
SELECT usr, department, CAST(NULLIF('<department><dept>' + REPLACE(department, ',', '</dept><dept>') + '</dept></department>', '<department><dept></dept></department>') AS xml)
FROM #test
)
SELECT departments.usr, departments.department
FROM departments
WHERE departments.depts.exist('/department/dept[text()[1] eq "IT"]') = 1
AND departments.depts.exist('/department/dept[text()[1] eq "HR"]') = 1
AND departments.depts.exist('/department/dept[text()[1] eq "Finance"]') = 1
I agree with others that your design is, um, not ideal, and given the fact that it may change, as per your comment, one is not too motivated to find a really fascinating solution for the present situation.
Still, you can have one that at least works correctly (I think) and meets the situation. Here's what I've come up with:
;
WITH
UserDepartment ([User], Department) AS (
SELECT 'user1', 'IT,HR,House Keeping' UNION ALL
SELECT 'user2', 'HR,House Keeping' UNION ALL
SELECT 'user3', 'IT,Finance,HR,Maintainance' UNION ALL
SELECT 'user4', 'Finance,HR,House Keeping' UNION ALL
SELECT 'user5', 'IT,HR,Finance'
),
Filter (FilterValue) AS (
SELECT 'IT' UNION ALL
SELECT 'Finance' UNION ALL
SELECT 'HR'
),
CSVSplit AS (
SELECT
ud.*,
--x.node.value('.', 'varchar(max)')
x.Value AS aDepartment
FROM UserDepartment ud
CROSS APPLY (SELECT * FROM dbo.Split(',', ud.Department)) x
)
SELECT
c.[User],
c.Department
FROM CSVSplit c
INNER JOIN Filter f ON c.aDepartment = f.FilterValue
GROUP BY
c.[User],
c.Department
HAVING
COUNT(*) = (SELECT COUNT(*) FROM Filter)
The first two CTEs are just sample tables, the rest of the query is the solution proper.
The CSVSplit CTE uses a Split function that splits a comma-separated list into a set of items and returns them as a table. The entire CTE turns the row set of the form
----- ---------------------------
user1 department1,department2,...
... ...
into this:
----- -----------
user1 department1
user1 department2
... ...
The main SELECT joins the normalised row set with the filter table and selects rows where the number of matches exactly equals the number of items in the filter table. (Note: this implies there's no identical names in UserDepartment.Department).

Resources