Click here to monitor SSC
SQLServerCentral is supported by Red Gate Software Ltd.
 
Log in  ::  Register  ::  Not logged in
 
 
 
        
Home       Members    Calendar    Who's On


Add to briefcase ««123»»

TSQL Case Statement help Expand / Collapse
Author
Message
Posted Wednesday, October 3, 2012 1:50 AM


SSCertifiable

SSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiable

Group: General Forum Members
Last Login: Friday, September 12, 2014 9:43 AM
Points: 6,719, Visits: 13,824
CELKO (10/2/2012)
...NO! We hate UDFs; they are not declarative, do not optimize ...


Nonsense. The OP has explained that (s)he is a novice, Joe, and is willing to learn or wouldn't be contributing on this thread. Feeding new pupils with dogma is unethical and unprofessional. You're wrong about optimisation too - iTVF's are subbed into the plan just like a view. Look no further than the two articles by Paul White referenced in my signature block.


“Write the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

For fast, accurate and documented assistance in answering your questions, please read this article.
Understanding and using APPLY, (I) and (II) Paul White
Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden
Exploring Recursive CTEs by Example Dwain Camps
Post #1367443
Posted Wednesday, October 3, 2012 1:51 AM


SSCertifiable

SSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiable

Group: General Forum Members
Last Login: Friday, September 12, 2014 9:43 AM
Points: 6,719, Visits: 13,824
John Mitchell-245523 (10/2/2012)
I think I would create a lookup table, like this:

CREATE TABLE CodeActions (
Code char(4)
, TheAction varchar(12)
)

INSERT INTO CodeActions (Code,TheAction)
VALUES
('ISSP','Install'),
('IECO','Install'),
('IECM','Install'),
('IESP','Install'),
('IEHD','Install'),
('ISHD','Install'),
('FRSI','Install'),
('SB42','Service Call'),
('SB4W','Service Call'),
('HD42','Service Call'),
('HD4W','Service Call'),
('SA2C','Service Call'),
('SA2W','Service Call'),
('HD2C','Service Call'),
('HD2W','Service Call'),
('SNCO','Service Call')

That way you don't have to play about with lengthy function definitions, nor rewrite them every time a code changes.

Your function becomes:
ALTER FUNCTION [dbo].[Tester] (@jdt_jty_code varchar(50))
Returns varchar(50)
as
Begin

Return
SELECT
COALESCE(TheAction,'UNKNOWN')
FROM
CodeActions
WHERE
Code = @jdt_jty_code

END

which is so trivial that you probably don't even need a function for it. If you decide to keep it, bear in mind what Chris said about table-valued vs scalar functions. If you're going to use this function on large amounts of data, you'll take a performance hit if you leave it like it is.

John


+1


“Write the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

For fast, accurate and documented assistance in answering your questions, please read this article.
Understanding and using APPLY, (I) and (II) Paul White
Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden
Exploring Recursive CTEs by Example Dwain Camps
Post #1367444
Posted Wednesday, October 3, 2012 1:53 AM
Grasshopper

GrasshopperGrasshopperGrasshopperGrasshopperGrasshopperGrasshopperGrasshopperGrasshopper

Group: General Forum Members
Last Login: Sunday, October 20, 2013 3:13 PM
Points: 20, Visits: 79
Yep this is the method ive decieded to use so thumbs up to everyone for helping out :)
Post #1367445
Posted Wednesday, October 3, 2012 3:45 AM


SSCertifiable

SSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiable

Group: General Forum Members
Last Login: Friday, September 12, 2014 9:43 AM
Points: 6,719, Visits: 13,824
ScottPletcher (10/2/2012)
SQL Server is optimized to do table lookups, whereas CASE statements are comparatively very slow.

Therefore, I suggest using a lookup table, as suggested by others. You absolutely want to make the lookup code the unqiue clustering key to the table, to speed up SQL's table search. You can make it an actual PRIMARY KEY also, if you want to, but that's not required.


That's interesting. I know from experience that CROSSTAB queries using CASE can be accelerated if the data is preaggregated before applying the aggregate across the CASE statements. I never really considered that CASE statements might significantly slow up a straightforward SELECT without aggregation. So here's a quick and dirty test:

-- crude test of cost of CASE 
DROP TABLE #Temp
SELECT TOP 200000 rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
INTO #Temp
FROM sys.columns a, sys.columns b, sys.columns c

DROP TABLE #temp2
DROP TABLE #temp3

PRINT 'Simple SELECT'
SET STATISTICS TIME ON
SELECT rn, rn2 = CAST(rn AS VARCHAR(6))
INTO #temp2
FROM #Temp
SET STATISTICS TIME OFF

PRINT 'SELECT with 60 CASE alternatives'
SET STATISTICS TIME ON
SELECT rn,
rn2 = CASE rn
WHEN 1 THEN CAST(rn AS VARCHAR(6))
WHEN 2 THEN CAST(rn AS VARCHAR(6))
WHEN 3 THEN CAST(rn AS VARCHAR(6))
WHEN 4 THEN CAST(rn AS VARCHAR(6))
WHEN 5 THEN CAST(rn AS VARCHAR(6))
WHEN 6 THEN CAST(rn AS VARCHAR(6))
WHEN 7 THEN CAST(rn AS VARCHAR(6))
WHEN 8 THEN CAST(rn AS VARCHAR(6))
WHEN 9 THEN CAST(rn AS VARCHAR(6))
WHEN 10 THEN CAST(rn AS VARCHAR(6))
WHEN 11 THEN CAST(rn AS VARCHAR(6))
WHEN 12 THEN CAST(rn AS VARCHAR(6))
WHEN 13 THEN CAST(rn AS VARCHAR(6))
WHEN 14 THEN CAST(rn AS VARCHAR(6))
WHEN 15 THEN CAST(rn AS VARCHAR(6))
WHEN 16 THEN CAST(rn AS VARCHAR(6))
WHEN 17 THEN CAST(rn AS VARCHAR(6))
WHEN 18 THEN CAST(rn AS VARCHAR(6))
WHEN 19 THEN CAST(rn AS VARCHAR(6))
WHEN 20 THEN CAST(rn AS VARCHAR(6))

WHEN 100 THEN CAST(rn AS VARCHAR(6))
WHEN 200 THEN CAST(rn AS VARCHAR(6))
WHEN 300 THEN CAST(rn AS VARCHAR(6))
WHEN 400 THEN CAST(rn AS VARCHAR(6))
WHEN 500 THEN CAST(rn AS VARCHAR(6))
WHEN 600 THEN CAST(rn AS VARCHAR(6))
WHEN 700 THEN CAST(rn AS VARCHAR(6))
WHEN 800 THEN CAST(rn AS VARCHAR(6))
WHEN 900 THEN CAST(rn AS VARCHAR(6))
WHEN 1000 THEN CAST(rn AS VARCHAR(6))
WHEN 1100 THEN CAST(rn AS VARCHAR(6))
WHEN 1200 THEN CAST(rn AS VARCHAR(6))
WHEN 1300 THEN CAST(rn AS VARCHAR(6))
WHEN 1400 THEN CAST(rn AS VARCHAR(6))
WHEN 1500 THEN CAST(rn AS VARCHAR(6))
WHEN 1600 THEN CAST(rn AS VARCHAR(6))
WHEN 1700 THEN CAST(rn AS VARCHAR(6))
WHEN 1800 THEN CAST(rn AS VARCHAR(6))
WHEN 1900 THEN CAST(rn AS VARCHAR(6))
WHEN 2000 THEN CAST(rn AS VARCHAR(6))

WHEN 10000 THEN CAST(rn AS VARCHAR(6))
WHEN 20000 THEN CAST(rn AS VARCHAR(6))
WHEN 30000 THEN CAST(rn AS VARCHAR(6))
WHEN 40000 THEN CAST(rn AS VARCHAR(6))
WHEN 50000 THEN CAST(rn AS VARCHAR(6))
WHEN 60000 THEN CAST(rn AS VARCHAR(6))
WHEN 70000 THEN CAST(rn AS VARCHAR(6))
WHEN 80000 THEN CAST(rn AS VARCHAR(6))
WHEN 90000 THEN CAST(rn AS VARCHAR(6))
WHEN 100000 THEN CAST(rn AS VARCHAR(6))
WHEN 110000 THEN CAST(rn AS VARCHAR(6))
WHEN 120000 THEN CAST(rn AS VARCHAR(6))
WHEN 130000 THEN CAST(rn AS VARCHAR(6))
WHEN 140000 THEN CAST(rn AS VARCHAR(6))
WHEN 150000 THEN CAST(rn AS VARCHAR(6))
WHEN 160000 THEN CAST(rn AS VARCHAR(6))
WHEN 170000 THEN CAST(rn AS VARCHAR(6))
WHEN 180000 THEN CAST(rn AS VARCHAR(6))
WHEN 190000 THEN CAST(rn AS VARCHAR(6))
WHEN 200000 THEN CAST(rn AS VARCHAR(6))
ELSE
CAST(rn AS VARCHAR(6))
END
INTO #temp3
FROM #Temp
SET STATISTICS TIME OFF




I ran the statements a number of times, returning the results to screen or to #temp table from my local instance. Here are the average values for 10 runs, returning to #temp tables:

 Simple SELECT

SQL Server Execution Times:
CPU time = 123.4 ms, elapsed time = 123.2 ms.

(200000 row(s) affected)


SELECT with 60 CASE alternatives

SQL Server Execution Times:
CPU time = 112.3 ms, elapsed time = 160.0 ms.

(200000 row(s) affected)


Adding loads of CASE alternatives doesn't appear to change the CPU time very much at all but appears to have a quite significant effect on the elapsed time – increasing it by about 30%.
I switched to using startdatetime/enddatetime, like this:

DECLARE @Startdate DATETIME

PRINT 'Simple SELECT'
SET @Startdate = GETDATE()
--SET STATISTICS TIME ON
SELECT rn, rn2 = CAST(rn AS VARCHAR(6))
INTO #temp2
FROM #Temp
--SET STATISTICS TIME OFF
PRINT DATEDIFF(MILLISECOND,@Startdate,GETDATE()) --@MSDuration



and here are the averaged results from 10 runs:

 Simple SELECT

(200000 row(s) affected)
136.3


SELECT with 60 CASE alternatives

(200000 row(s) affected)
159.7


The difference this time is a little less than 20%. The conclusion I’m going to take home from this is – “you can add quite a few options into a CASE statement before it will significantly affect the run time of your query”. What it doesn’t do is account for the relative cost of each option evaluated, i.e. what happens if the CASE options are computationally much more expensive than casting an INT to a VARCHAR? I think you can guess


“Write the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

For fast, accurate and documented assistance in answering your questions, please read this article.
Understanding and using APPLY, (I) and (II) Paul White
Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden
Exploring Recursive CTEs by Example Dwain Camps
Post #1367500
Posted Wednesday, October 3, 2012 8:57 AM
SSCrazy

SSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazy

Group: General Forum Members
Last Login: 2 days ago @ 3:14 PM
Points: 2,098, Visits: 3,155
ChrisM@Work (10/3/2012)
ScottPletcher (10/2/2012)
SQL Server is optimized to do table lookups, whereas CASE statements are comparatively very slow.

Therefore, I suggest using a lookup table, as suggested by others. You absolutely want to make the lookup code the unqiue clustering key to the table, to speed up SQL's table search. You can make it an actual PRIMARY KEY also, if you want to, but that's not required.


That's interesting. I know from experience that CROSSTAB queries using CASE can be accelerated if the data is preaggregated before applying the aggregate across the CASE statements. I never really considered that CASE statements might significantly slow up a straightforward SELECT without aggregation. So here's a quick and dirty test:

-- crude test of cost of CASE 
DROP TABLE #Temp
SELECT TOP 200000 rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
INTO #Temp
FROM sys.columns a, sys.columns b, sys.columns c

DROP TABLE #temp2
DROP TABLE #temp3

PRINT 'Simple SELECT'
SET STATISTICS TIME ON
SELECT rn, rn2 = CAST(rn AS VARCHAR(6))
INTO #temp2
FROM #Temp
SET STATISTICS TIME OFF

PRINT 'SELECT with 60 CASE alternatives'
SET STATISTICS TIME ON
SELECT rn,
rn2 = CASE rn
WHEN 1 THEN CAST(rn AS VARCHAR(6))
WHEN 2 THEN CAST(rn AS VARCHAR(6))
WHEN 3 THEN CAST(rn AS VARCHAR(6))
WHEN 4 THEN CAST(rn AS VARCHAR(6))
WHEN 5 THEN CAST(rn AS VARCHAR(6))
WHEN 6 THEN CAST(rn AS VARCHAR(6))
WHEN 7 THEN CAST(rn AS VARCHAR(6))
WHEN 8 THEN CAST(rn AS VARCHAR(6))
WHEN 9 THEN CAST(rn AS VARCHAR(6))
WHEN 10 THEN CAST(rn AS VARCHAR(6))
WHEN 11 THEN CAST(rn AS VARCHAR(6))
WHEN 12 THEN CAST(rn AS VARCHAR(6))
WHEN 13 THEN CAST(rn AS VARCHAR(6))
WHEN 14 THEN CAST(rn AS VARCHAR(6))
WHEN 15 THEN CAST(rn AS VARCHAR(6))
WHEN 16 THEN CAST(rn AS VARCHAR(6))
WHEN 17 THEN CAST(rn AS VARCHAR(6))
WHEN 18 THEN CAST(rn AS VARCHAR(6))
WHEN 19 THEN CAST(rn AS VARCHAR(6))
WHEN 20 THEN CAST(rn AS VARCHAR(6))

WHEN 100 THEN CAST(rn AS VARCHAR(6))
WHEN 200 THEN CAST(rn AS VARCHAR(6))
WHEN 300 THEN CAST(rn AS VARCHAR(6))
WHEN 400 THEN CAST(rn AS VARCHAR(6))
WHEN 500 THEN CAST(rn AS VARCHAR(6))
WHEN 600 THEN CAST(rn AS VARCHAR(6))
WHEN 700 THEN CAST(rn AS VARCHAR(6))
WHEN 800 THEN CAST(rn AS VARCHAR(6))
WHEN 900 THEN CAST(rn AS VARCHAR(6))
WHEN 1000 THEN CAST(rn AS VARCHAR(6))
WHEN 1100 THEN CAST(rn AS VARCHAR(6))
WHEN 1200 THEN CAST(rn AS VARCHAR(6))
WHEN 1300 THEN CAST(rn AS VARCHAR(6))
WHEN 1400 THEN CAST(rn AS VARCHAR(6))
WHEN 1500 THEN CAST(rn AS VARCHAR(6))
WHEN 1600 THEN CAST(rn AS VARCHAR(6))
WHEN 1700 THEN CAST(rn AS VARCHAR(6))
WHEN 1800 THEN CAST(rn AS VARCHAR(6))
WHEN 1900 THEN CAST(rn AS VARCHAR(6))
WHEN 2000 THEN CAST(rn AS VARCHAR(6))

WHEN 10000 THEN CAST(rn AS VARCHAR(6))
WHEN 20000 THEN CAST(rn AS VARCHAR(6))
WHEN 30000 THEN CAST(rn AS VARCHAR(6))
WHEN 40000 THEN CAST(rn AS VARCHAR(6))
WHEN 50000 THEN CAST(rn AS VARCHAR(6))
WHEN 60000 THEN CAST(rn AS VARCHAR(6))
WHEN 70000 THEN CAST(rn AS VARCHAR(6))
WHEN 80000 THEN CAST(rn AS VARCHAR(6))
WHEN 90000 THEN CAST(rn AS VARCHAR(6))
WHEN 100000 THEN CAST(rn AS VARCHAR(6))
WHEN 110000 THEN CAST(rn AS VARCHAR(6))
WHEN 120000 THEN CAST(rn AS VARCHAR(6))
WHEN 130000 THEN CAST(rn AS VARCHAR(6))
WHEN 140000 THEN CAST(rn AS VARCHAR(6))
WHEN 150000 THEN CAST(rn AS VARCHAR(6))
WHEN 160000 THEN CAST(rn AS VARCHAR(6))
WHEN 170000 THEN CAST(rn AS VARCHAR(6))
WHEN 180000 THEN CAST(rn AS VARCHAR(6))
WHEN 190000 THEN CAST(rn AS VARCHAR(6))
WHEN 200000 THEN CAST(rn AS VARCHAR(6))
ELSE
CAST(rn AS VARCHAR(6))
END
INTO #temp3
FROM #Temp
SET STATISTICS TIME OFF




I ran the statements a number of times, returning the results to screen or to #temp table from my local instance. Here are the average values for 10 runs, returning to #temp tables:

 Simple SELECT

SQL Server Execution Times:
CPU time = 123.4 ms, elapsed time = 123.2 ms.

(200000 row(s) affected)


SELECT with 60 CASE alternatives

SQL Server Execution Times:
CPU time = 112.3 ms, elapsed time = 160.0 ms.

(200000 row(s) affected)


Adding loads of CASE alternatives doesn't appear to change the CPU time very much at all but appears to have a quite significant effect on the elapsed time – increasing it by about 30%.
I switched to using startdatetime/enddatetime, like this:

DECLARE @Startdate DATETIME

PRINT 'Simple SELECT'
SET @Startdate = GETDATE()
--SET STATISTICS TIME ON
SELECT rn, rn2 = CAST(rn AS VARCHAR(6))
INTO #temp2
FROM #Temp
--SET STATISTICS TIME OFF
PRINT DATEDIFF(MILLISECOND,@Startdate,GETDATE()) --@MSDuration



and here are the averaged results from 10 runs:

 Simple SELECT

(200000 row(s) affected)
136.3


SELECT with 60 CASE alternatives

(200000 row(s) affected)
159.7


The difference this time is a little less than 20%. The conclusion I’m going to take home from this is – “you can add quite a few options into a CASE statement before it will significantly affect the run time of your query”. What it doesn’t do is account for the relative cost of each option evaluated, i.e. what happens if the CASE options are computationally much more expensive than casting an INT to a VARCHAR? I think you can guess




I consider 20-30% relatively much slower, particularly given how basic the task was.

A 5 min query then takes ~6+ mins instead. Not tragic, obviously, but significant.


SQL DBA,SQL Server MVP('07, '08, '09)

"We came in spastic, Like tameless horses /
We left in plastic, As numbered corpses / ...
Remember Charlie, Remember Baker /
They left their childhood On every acre /
And who was wrong? And who was right? /
It didn't matter in the thick of the fight." : the inimitable Mr. Billy Joel, about the Vietnam War
Post #1367753
Posted Wednesday, October 3, 2012 9:04 AM


SSCertifiable

SSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiable

Group: General Forum Members
Last Login: Friday, September 12, 2014 9:43 AM
Points: 6,719, Visits: 13,824
ScottPletcher (10/3/2012)
[quote][b]<<snip>>
I consider 20-30% relatively much slower, particularly given how basic the task was.

A 5 min query then takes ~6+ mins instead. Not tragic, obviously, but significant.


The figures are certainly worth remembering (20 computationally simple CASE options could slow your code by as much as 10%) - but in any case thanks, Scott, for the stimulation to code up the test.


“Write the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

For fast, accurate and documented assistance in answering your questions, please read this article.
Understanding and using APPLY, (I) and (II) Paul White
Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden
Exploring Recursive CTEs by Example Dwain Camps
Post #1367761
Posted Thursday, October 4, 2012 1:57 AM


SSCertifiable

SSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiable

Group: General Forum Members
Last Login: Today @ 6:01 PM
Points: 7,689, Visits: 9,415
CELKO (10/2/2012)
NO! We hate UDFs; they are not declarative, do not optimize and stink of 1950's FORTRAN. SQL is declarative and uses tables! This is a huge change in your mindset.

I don't know what you think declarative means, but the function that the OP is considering most certainly IS declarative in the sense the term is used by computer scientists and mathematicians, which is probably the only sense in which it should be used in a forum about T-SQL.


Tom
Post #1368214
Posted Thursday, October 4, 2012 1:59 AM


SSCertifiable

SSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiableSSCertifiable

Group: General Forum Members
Last Login: Today @ 6:01 PM
Points: 7,689, Visits: 9,415
ChrisM@Work (10/3/2012)
CELKO (10/2/2012)
...NO! We hate UDFs; they are not declarative, do not optimize ...


Nonsense. The OP has explained that (s)he is a novice, Joe, and is willing to learn or wouldn't be contributing on this thread. Feeding new pupils with dogma is unethical and unprofessional. You're wrong about optimisation too - iTVF's are subbed into the plan just like a view. Look no further than the two articles by Paul White referenced in my signature block.

+1


Tom
Post #1368216
Posted Thursday, October 4, 2012 4:26 AM
SSCrazy

SSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazy

Group: General Forum Members
Last Login: 2 days ago @ 5:50 AM
Points: 2,856, Visits: 5,124
CELKO (10/2/2012)
[quote]
...
NO! We hate UDFs; they are not declarative, do not optimize and stink of 1950's FORTRAN. SQL is declarative and uses tables! This is a huge change in your mindset.
...


Dear Mr Celko,
It's a pure lie! YOU may have some psychophysical problems, so you hate UDF's and feel language smells. There are may be some more like you around, but at least I am not one of them, so "some of your kind" doesn't build into "WE" on this forum.
So, WE don't! We don't hate UDF's and most of us don't feel any smells from 1950's.
It would be really great if you could learn your native language a bit more: "I" is used for first-person singular expressions, until you refer to yourself as to monarch (I'll be very surprised if US man/patriot would really do that) or you have split personality disorder (I hope you don't speak too often to Murat?)

OP has stated he is new in SQL, and he is asking for help not for some outdated and irrelevant personal stances on SQL Server powerful features from some theoretical demagogues.
But you are wrong even from theoretical point of view.
Functional programming is a subtype of declarative programming! It attempts to minimize or eliminate side effects, and is therefore considered declarative!
Also you are wrong about UDF optimization in SQL Server server. They are optimized!
Some more than another but optimized!
You better learn a bit more how to write proper UDF function before making such comments!
Good luck with sale of your literature


_____________________________________________
"The only true wisdom is in knowing you know nothing"
"O skol'ko nam otkrytiy chudnyh prevnosit microsofta duh!"
(So many miracle inventions provided by MS to us...)

How to post your question to get the best and quick help
Post #1368288
Posted Thursday, October 4, 2012 4:42 AM
Grasshopper

GrasshopperGrasshopperGrasshopperGrasshopperGrasshopperGrasshopperGrasshopperGrasshopper

Group: General Forum Members
Last Login: Sunday, October 20, 2013 3:13 PM
Points: 20, Visits: 79
Dudes..calm down...

What i ended up doing is creating a table in Excel and imported into the datawarehouse from which created a join in my query

I'll keep playing around with Stored Proceedures and Functions until i feel im at a competent level

Could the Mods please freeze this thread before some real abuse gets hurled around
Post #1368298
« Prev Topic | Next Topic »

Add to briefcase ««123»»

Permissions Expand / Collapse