Selecting min value of a group with id - select

Consider the following code:
DECLARE #table AS TABLE
(
id INT IDENTITY ,
DATA VARCHAR(100) ,
code CHAR(1)
)
INSERT INTO #table
( data, code )
VALUES ( 'xasdf', 'a' ),
( 'aasdf', 'a' ),
( 'basdf', 'a' ),
( 'casdf', 'b' ),
( 'Casdf', 'c' ),
( NULL, NULL )
I need to get a row with minimum data grouped by the code. Can I do this without nested queries?
Basically, what I want is something like this:
SELECT TOP ( 1 )
id ,
MIN(data)
FROM #table
GROUP BY code

SELECT * (SELECT
ROW_NUMBER() OVER(PARTITION BY code ORDER BY code DESC,data DESC) AS Row,
id,DATA,code
FROM #table) T where T.Row = 1

The short answer is NO. This is not possible.

Related

insert with multiple subinserts - reference return value from previous subinsert in a following subinsert

I have a series of tables, each referencing the serial id from the preceding one. Is there a way to insert into all of them with a single statement? I have tried
WITH ins1 AS ( INSERT INTO tab1 ( col ) VALUES ( 'val' ) RETURNING tab1.id AS tab1id ),
ins2 AS ( INSERT INTO tab2 ( col ) VALUES ( tab1id ) RETURNING tab2.id AS tab2id )
INSERT INTO finaltab ( col ) VALUES ( tab2id );
but WITH clause values are only available in the final insert.
Trying
WITH ins2 AS (
INSERT INTO tab2 ( col )
SELECT tab1id FROM (
WITH ins1 AS (
INSERT INTO tab1 ( col )
VALUES ( 'val' )
RETURNING tab1.id AS tab1id
)
SELECT tab1id
)
RETURNING tab2.id AS tab2id
)
INSERT INTO finaltab ( col ) VALUES ( tab2id );
does not work because data-modifying WITH clauses must be top-level.
Is there a way to do this?
You need to select from the CTEs:
WITH ins1 AS (
INSERT INTO tab1 (col)
VALUES ('val')
RETURNING tab1.id AS tab1id
), ins2 AS (
INSERT INTO tab2 (col)
select tab1id
from ins1
RETURNING tab2.id AS tab2id
)
INSERT INTO finaltab (col)
select tab2id
from ins2;

TSQL Case WHEN LIKE REPLACE

Newbie question... looking for the fastest way to update a new column based on the existence of a value from another table, while replacing values.
Example, below, taking the words 'Bought a car' with 'car' into another table. The problem is 'Bought a car' is into another table.
I did a hack to reselect the value and do a replace, but with more rows, the performance is horrible, taking up to 3 to 5 minutes to perform.
Oh SQL Gurus, what is the best way to do this?
Example
DECLARE #Staging_Table TABLE
(
ACCTID INT IDENTITY(1,1),
NAME VARCHAR(50),
PURCHASES VARCHAR(255)
)
INSERT INTO #Staging_Table (Name, Purchases)
VALUES ('John','Bought a table')
INSERT INTO #Staging_Table (Name, Purchases)
VALUES ('Jack','Sold a car')
INSERT INTO #Staging_Table (Name, Purchases)
VALUES ('Mary','Returned a chair')
DECLARE #HISTORY TABLE
(
ACCTID INT IDENTITY(1,1),
NAME VARCHAR(50),
Item VARCHAR(255)
)
INSERT INTO #HISTORY (Name, Item)
VALUES ('John','')
INSERT INTO #HISTORY (Name, Item)
VALUES ('Jack','')
INSERT INTO #HISTORY (Name, Item)
VALUES ('Mary','')
UPDATE #HISTORY
Set ITEM = CASE WHEN EXISTS(
Select ts.Purchases as Output from #Staging_Table ts
where ts.NAME = Name AND ts.PURCHASES LIKE '%table%')
THEN REPLACE((Select ts2.PURCHASES Output
from #Staging_Table ts2 where ts2.NAME = Name AND ts2.PURCHASES LIKE '%table%'),'Bought a ','')
WHEN EXISTS(
Select ts.Purchases as Output from #Staging_Table ts
where ts.NAME = Name AND ts.PURCHASES LIKE '%car%')
THEN REPLACE((Select ts2.PURCHASES Output
from #Staging_Table ts2 where ts2.NAME = Name AND ts2.PURCHASES LIKE '%car%'),'Bought a ','')
End
SELECT * FROM #HISTORY
DECLARE #Staging_Table TABLE
(
ACCTID INT IDENTITY(1, 1) ,
NAME VARCHAR(50) ,
PURCHASES VARCHAR(255)
)
INSERT INTO #Staging_Table
( Name, Purchases )
VALUES ( 'John', 'Bought a table' ),
( 'Jack', 'Sold a car' ),
( 'Mary', 'Returned a chair' )
DECLARE #HISTORY TABLE
(
ACCTID INT IDENTITY(1, 1) ,
NAME VARCHAR(50) ,
Item VARCHAR(255)
)
INSERT INTO #HISTORY
( Name, Item )
VALUES ( 'John', '' ),
( 'Jack', '' ),
( 'Mary', '' )
UPDATE L
SET L.ITEM = ( CASE WHEN R.PURCHASES LIKE '%table%'
THEN REPLACE(R.PURCHASES, 'Bought a ', '')
WHEN R.PURCHASES LIKE '%car%'
THEN REPLACE(R.PURCHASES, 'Sold a ', '')
END )
FROM #HISTORY AS L
JOIN #Staging_Table AS R ON L.NAME = R.NAME
WHERE ( R.PURCHASES LIKE '%table%'
OR R.PURCHASES LIKE '%car%'
)
SELECT *
FROM #HISTORY

TSQL: How to concatenate string of GROUPED values

I encountered a lot of thread about this, the solutions suggested all tend to go the same way, but it is very inconvenient in my case.
Most of the time something like this is suggested.
DECLARE #Actors TABLE ( [Id] INT , [Name] VARCHAR(20) , [MovieId] INT);
DECLARE #Movie TABLE ( [Id] INT, [Name] VARCHAR(20), [FranchiseId] INT );
INSERT INTO #Actors
( Id, Name, MovieId )
VALUES ( 1, 'Sean Connery', 1 ),
( 2, 'Gert Fröbe', 1 ),
( 3, 'Honor Blackman', 1 ),
( 4, 'Daniel Craig', 2 ),
( 5, 'Judi Dench', 2 ),
( 2, 'Harrison Ford', 3 )
INSERT INTO #Movie
( Id, Name, FranchiseId )
VALUES ( 1, 'Goldfinger', 1 ),
( 2, 'Skyfall', 1 ),
( 3, 'Return of the Jedi', 2 )
SELECT m.Name ,
STUFF(( SELECT ',' + a_c.Name
FROM #Actors a_c
WHERE a_c.MovieId = m.Id
FOR
XML PATH('')
), 1, 1, '')
FROM #Actors a
JOIN #Movie m ON a.MovieId = m.Id
GROUP BY m.Id ,
m.Name
The Problem is (how shall I explain?), one does not really access the grouped Items (as Count(), Max(), Min(), ...), one does rebuild the joining pattern of the "outer query" and force in the WHERE statement, that the corresponding values are the same as those in the GROUP BY statement (in the outer query).
If you do not understand what I'm trying to say, I extended the Example above, by one additional table and you will see, that I will also have to extend the "Inner Query"
DECLARE #Actors TABLE ( [Id] INT , [Name] VARCHAR(20) , [MovieId] INT);
DECLARE #Movie TABLE ( [Id] INT, [Name] VARCHAR(20), [FranchiseId] INT );
DECLARE #Franchise TABLE ( [Id] INT , [Name] VARCHAR(20));
INSERT INTO #Actors
( Id, Name, MovieId )
VALUES ( 1, 'Sean Connery', 1 ),
( 2, 'Gert Fröbe', 1 ),
( 3, 'Honor Blackman', 1 ),
( 4, 'Daniel Craig', 2 ),
( 5, 'Judi Dench', 2 ),
( 2, 'Harrison Ford', 3 )
INSERT INTO #Movie
( Id, Name, FranchiseId )
VALUES ( 1, 'Goldfinger', 1 ),
( 2, 'Skyfall', 1 ),
( 3, 'Return of the Jedi', 2 )
INSERT INTO #Franchise
( Id, Name )
VALUES ( 1, 'James Bond' ),
( 2, 'Star Wars' )
SELECT f.Name ,
STUFF(( SELECT ',' + a_c.Name
FROM #Actors a_c
JOIN #Movie m_c ON a_c.MovieId = m_c.Id
WHERE m_c.FranchiseId = f.Id
FOR
XML PATH('')
), 1, 1, '')
FROM #Actors a
JOIN #Movie m ON a.MovieId = m.Id
JOIN #Franchise f ON m.FranchiseId = m.Id
GROUP BY f.Id ,
f.Name
And now, going somewhat further, imagine a huge query, very complicated, several grouping values over many tables. Performance is an issue. I don't want to rebuild the whole joining pattern in the "inner query".
So is there any other way? A way that does not kill performance and you do not have to duplicate the joining pattern?
Contrary to what I said in this comment, you need no GROUP BY clause, nor a WHERE clause, at all!
You simply need the outer SELECT to "iterate" over all franchises (or whatever you want to group by). Then in the inner SELECT, you need some JOINs to get to the franchise key column. Instead of a WHERE clause to filter by the outer franchise's key, simply use the outer franchise key directly in the INNER JOIN:
SELECT f.Name AS FranchiseName,
COALESCE(STUFF((SELECT DISTINCT ', ' + a.Name
FROM #Actor a
JOIN #Movie m ON a.MovieId = m.Id
WHERE m.FranchiseId = f.Id
ORDER BY ', ' + a.Name -- this is optional
FOR XML PATH('')), 1, 1, ''), '') AS ActorNames
FROM #Franchise f
Source of information: "High Performance T-SQL Using Window Functions" by Itzik Ben-Gak. Because SQL Server unfortunately does not have an aggregate/window function for concatenating values, the book's author recommends something like the above as the next best solution.
P.S.: I've removed my previous solution that substituted an additional JOIN for a WHERE clause; I am now fairly certain that a WHERE clause is likely to perform better. Nevertheless, I left some evidence of my previous solution (i.e. the striked-through text) because of that reference to a comment I made earlier.

Select SUM then use it as the parameter for SELECT TOP

I want these two SQL statements in one SQL command. Help :)
Statement #1:
SELECT SUM(nrofitems) as totItems
FROM tblSets
WHERE moduleexamID = 20
Statement #2:
SELECT TOP (cast(totItems as int)) questions
FROM tblQuestions
WHERE moduleexamID = 20
ORDER BY NEWID()
create table #tblSets (
moduleexamID int,
moduleId int,
nrofitems int
)
go
create table #tblQuestions (
moduleexamID int,
body varchar(1024)
)
go
insert into #tblQuestions values
(20,'aaaaaa'),
(20,'bbbbbb'),
(20,'cccccc'),
(20,'dddddd'),
(21,'eeeeee'),
(21,'ffffff'),
(20,'gggggg')
go
insert into #tblSets values
(20,1,1),
(20,2,2),
(21,1,1),
(22,1,3)
go
select top (
select sum(s.nrofitems)
from #tblSets s
where s.moduleexamID=20
) *, newid() as id
from #tblQuestions q
where q.moduleexamID=20
order by id
go
Just use ROW_NUMBER().
Something like this:
SELECT * FROM
(
SELECT tblQuestions.*,
ROW_NUMBER() OVER (ORDER BY NEWID()) as RN
FROM tblQuestions
WHERE moduleexamID = 20
) as T1
WHERE RN<=
ISNULL(
(SELECT SUM(nrofitems) as totItems
FROM tblSets
WHERE moduleexamID = 20
),0);
You may try following also:
;WITH a AS (
SELECT moduleexamID, SUM(nrofitems) as totItems
FROM tblSets
GROUP BY moduleexamID
)
SELECT b.questions
FROM a
CROSS APPLY (
SELECT TOP (cast(a.totItems as int)) questions
FROM tblQuestions
WHERE moduleexamID = a.moduleexamID
ORDER BY CHECKSUM(NEWID())
) b
WHERE a.moduleexamID = 20

PostgreSQL join to denormalize a table with generate_series

I've this table:
CREATE TABLE "mytable"
( name text, count integer );
INSERT INTO mytable VALUES ('john', 4),('mark',2),('albert',3);
and I would like "denormlize" the rows in this way:
SELECT name FROM mytable JOIN generate_series(1,4) tmp(a) ON (a<=count)
so I've a number of rows for each name equals to the count column: I've 4 rows with john, 2 with mark and 3 with albert.
But i can't use the generate_series() function if I don't know the highest count (in this case 4). There is a way to do this without knowing the MAX(count) ?
select name,
generate_series(1,count)
from mytable;
Set returning functions can be used in the select list and will do a cross join with the row retrieved from the base table.
I think this is an undocumented behaviour that might go away in the future, but I'm not sure about that (I recall some discussion regarding this on the mailing list)
SQLFiddle example
DROP TABLE ztable ;
CREATE TABLE ztable (zname varchar, zvalue INTEGER NOT NULL);
INSERT INTO ztable(zname, zvalue) VALUES( 'one', 1), ( 'two', 2 ), ( 'three', 3) , ( 'four', 4 );
WITH expand AS (
WITH RECURSIVE zzz AS (
SELECT 1::integer AS rnk , t0.zname
FROM ztable t0
UNION
SELECT 1+rr.rnk , t1.zname
FROM ztable t1
JOIN zzz rr ON rr.rnk < t1.zvalue
)
SELECT zzz.zname
FROM zzz
)
SELECT x.*
FROM expand x
;